TCP 粘包解决

版权声明:请在征得作者同意的情况下,可以进行非盈利性引用。引用请注明出处:“作者:慕华思弦 转载地址” 字样,以尊重作者的劳动成果,并保持良好的版权意识。 https://blog.csdn.net/Superman___007/article/details/82934060

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] 1224323432423

   解决TCP粘包的服务器的代码实现 : 

#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
//服务器
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*)&num;
		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<num)
		{
			ilen=recv(newsock,buf+total,1,0);
			total+=ilen;
		}
		printf("收到%d字节 内容:%s\n",num,buf);
	}
	close(sock);
	return 0;
}

   服务器读取内容的效果 , 完整的将客户端发送过来的数据进行了读取 :

   客户端发送数据的代码 : 

#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
//服务器
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)注释掉 , 解决了 , 每次等发送一个结构体就等待 , 直到发送完毕再发送第二个结构体 , 就没有出现粘包现象 .

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

猜你喜欢

转载自blog.csdn.net/Superman___007/article/details/82934060
今日推荐