一、粘包
1、定义
指发送方发送的若干数据包在接收方接收时粘成一团,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。
2、产生的原因
(1)发送方的原因
TCP默认使用 Nagle 算法,而 Nagle 算法主要做如下两件事情:
- 只有上一个分组得到确认,才发送下一个分组。
- 收集多个小分组,在一个确认到来时一起发送。
Nagle 算法造成了发送方可能存在粘包现象。
(2)接收端的原因
接收端接收到分组时,应用层并不会立即处理,接收端将接收到的分组放到接收缓存中,然后应用程序主动从接收缓存中读取分组,当接收端接收分组的速度大于应用程序读取分组的速度时,多个包就会被存至缓存。应用程序读取时,就会读到多个首尾相连在一起的包。
3、什么时候需要处理 TCP 粘包问题。
- 如果发送方的多个分组本来就是同一个数据的不同部分,比如一个很大的文件分成多个分组发送,这时不需要处理TCP粘包问题。
- 如果多个分组毫不相干,甚至是并联关系,则一定要处理TCP粘包问题。
二、拆包
1、定义
发送方将一个数据包拆分成了多个数据包进行传送。
2、产生的原因
- 要发送的数据大于 TCP 剩余缓冲区的大小。
- 要发送的数据大于 MSS 最大报文长度。
三、如何处理TCP粘包和TCP拆包问题
无论是TCP拆包还是TCP粘包本质问题都在于无法区分包的界限,可以采用以下三种方式来区分包的界限
- 消息数据固定长度,但是浪费存储和网络资源。
- 使用分割符来区分包的界限。
- 数据包的头部中增加数据包长度字段。
四、代码示栗
#pragma pack(1)
struct XMNPkgHeader
{
// 报文的总长度(包头 + 包体)。
unsigned short pkglen;
// 消息类型的代码,用于区别不同的命令(消息)。
unsigned short msgcode;
// CRC32 校验,用于防止接收到的数据和 client 发送的数据不符的问题。
int crc32;
};
#pragma pack()
之所以使用 pack(1) ,目的是让包头大小更紧凑,节约流量。 由于包头的大小(headerlen)是固定的,通过 pkglen 就可以知道包体的大小(bodylen = pkglen - headerlen)。
在已知包头和包体的长度的情况下,接受数据包的时候,首先可以接收 headerlen 大小的数据,如果接收的数据不够,则下次接收包头剩余的数据,直至包头数据接收完毕。
包头数据接收完毕之后,再接收 bodylen 大小的数据,如果接收的数据不足,则接收剩下的包体的数据,直至包体的数据接收完毕。
五、总结
TCP 之所以存在拆包和粘包问题,本质就是 TCP 是面向字节流的,而 UDP 是面向报文的!
六、源码链接
https://github.com/xuchanglong/XMoon
(SAW:Game Over!)