问题表现
TCP是一个流协议,其字节流没有明确的分界线。TCP底层并不了解上层数据的具体含义,它会根据TCP缓冲区的实际情况进行包的划分,一个完整的包可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题。
- 正常的理想情况,两个包恰好满足TCP缓冲区的大小或达到TCP等待时长,分别发送两个包;
- 粘包:两个包较小,间隔时间短,发生粘包,合并成一个包发送;
- 拆包:一个包过大,超过缓存区大小,拆分成两个或多个包发送;
- 拆包和粘包:Packet1过大,进行了拆包处理,而拆出去的一部分又与Packet2进行粘包处理。
TCP协议是面向流的协议,UDP是面向消息的协议
UDP没有粘包拆包问题,因为UDP有消息保护边界。在每个UDP包都有消息头,对于接收端应用程序相当好区分。UDP每一段都是一条消息,应用程序必须以消息为单位提取数据。
UDP消息头包括UDP长度、源端口、目的端口、校验和。
TCP粘包/拆包发生的原因
- 应用程序的一次请求发送的数据量(滑动窗口/Nagle算法)
- 比较小,TCP会把多个请求合并为一个请求发送,导致粘包
- 比较大,超过缓冲区大小/MTU/MSS,TCP会将其拆分为多次发送,导致拆包
- MTU/MSS限制
- Maximum Segment Size,最大报文长度;Maximum Transmission Unit,最大传输单元
- 传输的数据大于MSS/MTU时,数据会被拆成多个包进行传输。由于MTU由MSS计算出,若满足MTU,则满足MSS(MSS长度=MTU长度-IP Header-TCP Header)
解决策略
- 首先TCP作为面向流的协议,没有消息保护边界,其字节流本身就没有明确的分界线,需要在接收端处理消息边界问题。
- 粘包/拆包和TCP没啥关系,是应用层没处理好数据包的分割,比如应用层的两个数据包粘一块了,因此粘包拆包应该由业务层处理。
解决思路:编码解码,应用层通过指定收发两端共同的约定,发送方按照特定的规则组装数据,接收方按照同样的规则拆解数据。发送方组装数据的过程称之为编码;接收方拆解数据的过程称之为解码。
- 长度编码:将消息分为消息头和消息体,消息头中包含消息的长度
- 特殊字符分隔消息
- 定长协议:发送端将数据包封装为固定长度(不够的补0),接收端每次从接收缓冲区中读取固定长度的数据就把每个数据包拆分开来。