TCP 数据粘包问题的处理

TCP协议在传输数据时可能出现粘包现象,即接收方无法准确区分不同数据包的边界。为解决此问题,文章介绍了两种常见策略:在数据尾部添加特殊字符作为边界标志,或在数据前添加包头记录数据长度。文中还提供了客户端和服务器的C代码示例,展示如何通过在数据前添加包头来避免粘包问题。
摘要由CSDN通过智能技术生成

TCP概述

TCP是传输层的协议,它是一个面向连接,安全的,流传输协议。因为数据传输基于流的所以发送端和接收端每次处理的数量,处理数据的频率是不对等的,传递的数据是没有消息边界的。

问题分析

客户端和服务器之间要进行基于TCP的套接字通信

  • 通信过程中客户端每次会不定期给服务器发送一个不定长的特定含义的字符串
  • 通信的服务器每次都需要接受到客户用户端这个不定长的字符串,并对其进行解析

根据上面的描述,我们可能遇到如下情况
1.一次接收到客户端发送过来的一个完整的数据包
2.一次接收到客户端发送的N个数据包,因为每个包长度不定,无法将每个数据包拆开
3.一次接受到一个或者多个数据包+下一个数据包的一部分,无法将数据包拆开
4.一次收到半个数据包,下一次接收数据的时候收到剩下的一部分+下一个数据包的一部分,当然这种情况更难拆包
5.一些其他因素,导致客户端和服务器的发送和接受速度不一样。

上述问题,就是TCP粘包问题

解决方案

解决方案
1.使用标准应用层协议(比如:http)来封装传输的不定长数据包
2.在每条数据的尾部添加特殊字符,(充当边界),如果遇到特殊字符,代表当条数据接受完毕。
(效率低,需要一个一个字节的接受,接受一个字符判断一次)
3.在发送数据之前,在数据块最前便添加一固定大小的数据头,用来存放此条数据的大小。

具体实现分析

在这里插入图片描述

发送端(客户端)

1.根据待发送的数据长度N动态开辟固定达雄安的内存:N+4(4是包头的长度)
2.将待发送数据的总长度写入申请的内存的前四个字节,此处需要将其转化成为网络字节序(大端)
3.将待发送的数据拷贝到包头后边的地址空间中,再将完整的数据包发送出去(字符串没有字节序问题)
4.申请释放空间

int writen(int fd, char*msg,int size)
{
  char*buf=msg;
  int count =size;
  while(count>0)
  {
    int len=send(fd,buf,count,0);
    if(len==-1)
    {
      return -1;
    }
    else if(len==0)
    {
      continue;
    }
    buf+=len;
    count-=len;
  }
  return size;
}

int sendMsg(int fd,const char*msg,int len)
{
  if(fd<0||msg==NULL||len<=0)
  {
    return -1;
  }
  char*data=(char*)malloc(sizeof(char)*(len+4));
  int biglen=htonl(len);
  memcpy(data,&biglen,4);
  memcpy(data+4,msg,len);
  int ret;
  ret=writen(fd,data,len+4);
  if(ret==-1)
  {
    perror("send error");
    free(data);
    close(fd);
  }
  free(data);
  return ret;
}

客户端

#include"socket.h"
int main()
{
  char temp[1001];
  int fd=socket(AF_INET,SOCK_STREAM,0);
  struct sockaddr_in caddr;
  caddr.sin_family=AF_INET;
  caddr.sin_port=htons(9526);
  inet_pton(AF_INET,"127.0.0.1",&caddr.sin_addr.s_addr);
  connect(fd,(struct sockaddr*)&caddr,sizeof(caddr));
  printf("客户端连接成功\n");
  if(fd==-1)
    return -1;
  
  int length=0;
  int fd1=open("english.txt",O_RDONLY);
  while((length=read(fd1,temp,rand()%1000))>0)
  {
    sendMsg(fd,temp,length);
    memset(temp,0,sizeof(temp));
    usleep(300);
  }
  sleep(10);
}

接收端(服务器)

int readn(int fd,char*buf,int size)
{
  char*pt=buf;
  int count =size;
  while(count>0)
  {
    int len=recv(fd,pt,count,0);
    if(len==-1)
    {
      return -1;
    }
    else if(len==0)
    {
      return size-count;
    }
    pt+=len;
    count-=len;
  }
  return size-count;
}
int recvMsg(int fd,char**msg)
{
  int len=0;
  readn(fd,(char*)&len,4);
  len=ntohl(len);
  printf("接收到的 数据块大小 %d\n",len);
  char * data=(char*)malloc(len+1);
  int Len=readn(fd,data,len);
  if(Len==0)
  { 
      printf("对方断开链接\n");
      close(fd);    
  }
  else if(len!=Len)
  {
    printf("数据接收失败\n");
  }
  data[len]='\0';
  *msg=data;
  return Len;
}

服务器

#include"socket.h"
int createsocket()
{
  int lfd=socket(AF_INET,SOCK_STREAM,0);
  if(lfd==-1)
  {
    perror("socket error");
    return -1;
  }
  else 
  {
    printf("套接字创建成功\n");
    return lfd;
  }
}
int setListen(int lfd,unsigned short port)
{
  struct sockaddr_in saddr;
  saddr.sin_family=AF_INET;
  saddr.sin_port=htons(port);
  saddr.sin_addr.s_addr=INADDR_ANY;
  int ret=0;
  ret=bind(lfd,(struct sockaddr*)&saddr,sizeof(saddr));
  if(ret==-1)
  {
    perror("bind error");
    return -1;
  }
  else 
  {
    printf("套接字地址绑定成功\n");
  }
  listen(lfd,128);
  return 1;
}
int acceptConn(int lfd,struct sockaddr_in*addr)
{
  int cfd=-1;
  if(addr==NULL)
  {
    cfd=accept(lfd,NULL,NULL);
    return cfd;
  }
  socklen_t len=sizeof(addr);
  cfd=accept(lfd,(struct sockaddr*)&addr,&len);
  return cfd;
}
int connectToHost(int  lfd,char*ip,unsigned short port)
{
  struct sockaddr_in caddr;
  caddr.sin_family=AF_INET;
  caddr.sin_port=htons(port);
  inet_pton(AF_INET,ip,&caddr.sin_addr.s_addr);
  int fd= connect(lfd,(struct sockaddr*)&caddr,sizeof(caddr));
  printf("成功与客户端建立链接\n");
  return fd;
}
int writen(int fd, char*msg,int size)
{
  char*buf=msg;
  int count =size;
  while(count>0)
  {
    int len=send(fd,buf,count,0);
    if(len==-1)
    {
      return -1;
    }
    else if(len==0)
    {
      continue;
    }
    buf+=len;
    count-=len;
  }
  return size;
}

int sendMsg(int fd,const char*msg,int len)
{
  if(fd<0||msg==NULL||len<=0)
  {
    return -1;
  }
  char*data=(char*)malloc(sizeof(char)*(len+4));
  int biglen=htonl(len);
  memcpy(data,&biglen,4);
  memcpy(data+4,msg,len);
  int ret;
  ret=writen(fd,data,len+4);
  if(ret==-1)
  {
    perror("send error");
    close(fd);
  }
  return ret;
}
int readn(int fd,char*buf,int size)
{
  char*pt=buf;
  int count =size;
  while(count>0)
  {
    int len=recv(fd,pt,count,0);
    if(len==-1)
    {
      return -1;
    }
    else if(len==0)
    {
      return size-count;
    }
    pt+=len;
    count-=len;
  }
  return size-count;
}
int recvMsg(int fd,char**msg)
{
  int len=0;
  readn(fd,(char*)&len,4);
  len=ntohl(len);
  printf("接收到的 数据块大小 %d\n",len);
  char * data=(char*)malloc(len+1);
  int Len=readn(fd,data,len);
  if(Len==0)
  { 
      printf("对方断开链接\n");
      close(fd);    
  }
  else if(len!=Len)
  {
    printf("数据接收失败\n");
  }
  data[len]='\0';
  *msg=data;
  return Len;
}
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

binary~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值