TCP 粘包:
什么是粘包现象 :
TCP粘包是指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。
为什么出现粘包现象 :
(1) 发送方原因 我们知道,TCP默认会使用Nagle算法。而Nagle算法主要做两件事:
1)只有上一个分组得到确认,才会发送下一个分组;
2)收集多个小分组,在一个确认到来时一起发送。所以,正是Nagle算法造成了发送方有可能造成粘包现象。 由于TCP协议本身的机制(面向连接的可靠地协议-三次握手机制)客户端与服务器会维持一个连接(Channel),数据在连接不断开的情况下,可以持续不断地将多个数据包发往服务器,但是如果发送的网络数据包太小,那么他本身会启用Nagle算法(可配置是否启用)对较小的数据包进行合并(基于此,TCP的网络延迟要UDP的高些)然后再发送(超时或者包大小足够)。那么这样的话,服务器在接收到消息(数据流)的时候就无法区分哪些数据包是客户端自己分开发送的,这样产生了粘包;服务器在接收到数据库后,放到缓冲区中,如果消息没有被及时从缓存区取走,下次在取数据的时候可能就会出现一次取出多个数据包的情况,造成粘包现象(确切来讲,对于基于TCP协议的应用,不应用包来描述,而应用流来描述),个人认为服务器接收端产生的粘包应该与linux内核处理socket的方式select轮询机制的线性扫描频度无关。
(2) 接收方原因 TCP接收到分组时,并不会立刻送至应用层处理,或者说,应用层并不一定会立即处理;实际上,TCP将收到的分组保存至接收缓存里,然后应用程序主动从缓存里读收到的分组。这样一来,如果TCP接收分组的速度大于应用程序读分组的速度,多个包就会被存至缓存,应用程序读时,就会读到多个首尾相接粘到一起的包。
什么时候需要处理粘包现象 :
(1) 如果发送方发送的多个分组本来就是同一个数据的不同部分,比如一个很大的文件被分成多个分组发送,这时,当然不需要处理粘包的现象;
(2) 但如果多个分组本毫不相干,甚至是并列的关系,我们就一定要处理粘包问题了。比如,我当时要接收的每个分组都是一个有固定格式的商品信息,如果不处理粘包问题,每个读进来的分组我只会处理最前边的那个商品,后边的就会被丢弃。这显然不是我要的结果。
如何处理粘包现象 :
(1) 发送方
对于发送方造成的粘包现象,我们可以通过关闭Nagle算法来解决,使用TCP_NODELAY选项来关闭Nagle算法。
(2) 接收方
遗憾的是TCP并没有处理接收方粘包现象的机制,我们只能在应用层进行处理。
(3) 应用层处理
应用层的处理简单易行!并且不仅可以解决接收方造成的粘包问题,还能解决发送方造成的粘包问题。
解决方法就是循环处理:应用程序在处理从缓存读来的分组时,读完一条数据时,就应该循环读下一条数据,直到所有的数据都被处理;但是如何判断每条数据的长度呢? 解决方法:
1、关闭TCP套接字的NAGL的算法(效率低下)
2、每发送一次,等待对方收到才发送下一条。(效率低下)
3、回答机制:
4、在数据之前添加特殊信号或长度:
[ 5 ] abcefg
[13] 23
解决TCP粘包的服务器的代码实现 :
#include
#include
#include
#include
#include
//服务器 int main() { //1创建套接字 int sock=socket(AF_INET,SOCK_STREAM,0); if(sock<0) { perror("socket fail"); return -1; } socklen_t slen=sizeof(socklen_t); if(setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&slen,sizeof(slen))<0) { perror("setsockopt fail"); return -1; } //2绑定 /*先填充 再绑定bind*/ struct sockaddr_in myaddr; bzero(&myaddr,sizeof(myaddr)); myaddr.sin_family =AF_INET; myaddr.sin_port =htons(7979); myaddr.sin_addr.s_addr =INADDR_ANY; if(bind(sock,(struct sockaddr*)&myaddr,sizeof(myaddr))<0) { perror("bind fail"); return -1; } //3监听 if(listen(sock,1)<0) { perror("listen fail"); return -1; } int newsock=accept(sock,NULL,NULL); //sleep(5);//在10秒的睡眠时,客户分三次发送1 2 3字符串。 char buf[100]=""; int ilen=0; short num=0; int total=0; char* p=NULL; while(1) { total=0; p=(char*)# bzero(buf,sizeof(buf)); //读取长度 [5] abcefg 相当于读取的括号中的 5 while(total<2)//1 2 { ilen=recv(newsock,p+total,1,0); total+=ilen; } //读取字符内容 [5] abcefg 相当于读取 abcefg 的数据 total=0; while(total
服务器读取内容的效果 , 完整的将客户端发送过来的数据进行了读取 :

客户端发送数据的代码 :
#include
#include
#include
#include
#include
//服务器 int main() { //1创建 int sock=socket(AF_INET,SOCK_STREAM,0); if(sock<0) { perror("fail\n"); return -1; } //2绑定 //3发送连接 struct sockaddr_in myaddr; bzero(&myaddr,sizeof(myaddr)); myaddr.sin_family =AF_INET; myaddr.sin_port =htons(7979); myaddr.sin_addr.s_addr =inet_addr("127.0.0.1"); if(connect(sock,(struct sockaddr*)&myaddr,sizeof(myaddr))<0) { perror("连接失败"); return -1; } //4发送信息 short ilen=5; //每次发送数据前先发送数据长度 如: [5] abcefg send(sock,"12345",5,0); ilen=17; send(sock,&ilen,2,0); send(sock,"abcdefghijklmnopq",17,0); ilen=12; send(sock,&ilen,2,0); send(sock,"How are you!",12,0); sleep(5); //3关闭 close(sock); }
我们再来看看现象 :

结果将数据完好的读取了出来 , 是因为一个字节发送 , 一个字节读取的所以没有出现粘包现象 :

我们再来看看粘包的现象 , 这次发送过来的是结构体数据 , 而服务器需要读取结构体的数据 .

结果 出现粘包现象 :

怎么解决上面出现的粘包现象 , 只需要将客户端发送的数据 sleep(2) , 再把服务器的 sleep(5)注释掉 , 解决了 , 每次等发送一个结构体就等待 , 直到发送完毕再发送第二个结构体 , 就没有出现粘包现象 .

结果 数据完好的读取了出来 :


发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/220898.html原文链接:https://javaforall.net
