目录
粘包/拆包 问题产生原因:
发生TCP粘包或拆包有很多原因,现列出常见的几点:
- 要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包。
- 待发送数据大于MSS(TCP报文长度 - TCP头部长度 > MSS最大报文长度),TCP在传输前将根据MSS大小进行拆包分段发送。
- 要发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据包合并为一次发送,将发生粘包(Nagle算法优化,避免tcp报文头重脚轻的情况发生)
- 接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包。
补充:
MTU 最大传输单元 (链路层):
数据链路层传输的帧大小是有限制的,不能把一个太大的包直接塞给链路层,这个限制被称为「最大传输单元(Maximum Transmission Unit, MTU)」
以太网的帧最大的帧是 1518 字节,除去 14 字节头部和 4 字节 CRC,有效荷载最大为 1500,这个值就是以太网的 MTU。
MSS TCP最大段大小 Maximum Segment Size(传输层,可传输tcp数据包大小):
TCP 为了避免被发送方分片,会主动把数据分割成小段再交给网络层,最大的分段大小称之为 MSS(Max Segment Size)。
这样一个 MSS 的数据恰好能装进一个 MTU 而不用分片。
一般,在以太网中 TCP 的 MSS = 1500(MTU) - 20(IP 头大小) - 20(TCP 头大小)= 1460
编辑
总结:IP 数据包长度超过链路的 MTU 时,在发送之前需要分片,而 TCP 层为了 IP 层不用分片主动将包切割成 MSS 大小。
解决 粘包/拆包 问题:
解决问题关键在于如何给每个数据包添加边界信息用于区分不同数据包
- 消息分为tcp首部和tcp消息体,tcp首部中应保存数据包的长度(TCP的首部原本是没有表示数据长度的字段,因为可由IP层计算出:TCP数据包长度 = IP首部的数据包长度 - IP首部长度(20字节 )- TCP包首部长度(20字节 ))。这样接收端在接收到数据后,通过读取包首部的长度字段,就知道每个数据包的实际长度了。
- 发送端在每个包的末尾使用固定的分隔符(如 \r\n),这样接收端通过这个边界就可以将不同的数据包拆分开(如 FTP协议)。
- 发送端将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。不推荐这种方式,尤其是在高并发大流量的业务场景下,会消耗不必要的资源...
补充:TCP报文格式图
编辑
为什么TCP有粘包?
TCP协议粘包拆包问题是因为TCP协议数据传输是基于 "字节流" 的,它不包含消息、数据包等概念,需要应用层协议自己设计消息边界。日常网络应用开发大都在传输层进行,因此粘包拆包问题大都只发生在TCP协议中。
为什么UDP没有粘包?
- UDP有消息保护边界,不会发生粘包拆包问题
- UDP发送的时候,不经过Nagle算法优化,不会将多个小包合并一次发送出去。
- UDP协议的接收端,采用了链式结构来记录每一个到达的UDP包。UDP是基于报文发送的,从UDP的帧结构可以看出,在UDP首部采用了16bit字段来指示UDP数据报文的长度,因此在应用层能很好的将不同的数据报文区分开,从而避免粘包和拆包的问题。
补充:"消息保护边界",就是指传输协议把数据当作一条独立的消息在网络上传输,接收端只能接收独立消息。也就是说存在保护消息边界,接收端一次只能接收发送端发出的一个数据包。而面向字节流是指无消息保护边界的,如果发送端连续发送数据,接收端有可能在一次接收动作中,会接收两个或者更多的数据包。
发生在网络的哪些层上?
粘包拆包问题在数据 “链路层、网络层、传输层” 都可能发生。