TCP为什么会有粘包问题
TCP和UDP是存在于传输层的两个网络传输协议,由于UDP的消息传输发送是基于数据包的是有边界的数据,而TCP是基于字节流的数据传输方式,而流的方式就注定了它的无边界性,比如在管道中流动的水就是无边界的,如果把水装入某个容器中在用管道传输,那它就是有边界的,容器的大小就可以看成它的边界。因此UDP可以保证每次收到的都是一个完整的数据包,而TCP缺无法保证,所以才有粘包问题。
产生原因
TCP的粘包在发送方与接收方都会出现,由于TCP有一个缓冲区,发送端往其中写数据,为了增加发送效率减小网络负担,TCP会等缓冲区满或者某个时间发送此缓冲区的数据,此时粘包在发送方就已经出现;同时接收方没能及时处理数据,那么后续到达的数据也会存放在缓冲区中,等待下一次读取的时候缓冲区就形成了粘包;或者是一次写入的数据小于缓冲区容量,在第二次写入诗缓冲区满了,数据直接发送,剩下的部分会在下一次发送时补发,因此会造成不完整的粘包等等。
解决方法
在这里所讨论的是在应用层也就是socket传输在接收端的解决方式。
1. 发送定长的数据包。就是说每个消息的大小都是一样的,接收方等待累计已接收数据的大小才会将其作为一个完整的消息处理;还有就是可以无用信息填充的方式,如果某次写入的数据不够一个定包长,可以在数据后填充无用信息,这样接收方不用累计数据,但是增加了接收方处理数据的难度。
2. 用特定的字符串比如“\r\n”来做每条数据的分割。但是像“\r\n”这种串在正文中也会遇到,这样会误判为消息边界,因此在字符串的选取上也应做适当的选择。
在这种方式中就可以用到int recv(int sockfd,void *buff, size_t len,int flags);函数中flag选项;在此选项中有个属性是PEEK属性,这个属性书不清除缓冲区,而是可先看缓冲区的内容,这样就可以通过PEEK属性想在缓冲区查找定义的边界来获取数据,如果缓冲区里不含定义边界,则将缓冲区内容读出,在此节后数据,如此知道找到边界然后在处理数据;
3. 在每个 报头上说明包体的长度,这样通过报头大小来获取相应的数据大小;
在此主要讨论解决方法3,在报头说明包体大小的方式
在此方式中,每个包都分为两部分,第一部分为数据部分的大小,紧接的部分是数据部分,可以用结构体实现
struct packet{
unsigned int m_msgLen;
char data[maxsize];
}
这样数据在发送的过程中将报头和包体全部发送,在接收的时候先接收4字节的包头数据
int a;
recv(sockfd,&a,4,0);
或者recv(sockfd,buff,maxsize,0);//线将缓冲区数据全部拿出来再处理
strncpy(&a,buff,4);
这样就可以获取包体数据大小,然后通过报头大小获取包体数据来解决粘包
在发送端的处理方式可以用编程设置来实现,TCP提供了强制数据立即传送的操作指令push,TCP软件收到该操作指令后,就立即将本段数据发送出去,而不必等待发送缓冲区满;但是这种编程设置方法虽然可以避免发送方引起的粘包,但它关闭了优化算法,降低了网络发送效率,影响应用程序的性能。