天天看点

tcp遇到黏包怎么解决?

**@tcp遇到黏包怎么解决?

这段时间复习下网络协议相关的知识, 在网上偶然看到tcp黏包的问题,就好奇地自己就实验了下,总结了下面几点经验,写的不对的还请指正:

一: 什么情况下tcp会发生黏包现象?

  1. 发送数据方多次send之间的间隔太短。tcp为了提高系统效率,使用了Nagle算法,假如多次短时间内使用send函数发送数据, Nagle会先将这几次send的数据存在内核的缓冲区,等缓冲区满了后或者超时(200ms)就会发送出去,这样就减少了发送的次数,提升了效率。
  2. 网路环境不好时,数据没有发送出去,就会全部堆积在缓冲区,等网络好后,就会一股脑的全部发送出去,这也会导致黏包。
  3. 接受数据方处理不当。tcp中接受方会将接受到的数据先放到缓冲区,然后应用程序再读取缓冲区的数据进行操作。假如缓冲区已经有个两个大小各为32个字节的数据,而此时直接读取64字节的数据的话,必然会造成黏包的。

二 : 怎么避免黏包问题?

  1. 关闭Nagle算法, 使用TCP_NODELAY选项:
int enable = 1;
setsockopt(sockfd,IPROTO_TCP,TCP_NODELAY(void*)&enable,sizeof(enable));
           
但我不建议这种做法,如果要进行文件传输,Nagle算法能大大提高效率。
           
  1. 尽量避免两次send时间过短,使用sleep或者usleep进行延时。但这也会影响程序运行效率,所以也不建议。

上面两种方法都是在数据发送时避免黏包,但假如网路不好或者接受处理不当还是会黏包,所以我推荐使用下面的方法:

  1. 格式化数据,每次发送的都是固定长度,每次接收的也是固定长度。

    发送方:

char buf1[32] = "aaa";
char buf2[32] = "bbb";

send(sockfd, buf1, sizeof(buf1), 0); //每次固定发送32个字节
send(sockfd, buf2, sizeof(buf1), 0);
           

数组buf1里保存的数据长度只有3个字节,正常来说应该发送strlen(buf1) ,既3个字节,但因为此时连续发送了send了两次, 所以改为发送数组buf1长度,既32个字节。

接受方:

char buf[32] = {0};
ret = recv(fd, buf, sizeof(buf), 0);  //每次接收固定长度32个字节
           

也可以将数据放再结构体里面。收发双发都约定好用相同大小的结构体或数组收发数据,这样就会避免黏包。

  1. 如果发送的数据不能固定长度,那每次发送数据时可以在数据前面加上这条数据的长度。例如规定数据的前四位为数据的长度length,接收方每次先接收四个字节,这四个字节中保存的就是应该接收的数据长度length,然后再次接收时就接收length个字节,这样也可以避免黏包。

    发送方:

char buf[1024] = {0};
 73         char send_msg[] = "aaa"; //需要发送的数据
 74         int length = strlen(send_msg);
 75 
 76         sprintf(buf, "%4d%s", length, send_msg);//length转化为字符串,并让length占4个字节
 79         send(sockfd, buf, strlen(buf), 0);
           

接收方:

char buf[1024] = {0};
 66     while(1)
 67     {
 68         //从TCP连接读取数据
 70      	ret = recv(fd, buf, 4, 0);  //接收4个字节的长度信息
 71         if(-1 == ret)
 72         {
 73             perror("recv1");
 74         }
 75         else if(0 == ret)
 76         {
 77             printf("客户端退出\n");
 78             break;
 79         }
 
 81         printf("buf_length = %s\n", buf);
 82         int length = atoi(buf); //字符串转化为整数
 83         printf("length = %d\n", length);
 84         memset(buf, 0, sizeof(buf));
 85 
 86         ret = recv(fd, buf, length, 0); //接收长度为length字节的数据信息
 87         if(-1 == ret)
 88         {
 				perror("recv2");
 89         }
 91         else if(0 == ret)
 92         {
 93             printf("客户端退出\n");
 94             break;
 95         }
 96         printf("buf_msg = %s\n", buf);
 			memset(buf, 0, sizeof(buf));
 		}			
           

打印结果:

等待客户端的连接....
接受客户端的连接fd = 4
buf_length =    3
length = 3
buf_msg = aaa
客户端退出
           

参考资料:

1.:https://baike.baidu.com/item/Nagle%E7%AE%97%E6%B3%95/5645172?fr=aladdin

2.https://blog.csdn.net/weixin_41047704/article/details/85340311