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/
使用LengthFieldPrepender和LengthFieldBaseFrameDecoder,结合MessagePack编解码框架,实现对TCP粘包,半包的支持。
2.Google protobuf编解码
3.JBoss Marshalling编解码