美文网首页
netty LengthFieldBasedFrameDeco

netty LengthFieldBasedFrameDeco

作者: lesliefang | 来源:发表于2020-01-08 18:41 被阅读0次

LengthFieldBasedFrameDecoder 长度拆包器

因为 TCP 底层就是字节流,所以我们协议中必须定义各个包的分割界限,上层应用才能分离出每一个业务包

根据长度拆包是用的最多的一个拆包器了

通常比如协议前2个字节表示数据的长度,后面紧跟数据。这样我们先读取长度字段,根据长度字段再接着往后读固定长度的字节就拆出了一个业务包,就这样重复一直往前读,一个包一个包的拆。

参数常用组合

  1. 长度字段表示数据的长度(不包括长度字段本身),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个字节。

  1. 长度字段表示数据的长度(不包括长度字段本身),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 等于长度字段的字节数就行。拆包后就不包括长度字段了。

  1. 有些协议长度字段表示的是整个包的长度(包含数据长度 + 长度字段的长度)
 * 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 个字节就跟第一种情况一样了

  1. 长度字段表示数据的长度,只是长度字段不在开头而在中间,这时只要设置 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" |
 * +----------+----------+----------------+      +----------+----------+----------------+
  1. 长度字段还是表示数据的长度,但长度字段和数据之间隔了一个 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. 综合运用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" |
 * +------+--------+------+----------------+      +------+----------------+

一看就懂,不多解释

  1. 综合运用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% 的场景, 再复杂的只能自己继承重写,或仿照重写修改了。

要注意字节序的问题,字节序不对长度计算也就不对了,如果不传字节序默认是 BIG_ENDIAN,如果是小端构造函数第一个参数可传入 ByteOrder.LITTLE_ENDIAN 指定。

多看源码,写一个健壮的分包器是很难的,因为网络连接是不稳定的,包可能损坏,有可能长度字段很长一直拆不完。如果第一个包就乱掉,后面基本很难拆出正确的包了。

相关文章

网友评论

      本文标题:netty LengthFieldBasedFrameDeco

      本文链接:https://www.haomeiwen.com/subject/sdmsactx.html