LengthFieldBasedFrameDecoder 长度拆包器
因为 TCP 底层就是字节流,所以我们协议中必须定义各个包的分割界限,上层应用才能分离出每一个业务包
根据长度拆包是用的最多的一个拆包器了
通常比如协议前2个字节表示数据的长度,后面紧跟数据。这样我们先读取长度字段,根据长度字段再接着往后读固定长度的字节就拆出了一个业务包,就这样重复一直往前读,一个包一个包的拆。
参数常用组合
- 长度字段表示数据的长度(不包括长度字段本身),initialBytesToStrip=0 表示拆包后不剔除长度字段
* lengthFieldOffset = 0
* lengthFieldLength = 2
* lengthAdjustment = 0
* initialBytesToStrip = 0 (= do not strip header)
*
* BEFORE DECODE (14 bytes) AFTER DECODE (14 bytes)
* +--------+----------------+ +--------+----------------+
* | Length | Actual Content |----->| Length | Actual Content |
* | 0x000C | "HELLO, WORLD" | | 0x000C | "HELLO, WORLD" |
* +--------+----------------+ +--------+----------------+
从0开始的2两个字节表示数据包长度, 0xC 表示数据包的长度是 12,即 HELLO, WORLD 的长度(中间有个空格)12个字节。
- 长度字段表示数据的长度(不包括长度字段本身),initialBytesToStrip=2 表示拆包后剔除长度字段
* lengthFieldOffset = 0
* lengthFieldLength = 2
* lengthAdjustment = 0
* initialBytesToStrip = 2 (= the length of the Length field)
*
* BEFORE DECODE (14 bytes) AFTER DECODE (12 bytes)
* +--------+----------------+ +----------------+
* | Length | Actual Content |----->| Actual Content |
* | 0x000C | "HELLO, WORLD" | | "HELLO, WORLD" |
* +--------+----------------+ +----------------+
很多时候拆包后想把开头的长度字段去掉,指定 initialBytesToStrip 等于长度字段的字节数就行。拆包后就不包括长度字段了。
- 有些协议长度字段表示的是整个包的长度(包含数据长度 + 长度字段的长度)
* lengthFieldOffset = 0
* lengthFieldLength = 2
* lengthAdjustment = -2 (= the length of the Length field)
* initialBytesToStrip = 0
*
* BEFORE DECODE (14 bytes) AFTER DECODE (14 bytes)
* +--------+----------------+ +--------+----------------+
* | Length | Actual Content |----->| Length | Actual Content |
* | 0x000E | "HELLO, WORLD" | | 0x000E | "HELLO, WORLD" |
* +--------+----------------+ +--------+----------------+
0xE 表示整个包的长度是 14 个字节,指定 lengthAdjustment=-2 往前调整 2 个字节就跟第一种情况一样了
- 长度字段表示数据的长度,只是长度字段不在开头而在中间,这时只要设置 lengthFieldOffset 就行
* lengthFieldOffset = 2 (= the length of Header 1)
* lengthFieldLength = 3
* lengthAdjustment = 0
* initialBytesToStrip = 0
*
* BEFORE DECODE (17 bytes) AFTER DECODE (17 bytes)
* +----------+----------+----------------+ +----------+----------+----------------+
* | Header 1 | Length | Actual Content |----->| Header 1 | Length | Actual Content |
* | 0xCAFE | 0x00000C | "HELLO, WORLD" | | 0xCAFE | 0x00000C | "HELLO, WORLD" |
* +----------+----------+----------------+ +----------+----------+----------------+
- 长度字段还是表示数据的长度,但长度字段和数据之间隔了一个 Header
* lengthFieldOffset = 0
* lengthFieldLength = 3
* lengthAdjustment = 2 (= the length of Header 1)
* initialBytesToStrip = 0
*
* BEFORE DECODE (17 bytes) AFTER DECODE (17 bytes)
* +----------+----------+----------------+ +----------+----------+----------------+
* | Length | Header 1 | Actual Content |----->| Length | Header 1 | Actual Content |
* | 0x00000C | 0xCAFE | "HELLO, WORLD" | | 0x00000C | 0xCAFE | "HELLO, WORLD" |
* +----------+----------+----------------+ +----------+----------+----------------+
指定 lengthAdjustment=2 表示向后调整2个字节就行(就是要加上Header 1 的长度)
- 综合运用1,长度字段还是表示数据的长度
* lengthFieldOffset = 1 (= the length of HDR1)
* lengthFieldLength = 2
* lengthAdjustment = 1 (= the length of HDR2)
* initialBytesToStrip = 3 (= the length of HDR1 + LEN)
*
* BEFORE DECODE (16 bytes) AFTER DECODE (13 bytes)
* +------+--------+------+----------------+ +------+----------------+
* | HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content |
* | 0xCA | 0x000C | 0xFE | "HELLO, WORLD" | | 0xFE | "HELLO, WORLD" |
* +------+--------+------+----------------+ +------+----------------+
一看就懂,不多解释
- 综合运用2 ,与6的唯一区别是长度字段表示的是整个包的长度
* lengthFieldOffset = 1
* lengthFieldLength = 2
* lengthAdjustment = -3 (= the length of HDR1 + LEN, negative)
* initialBytesToStrip = 3
*
* BEFORE DECODE (16 bytes) AFTER DECODE (13 bytes)
* +------+--------+------+----------------+ +------+----------------+
* | HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content |
* | 0xCA | 0x0010 | 0xFE | "HELLO, WORLD" | | 0xFE | "HELLO, WORLD" |
* +------+--------+------+----------------+ +------+----------------+
0x0010 表示整个包的长度是 16个字节,initialBytesToStrip=3 表示剔除掉前3个字节, lengthAdjustment=-3 表示向前调整3。HDR2 不需要考虑,因为长度字段里面已经包含 HDR2 了。
有些情况长度字段是加密的或者字节序跟数据不一致,这时继承重写 getUnadjustedFrameLength
就行了。类在设计时就考虑到了会有这种情况,我们的协议就是加密的,长度字段也需要手动解密。
public class MyLengthFieldBasedFrameDecoder extends LengthFieldBasedFrameDecoder {
public MyLengthFieldBasedFrameDecoder() {
super(5000, 0, 2, 0, 0);
}
@Override
protected long getUnadjustedFrameLength(ByteBuf buf, int offset, int length, ByteOrder order) {
int low = buf.getByte(offset) & 0xff;
int high = buf.getByte(offset + 1) & 0xff;
int len = high;
len = (len << 8) | low;
return len;
}
}
offset 表示每一个包长度字段的偏移量, length 是长度字段的字节数, order 是大小端字节序。
上面基本能覆盖长度分包 95% 的场景, 再复杂的只能自己继承重写,或仿照重写修改了。
多看源码,写一个健壮的分包器是很难的,因为网络连接是不稳定的,包可能损坏,有可能长度字段很长一直拆不完。如果第一个包就乱掉,后面基本很难拆出正确的包了。