网站建设应用技术网络推广营销软件
Netty笔记1:线程模型
Netty笔记2:零拷贝
Netty笔记3:NIO编程
Netty笔记4:Epoll
Netty笔记5:Netty开发实例
Netty笔记6:Netty组件
Netty笔记7:ChannelPromise通知处理
Netty笔记8:ByteBuf使用介绍
Netty笔记9:粘包半包
Netty笔记10:LengthFieldBasedFrameDecoder
Netty笔记11:编解码器
Netty笔记12:模拟Web服务器
Netty笔记13:序列化
文章目录
- 前言
- 什么是`LengthFieldBasedFrameDecoder`
- 理论
- 实践验证
- 粘包
- 半包
- 补充
前言
本部只是LengthFieldBasedFrameDecoder
的理论总结,和理论验证。
什么是LengthFieldBasedFrameDecoder
LengthFieldBasedFrameDecoder
处理基于长度字段的协议。它能够根据数据包中的长度字段来解析数据流,并将数据流分割成独立的帧;
因其能对数据包边界的识别,而应用于粘包和半包的处理;
理论
应用层协议都是基于TCP/IP
进行开发的,传输数据时就会有协议特征,就是数据头,它不作为我们真正的有效数据;
那么在解码时,需要考虑,数据包长度,数据头长度,和数据体长度;
public LengthFieldBasedFrameDecoder(// 允许的最大数据长度(以字节为单位),就是你的一个数据最大多大,超过报异常int maxFrameLength,// 长度字段的开始索引下标int lengthFieldOffset, // 长度字段占用的字节数int lengthFieldLength,// 长度字段之后,开始读取的索引下标偏移量int lengthAdjustment, // 接收到的发送数据包,丢弃多少位int initialBytesToStrip) {
lengthFieldOffset、lengthFieldLength
:这两个字段很好理解,就是解码器,要确定读取多少字节长度的数据,就必须先读取我们指定的长度字段,那么这两个字段就可以确定从哪一个索引下标开始,读取多少字节,以此来获取数据长度(底层读取方式:ByteBuf.getUnsignedInt(lengthFieldOffset)
);
lengthAdjustment
:就是待读取数据的开始索引下标与长度字段结束索引的差值(带读取数据的开始索引 - 长度字段结束索引),或者是以长度字段结束索引为坐标0点,待读取数据的开始索引到0点的距离(左负右正);
那么,怎么判断待读取的开始位置?
从数据结构右边的尾部,向左移动长度字段值的位置就是开始索引位置,如长度字段(length)值是33,那么,从右边尾部向左移动33个索引位置就是开始位置,也或是看长度字段包含了哪些部分(长度字段包发的部分应是连续的数据块);
假设协议如下:
| header | length | header | 数据体1 4 8 x长度如上:4,4,8,x(未知)
画出坐标如下:
取值规则如下:
length值:数据体长度
取值:
lengthFieldOffset=4
-> 长度字段开始索引下标
lengthFieldLength=4
-> 长度字段长度
lengthAdjustment=8
-> 长度字段与待读取数据的开始索引差值
length值:包含8字节的header,数据体
lengthFieldOffset=4
-> 长度字段开始索引下标
lengthFieldLength=4
-> 长度字段长度
lengthAdjustment=0
-> 长度字段与待读取数据的开始索引差值
length值:包含本身长度,以及8字节的header,数据体
lengthFieldOffset=4
-> 长度字段开始索引下标
lengthFieldLength=4
-> 长度字段长度
lengthAdjustment=-4
-> 长度字段与待读取数据的开始索引差值
length值:包含1字节的header,本身长度,以及8字节的header
lengthFieldOffset=4
-> 长度字段开始索引下标
lengthFieldLength=4
-> 长度字段长度
lengthAdjustment=-5
-> 长度字段与待读取数据的开始索引差值
感觉画图不够清晰,下面是文字版的:
| header | length | header | 数据体1 4 8 x长度如上:4,4,8,x(未知)length值:数据体长度
| header | length | header | 数据体1 4 8 x^ ^| |0 从这开始读
则:
lengthFieldOffset=4 -> 长度字段开始索引下标
lengthFieldLength=4 -> 长度字段长度
lengthAdjustment=8 -> 长度字段与待读取数据的开始索引差值length值:包含8字节的header,数据体| header | length | header | 数据体1 4 8 x^ | 0 从这开始读lengthFieldOffset=4 -> 长度字段开始索引下标
lengthFieldLength=4 -> 长度字段长度
lengthAdjustment=0 -> 长度字段与待读取数据的开始索引差值length值:包含本身长度,以及8字节的header,数据体| header | length | header | 数据体1 4 8 x^ ^ | | 从这开始读 0 lengthFieldOffset=4 -> 长度字段开始索引下标
lengthFieldLength=4 -> 长度字段长度
lengthAdjustment=-4 -> 长度字段与待读取数据的开始索引差值length值:包含1字节的header,本身长度,以及8字节的header| header | length | header | 数据体1 4 8 x
^ ^
| |
从这开始读 0 lengthFieldOffset=4 -> 长度字段开始索引下标
lengthFieldLength=4 -> 长度字段长度
lengthAdjustment=-5 -> 长度字段与待读取数据的开始索引差值
实践验证
LengthFieldBasedFrameDecoder
要做数据流解析验证用的,所以放在第一个,待它根据我们指定的规则解析数据流后,将独立的帧(数据头、长度字段、数据体等)作为一个完整的数据包传给下一个handler
;
粘包
步骤:
-
创建一个
MessageToByteEncoder
,消息写入时,将消息组装各部分帧:长度字段,数据头,和数据体; -
客户端发送多次数据,或者一次将多个消息数据合并为一个
ByteBuf
发送(模拟粘包); -
服务端第一个添加
LengthFieldBasedFrameDecoder
,第二个添加我们自定义的handler
用来获取数据和判断读取次数; -
出站
handler
,连续写入两个数据包,但只发送一次public class MBHandler extends MessageToByteEncoder<UserInfo> {private static final long serializable = 123456789;private static final int type = 1;@Overrideprotected void encode(ChannelHandlerContext ctx,