Netty中的解码器介绍
在TCP传输中,由于拆包粘包的存在,会导致半包读写的问题。
导致拆包粘包问题原因:
1.应用程序写入的字节数大于套接字缓冲区的大小
2.进行MMS(最大报文段长度)大小的TCP分段
3.以太网帧的payload大于MTU进行IP分片
以上问题可以参考文章:https://blog.youkuaiyun.com/q1007729991/article/details/69668576
和 https://blog.51cto.com/michaeljinxq/1215174
半包读写的常用解决方案如下:
1.消息定长 ,例如固定数据报文的长度为200字节,不够长度则空格补齐
2.在包尾增加回车换行符进行分割, 例如FTP协议
3.将消息分为消息头和消息体,消息头中包含消息总长度(或者消息体长度)字段,通常的设计思路是为消息头的第一个字段使用int32来表示消息的总长度
以下介绍Netty中自带解码器解决半包读写问题
LengthFieldBasedFrameDecoder解码器
这个解码器包含以下三个参数
maxFrameLength 发送的数据帧最大长度
lengthFieldOffset 长度域字段起始偏移量
lengthFieldLength 长度域字段占用长度
lengthAdjustment 读取消息题的长度调整长度
initialBytesToStrip 消息题读取的起始偏移量
场景一
消息编码后通过设置参数能够自动将消息解码
lengthFieldOffset 和 lengthFieldLength 能够确定读取到长度域字段的值,从而确定消息体body的大小,然后加上长度域字段的 4长度,就能够完整确定一条消息的长度
场景二
在场景一的基础上确定了整个消息的长度,设置initialBytesToStrip参数是确定读取整个消息需要丢弃的长度,这里是丢弃掉了长度域的长度,读取整个消息体的内容。
场景三
与上面两个场景不同的是,这里的长度域字段表示的值是整个消息的长度。所以这个时候通过lengthAdjustment 参数调整要读取的消息体的长度。以下示例中,读取到长度域的值再加上-4就是消息体的长度,就能读取到消息体。从而获得整个消息。又由于没有丢弃任何长度字节,所以获得整个消息。
场景四
在场景三的基础上,增加了initialBytesToStrip参数,丢弃了长度域字节,获取到消息体。
场景五
在场景四的基础上增加了一个请求头字段
场景六
在场景五的基础上调整参数,读取整条消息
其它场景大家可以自己去组合探索,归根结底 lengthFieldOffset参数确定长度域字段的读取起始位置,lengthFieldLength 确定读取长度域的长度,从而读取到长度值。 长度值可能是消息题的长度值,也可能是整个消息的长度值。再由长度值加上 lengthAdjustment 参数值确定要读取的消息长度。最后initialBytesToStrip 参数确定从哪个位置开始读取消息的长度。
LineBasedFrameDecoder 换行符解码器
这个解码器通过识别换行符解决拆包粘包导致的半包读问题
使用方式:
SocketChannel.pipeline().addLast(new LineBasedFrameDecoder(4096));
DelimiterBasedFrameDecoder使用自定义标识符解码器
这个解码器通过使用用户自定义的标识符解决半包读问题
使用方式:
//使用 $ 作为标识符
ByteBuf byteBuf = Unpooled.copiedBuffer("$".getBytes());
SocketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(4096,byteBuf));
FixedLengthFrameDecoder固定长度解码器
每次读取固定长度的自己解决半包读问题
SocketChannel.pipeline().addLast(new FixedLengthFrameDecoder(100));