Netty:TCP粘包/拆包问题解决之道

1.TCP粘包/拆包

TCP是一个“流”协议,流,就是没有界限的一串数据。TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题。

2.粘包问题的解决策略

由于底层TCP无法理解上层的业务数据,所以底层无法保证数据包不被拆分和重组,这个问题只能通过上层的应用协议栈设计来解决,根据业界主流的解决方案,归纳如下:

1.消息定长,例如每个报文大小固定为长度200个字节,如果不够,空位补空格;

2.用特殊分隔符分割,例如:在包尾增加回车换行符进行分割,如:FTP协议;

3.将消息分为消息头和消息体,消息头包含消息总长度(或消息体长度)的字段,通常设计思路为消息头的第一个字段使用int32来表示消息的总长度;

3.Netty提供的半包解码器解决TCP粘包/拆包问题

3.1 利用LineBaseFrameDecoder解决TCP粘包问题

LineBaseFrameDecoder工作原理是它依次遍历ByteBuf中的可读字节,判断看是否有"\n"或者"\r\n",如果有,就以此位置为结束位置,从可读索引到结束位置区间的字节就组成了一行。它是以换行符为结束标识的解码器,支持携带结束符,或者不携带结束符两种解码方式,同时支持配置单行的最大长度。如果连续读取到最大长度后仍然没有发现换行符,就抛出异常,同时忽略掉之前读到的异常码流。

3.2 StringDecoder

StringDecoder的功能非常简单,就是将接收到的对象转换成字符串,然后继续调用后面的handler.

LineBaseFrameDecoder+StringDecoder组合是按行切换的文本解码器,它被设计用来支持TCP的粘包和拆包。

如果发送的消息不是以换行符结束呢,或者没有回车换行符,靠消息头的长度字段来分包如何操作?

 

3.2 分割符和定长解码器的应用

1.消息长度固定,累计读到长度总和为定长LEN的报文后,就认为读到了一个完整的消息;将计数器置位,重新重新开始读取下一个数据报;

2.特殊分割符作为消息结束标识,回车换行符就是一种特殊的结束分隔符。

DelimiterBasedFrameDecoder:可以自动完成以分隔符做结束标识的消息的解码

FixedLengthFrameDecoder:可以自动完成对定长消息的解码

FixedLengthFrameDecoder解码器,无论一次接收到多少数据报,它都会按照构造函数中设置的固定长度进行解码,如果是半包消息,FixedLengthFrameDecoder会缓存半包消息并等待下个包到达后拼包,直到读取一个完整的包。

它们都能解决TCP粘包/拆包导致的读半包问题。

 

4.编码解码技术

1.MessagePack编解码

MessagePack提供了多语言的支持。官网:http://msgpack.org/

Netty:TCP粘包/拆包问题解决之道

使用LengthFieldPrepender和LengthFieldBaseFrameDecoder,结合MessagePack编解码框架,实现对TCP粘包,半包的支持。

2.Google protobuf编解码

3.JBoss Marshalling编解码