当前位置: 首页 > news >正文

TCP粘包原因分析以及解决方案

一、TCP粘包简介

        使用TCP 协议进行数据传输时,多个数据包被连续存储于缓存中,在对数据包进行读取时由于无法确定发送方的发送边界,而采用某一估测值大小来进行数据读取,使得发送方发送的若干个数据包到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。
从上图中可以看出粘包主要分为两种情况:
        多个完整的数据包粘在一起
        一个完整的数据包被拆分成两部分与其它完整包粘在一起
TCP粘包问题会影响到有数据结构的数据包,会导致数据包解析出现问题.

二、TCP粘包原因分析

TCP 协议是面向连接的、可靠的、基于字节流的传输层通信协议出现粘包现象的原因是多方面的,它既可能由发送方造成,也可能由接收方造成。

粘包的根本原因

原因说明
TCP是字节流协议数据像水流一样没有固定边界,应用层需自行划分。
Nagle算法默认开启,会合并多个小数据包发送(减少网络拥塞)。
缓冲区机制发送缓冲区积累到一定大小或超时才会发送;接收缓冲区可能一次性读取多个包。

发送方引起的粘包是由TCP协议本身造成的

        TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一包数据。若连续几次发送的数据都很少,通常TCP 会根据优化算法 (Nagle) 把这些数据合成一包后一次发送出去,这样接收方就收到了粘包数据。

发送方一次性发送的数据大于 MTU ,则会发生拆包,将字节流进行切片分成多个包进行发送
接收方引起的粘包是由于接收方用户进程不及时接收数据,从而导致粘包现象
        这是因为接收方先把收到的数据放在系统接收缓冲区,用户进程从该缓冲区取数据,若下一包数据到达时前一包数据尚未被用户进程取走,则下一包数据放到系统接收缓冲区时就接到前一包数据之后,而用户进程根据预先设定的缓冲区大小从系统接收缓冲区取数据,这样就一次取到了多包数据。

三、TCP解决方案

方式一 : 使用定长数据包,每次必须要读取固定长度的数据,(适用于数据长度固定的情景)

方式二:使用数据长度+数据的方式,先接收数据长度,再根据长度接收数据,(适用于数据长读度不固定的情景)

三, TCP粘包使用方式二解决实现

发送时,将数据长度+数据 作为数据包的内容,整体发送:
客户端:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
int main(int argc,char* argv[])
{
// 创建socket套接字的
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1)
 {
perror("socket");
exit(EXIT_FAILURE);
 }
// 与服务器进行连接
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port=htons(atoi(argv[2]));
inet_aton(argv[1],&(addr.sin_addr));
int ret = connect(sockfd,(const struct sockaddr*)&addr,sizeof(struct
sockaddr_in));
if(ret==-1 )
 {
perror("connect");
close(sockfd);
exit(EXIT_FAILURE);
 }
// 向服务器端发送数据
char buf[]={"hello tcp!"};
for(int i=0;i<1000;i++)
 {
int ret;
// 1、计算数据长度
int length = strlen(buf);
// 2、开辟空间
char* p_buf=(char*)malloc(length+4);// 使用整型来保存数据长度
// 3、对指定的空间赋值
memcpy(p_buf,&length,4);
memcpy(p_buf+4,buf,length);
//4、发送数据
ret = send(sockfd,p_buf,length+4,0);
if(ret == -1)
 {
perror("send failed");
exit(EXIT_FAILURE);
 }
 }
close(sockfd);
return 0;
}

服务器端:

接收时,先接收数据长度,再根据数据长度固定接收数据:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <strings.h>
#include <arpa/inet.h>
#include <unistd.h>
#define BACKLOG 20
int main(int argc,char* argv[])
{
if(argc !=3)
 {
fprintf(stderr,"%s ip port.\n",argv[0]);
exit(EXIT_FAILURE);
 }
// 创建socket
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1)
 {
perror("socket failed.");
exit(EXIT_FAILURE);
 }
// 绑定ip+端口号
struct sockaddr_in addr;
int addrlen = sizeof(struct sockaddr_in);
bzero(&addr,addrlen);
addr.sin_family = AF_INET;
addr.sin_port=htons(atoi(argv[2]));
inet_aton(argv[1],&(addr.sin_addr));
int ret = bind(sockfd,(const struct sockaddr*)&addr,addrlen);
if(ret == -1)
 {
perror("bind failed");
exit(EXIT_FAILURE);
 }
// 建立监听队列
ret = listen(sockfd,BACKLOG);
if(ret == -1)
 {
perror("listen failed");
exit(EXIT_FAILURE);
 }
//建立连接
struct sockaddr_in client_addr;
int client_addrlen = sizeof(struct sockaddr_in);
int cfd = accept(sockfd,(struct
sockaddr*)&client_addr,&client_addrlen);
if(cfd == -1)
 {
perror("accept failed.");
exit(EXIT_FAILURE);
 }
// 打印客户端的信息
ssize_t rbytes = 0;
char buf[1024]={0};
while(1)
 {
int length = 0;
int total_received = 0;//已经接收的字节数
// 1、先接收数据长度
rbytes = recv(cfd,&length,4,0);
if(rbytes == -1)
 {
perror("recv data length failed");
exit(EXIT_FAILURE);
 }
memset(buf,0,sizeof(buf));
// 接收客户端的消息(注意:使用新的文件标识符)
while(1)
 {
printf("length=%d\n",length);
rbytes = recv(cfd,buf+total_received,lengthtotal_received,0);
if(rbytes==-1)
 {
perror("server recv failed.");
exit(EXIT_FAILURE);
 }
else if(rbytes == 0)
 {
fprintf(stderr,"stream socket shutdown.\n");
exit(EXIT_FAILURE);
 }
else
 {
total_received += rbytes;
if(total_received == length)//接收完成
 {
printf("recv message: %s\n",buf);
break;
 }
 }
 }
// 休眠一秒,发送数据比接收数据快
sleep(1);
 }
return 0;
}
运行结果:

相关文章:

  • 什么是权威解析服务器?权威解析服务器有什么用?(国科云)
  • 时尚复古品牌海报包装设计无衬线英文字体安装包 Malevice Inkbleed
  • uniapp中的vue组件与组件使用差异
  • Oralce 数据库通过exp/imp工具迁移指定数据表
  • ANSYS Swan 语言 forward 迭代 (三) - resume 状态保持
  • el-table 合并单元格
  • keil编译报错,error:xx.h:NO such file or directory 解决办法
  • StarRocks BE宕机排查
  • 【鸿蒙开发】Hi3861学习笔记- TCP客户端
  • 卷积神经网络 - AlexNet
  • Java学习总结-Map集合的实现类
  • 模数转换电路(A/D转换器)
  • 第一天学爬虫
  • <Transition>和<KeepAlive>组件一起用有什么用
  • 椭圆曲线密码学(ECC)深度解析:下一代非对称加密的核心
  • 【react】在react中async/await一般用来实现什么功能
  • 动态规划二维费用的背包系列一>一和零
  • 【CXX-Qt】5.1 CXX-Qt 构建系统
  • Go 代理爬虫
  • 《基于SpringBoot的图书网购平台的设计与实现》开题报告
  • 如何自己网站接装修生意做/百度用户服务中心官网
  • 织梦做仿站时 为何会发生本地地址跳转网站地址/北京网站seo服务
  • 内蒙古网络/爱站网seo工具包
  • 网站建设全/上海seo
  • 怎么查询技术支持公司做的网站/拉新充场app推广平台
  • 政府网站建设管理原则/媒体资源网