TCP粘包/拆包的产生原因和解决办法

TCP底层不了解应用层数据的含义,它会根据TCP缓冲区的实际情况进行包的划分,所以业务上认为,一个完整的包(应用层数据)可能被TCP拆分为多个包进行发送,也可能把多个小包封装成一个大的数据包进行发送,这就是所谓的TCP粘包和拆包问题。出现粘包的原因是多方面的,可能是来自发送方,也可能是来自接收方。
拆包和粘包是在socket编程中经常出现的情况,在socket通讯过程中,如果通讯的一端一次性连续发送多条数据包,tcp协议会将多个完整的消息(应用层数据)打包成一个tcp报文发送出去,这就是所谓的粘包。而如果通讯的一端发送完整消息超过一次tcp报文所能传输的最大值时,就会将一个数据包拆成多个最大tcp长度的tcp报文分开传输,这就叫做拆包。

TCP粘包/拆包的产生原因和解决办法_第1张图片
如图所示,客户端和服务端之间的通道代表TCP的传输通道,两个箭头之间的方块代表一个TCP数据包(去掉TCP首部),正常情况下一个TCP包传输一个应用数据。粘包时,两个或多个应用数据包被粘合在一起通过一个TCP包传输。而拆包情况下,会一个应用数据包会被拆成两段分开传输,其他的一段可能会和其他应用数据包粘合。
注意:粘包和拆包,这里的包实际指的是应用层的完整的数据或消息,而不是TCP数据包。
1. 粘包原因
发送的数据包(消息)小于一次tcp报文所能传输的最大值,由于发送速度过快,此时TCP在封装过程中会将多个完整的消息封装成一个TCP数据包;
接收方不及时读取套接字缓冲区数据(应用层缓冲区),这将发生粘包。

主要的原因是数据发送过快,数据堆积导致缓冲区积压多个数据后打包才一次性发送出去(如果客户端每发送一条数据就睡眠一段时间就不会发生粘包)。出现粘包会导致接受端没有办法解析粘包(粘在一块的消息),因为在TCP的首部没有表示数据长度的字段。
2. 拆包原因
发送的数据包(消息)超过一次tcp报文所能传输的最大值时,就会将一个数据包拆成多个最大tcp长度的tcp报文分开传输。进行MSS(最大报文长度)大小的TCP分段,当(TCP报文长度-TCP头部长度)>MSS的时候将发生拆包。
MSS:Maximum Segment Size ,TCP提交给IP层最大分段大小,不包含TCP Header和 TCP Option,只包含TCP Payload ,MSS是TCP用来限制application层最大的发送字节数。如果底层物理接口MTU= 1500 byte,则 MSS = 1500- 20(IP Header) -20 (TCP Header) = 1460 byte(一般来说还需要减去12“tcp option” = 1460-12 = 1448),如果application层 有2000 byte发送,需要两个segment才可以完成发送,第一个TCP segment = 1460,第二个TCP segment = 540。

3. 解决方案

  1. 发送端给每个数据包添加TCP首部时,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度(简单理解就是,应用层下来的报文前面加上一个报文长度字段);
  2. 发送端将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来;
  3. 可以在数据包之间设置边界,如添加特殊符号,这样,接收端通过这个边界就可以将不同的数据包拆分开。

为什么UDP没有粘包?
粘包拆包问题在数据链路层、网络层以及传输层都有可能发生。日常的网络应用开发大都在传输层进行,由于UDP有消息保护边界,不会发生粘包拆包问题,因此粘包拆包问题只发生在TCP协议中。

具体原因是由于UDP发送的时候,没有经过Negal算法优化,不会将多个小包合并一次发送出去。另外,在UDP协议的接收端,采用了链式结构来记录每一个到达的UDP包;
UDP是基于报文发送的,从UDP的帧结构可以看出,在UDP首部采用了16bit来指示UDP数据报文的长度,因此在应用层能很好的将不同的数据报文区分开,从而避免粘包和拆包的问题。

[1] https://zhuanlan.zhihu.com/p/77275039

你可能感兴趣的:(计算机网络,计算机网络)