使用LengthFieldBasedFrameDecoder解决复杂的自定义协议-粘包与半包问题
程序员文章站
2024-02-19 23:45:10
...
之前做过一个项目,项目中web应用为了与传感器通讯,定义了一整套通讯协议,这里拿最简单的心跳协议来讲,使用netty自带的LengthFieldBasedFrameDecoder解码器来解决粘包与半包问题。
心跳协议如下:
简单说下这个协议,固定值的包头包尾设定,更多的是为了迎合硬件,如果你去看过一下rpc框架的通信协议,比如dubbo,为了使包字节数更少,不会这样。所以站在高效通讯的角度上讲,这个协议设计并不合理。
回到主题,上面这套协议算是LengthFieldBasedFrameDecoder最复杂的一种应用吧。
先看看LengthFieldBasedFrameDecoder的一个构造函数源码,如下:
public LengthFieldBasedFrameDecoder(
int maxFrameLength,
int lengthFieldOffset, int lengthFieldLength,
int lengthAdjustment, int initialBytesToStrip) {
this(
maxFrameLength,
lengthFieldOffset, lengthFieldLength, lengthAdjustment,
initialBytesToStrip, true);
}
入参有五个参数,分别解释如下:
- maxFrameLength:单个包最大的长度,这个值根据实际场景而定,我设置的是1024,固然我的心跳包不大,但是其他包可能比较大。
- lengthFieldOffset:表示数据长度字段开始的偏移量,比如上面的协议,lengthFieldOffset应该取值为5,因为数据长度之前有2个字节的包头,1个字节的功能ID,2个字节的设备ID,一共为5。
- lengthFieldLength:数据长度字段的所占的直接数,上面的协议中写的是2个字节,所以取值为2
- lengthAdjustment:这里取值为10=7(系统时间) + 1(校验码)+ 2 (包尾),如果这个值取值为0,试想一下,解码器跟数据长度字段的取值(这里数据长度内容肯定是1),只向后取一个字节,肯定不对。
(lengthAdjustment + 数据长度取值 = 数据长度字段之后剩下包的字节数) - initialBytesToStrip:表示从整个包第一个字节开始,向后忽略的字节数,我设置为0,本来可以忽略掉包头的两个字节(即设置为2),但是,实际项目中,需要校验包头取值是否为AA55,来判断包的合法性。
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024,5,2,10,0));
// ch.pipeline().addLast(new AuthCheckHandler());
}
所以LengthFieldBasedFrameDecoder设置如上面代码所示。