1. 拆包和粘包的概念
上图为 TCP 协议传输的大致过程,其数据传输的性质是流式的,并没有分段的概念,所以这个过程可能有 3 种情况:
正常情况
发送端发了两条消息,接收端也读到了两个数据包,第一个包包含发送端发出的第一条消息的完整信息,第二个包包含完整的第二条消息。这种情况接收端只需要简单的从缓冲区去读数据就好了,能够正确处理TCP粘包
发送端发出了两条消息,接收端只读到一个数据包。发送端发出的两条消息的完整信息都被包含在一个数据包中,接收端不知道第一条消息从哪儿结束而第二条消息是从哪儿开始的,很难正确处理数据TCP拆包
发送端发送两条消息,接收端一共收到了两个数据包,其中一个数据包只包含了一条消息的一部分,这条消息的后半部分和另一条消息都在另一个数据包中,简而言之就是一条消息被拆分在两个包里面发送了
2. 拆包和粘包的原因
TCP协议
以流的方式传输数据,传输的最小单位为一个报文段(segment)。TCP报文的Header
中有个Options标识位
,其常见的标识MSS(Maximum Segment Size)
指的是报文传输的内容的最大长度,这个值通常为 MTU 减去 IP头(20 Byte)和TCP头(20 Byte)
的大小,一般为1460 Byte
MTU(Maximum Transmission Unit)
是数据链路层每次传输的数据的最大限制,通常是1500 Byte,超过这个大小的数据要分成多个报文段
为提高性能,TCP 发送端会将需要发送的数据发送到缓冲区,等待缓冲区满了或者触发 flush 操作再将缓冲中的数据发送到接收方。同样,接收方也有缓冲区用于接收数据,因此发生TCP粘包、拆包主要有以下原因:
- 发送方发送的数据超过 MSS(最大报文长度)大小,即当
TCP 总数据量 - TCP头部长度 > MSS
的时候将发生拆包- 发送方写入的数据大于 socket 缓冲区大小,将会发生拆包
- 发送方写入数据小于 socket 缓冲区大小,网卡将其多次写入的数据一起发送,将会发生粘包
- 接收方不及时读取 socket 缓冲区数据,可能发生粘包
3. 解决方案
底层的 TCP 协议本身不关心上层的业务数据,所以无法避免粘包拆包的发生,只能在应用层数据协议上加以控制,常用的方法如下:
- 使用带消息头的协议
消息头存储消息开始标识及消息长度信息,接收端获取消息头的时候解析出消息长度,然后向后读取该长度的内容,位数不够补 0- 设置定长消息
接收端每次读取既定长度的内容作为一条完整消息,不足的空位补齐- 设置消息边界
可在报文末尾增加换行符表明一条完整的消息,这样接收端可以根据这个换行符来判断消息是否完整
Netty 框架中对 TCP 粘包拆包有现成的解决工具,只要在其 pipline 中加入对应的解码器就可实现。需注意一旦添加了对应的解码器,发送数据时一定要符合其规范,否则消息可能无法正常读取
- LineBasedFrameDecoder 基于换行符解决
- DelimiterBasedFrameDecoder 基于分隔符解决
- FixedLengthFrameDecoder 指定长度解决