前面已经说过:
TCP以流的方式进行数据传输,上层应用协议为了对消息进行区分,往往采用如下4种方式。
(1)消息长度固定:累计读取到固定长度为LENGTH之后就认为读取到了一个完整的消息。然后将计数器复位,重新开始读下一个数据报文。(2)回车换行符作为消息结束符:在文本协议中应用比较广泛。
(3)将特殊的分隔符作为消息的结束标志,回车换行符就是一种特殊的结束分隔符。
(4)通过在消息头中定义长度字段来标示消息的总长度。
netty中针对这四种场景均有对应的解码器作为解决方案,比如:
(1)通过FixedLengthFrameDecoder 定长解码器来解决定长消息的黏包问题;
(2)通过LineBasedFrameDecoder和StringDecoder来解决以回车换行符作为消息结束符的TCP黏包的问题;
(3)通过DelimiterBasedFrameDecoder 特殊分隔符解码器来解决以特殊符号作为消息结束符的TCP黏包问题;
(4)最后一种,也是本文的重点,通过LengthFieldBasedFrameDecoder 自定义长度解码器解决TCP黏包问题。
大多数的协议在协议头中都会携带长度字段,用于标识消息体或则整包消息的长度。LengthFieldBasedFrameDecoder通过指定长度来标识整包消息,这样就可以自动的处理黏包和半包消息,只要传入正确的参数,就可以轻松解决“读半包”的问题。
1. LengthFieldBasedFrameDecoder功能说明
下面我们通过实例来看如何通过配置不同的参数组合来实现不同的半包读取策略。
(1)场景一:消息的第一个字段是长度字段,后面是消息体,消息头中只包含一个长度字段,消息结构定义如下:
在解码前字节缓冲区占了14个字节,其中前两个字节是标识长度的字节,后面12个字节是消息体。
使用的组合参数如下:
1) lengthFieldOffset = 0;//长度字段的偏差
2) lengthFieldLength = 2;//长度字段占的字节数
3) lengthAdjustment = 0;//添加到长度字段的补偿值
4) initialBytesToStrip = 0。//从解码帧中第一次去除的字节数
解码后的字节缓冲区的内容是:
解码后还是14个字节。
(2)场景二:通过ByteBuf.readableBytes()方法我们可以获取当前消息的长度,所以解码后的字节缓冲区可以不携带长度字段,由于长度字段在起始位置并且长度为2,所以将initialBytesToStrip设置为2。参数组合修改为:
1) lengthFieldOffset = 0;
2) lengthFieldLength = 2;
3) lengthAdjustment = 0;
4) initialBytesToStrip = 2。
这时候解码后字节缓冲区的数据就是:
很明显跳过了长度字段后的字节缓冲区就只有12个字节了。
解码后的字节缓冲区丢弃了长度字段,仅仅包含消息体,对于大多数的协议,解码之后消息长度没有用处,因此可以丢弃。
(3)场景三:在大多数的应用场景中,长度字段仅用来标识消息体的长度,这类协议通常由消息长度字段+消息体组成,如上图所示的几个例子。但是,