在发送数据头部加上内容长度解决TCP 数据粘包

发布时间 2023-04-07 13:13:10作者: 面筋玄师

  在学习网络编程socket章节时,发现在客户端向服务器端发送数据时有时可能出现粘包的问题,因此这里记录一下通过添加数据头的方式解决粘包问题。

  • 首先什么是数据粘包?其实之所以出现粘包问题,往往是因为网络问题,或者发送端与接收端发送/接收频率不对等引起的

  因为TCP协议是传输层协议,是面向连接、安全、流式传输协议,流式传输意味着数据传输是基于流的,发送端、接收端处理数据的量、处理频率可以不对等。

  而发送接收数据都会首先经过缓冲区,如果在一个首发过程中,发送端发送了多个数据,而接收端因为尚书等情况从缓冲区读取数据时一次性读取多个数据,就造成了所谓“粘包”

  不过因为tcp协议的稳定性,即使有粘连的多个数据,每个数据自身是不会出错的。

  在下列情况下可能会出现粘包:

       

  实际上所谓的TCP粘包问题根本原因出现在需求过于复杂,并非协议原因

  为了解决粘包问题,我们可以通过这些方式解决:

  • 关键思路代码:

   发送端发送数据时,先将数据的大小记录为到一个4字节的整形数据中,并把其地址拷贝到预发送数据的前面(因此预发送数据大小需要+4);

   接收时先读取最前面的四个字节获取该信息的长度,再根据该长度读取数据,即使在缓冲区中已经有多个数据,但是每次只根据解析出的长度读取相应数据,就不会产生数据粘连

   

   int len = strlen(buf);                     //假设预发送数据保存在buf中:
    int Len =htons(len);                      //字符串没有字节序问题,但是数据头不是字符串是整形,因此需要从主机字节序转换为网络字节序再发送。
    char *msg = (char*)malloc(sizeof(char)*(len+4));     //预发送数据大小需要+4,开辟多四个字节的空间同时用msg指针保存首地址
			
    memcpy(msg,&Len,4);                      //头四个字节用于保存与预送数据长度
    memcpy(msg+4,buf,len);                    //前四个字节已经写入了长度的地址,因此指针需要后移
	
    rv=writen(connect_fd,msg,len+4);                //writen是自己封装的一个函数(见下面),参数为发送的文件描述符,加上头部的预发送数据,加上头部的预发送数据的大小

   usleep(100);                          //故意设置接收时与发送时速度不一致

 

int writen(int fd,char *buf,int n)
{
  int left = n;              //保存每次发送后剩余数据的长度,还没发送时长度为预发送数据的大小
  int nwritten;              //记录每次发送的字节数

  while(left >0)
  {
    if((nwritten = write(fd,buf,left))<= 0)
    {
  return -1;     } left = left -nwritten; buf += nwritten;   }   return 1; }

  接收时也类似

     int rv =0;
	int len = 0;        //用于接收网路字节序的前4个字节数据头
	int leng = 0;       //将接收端到的数据头转为主机字节序
	if(rv =(read(client_fd,(void*)&len,4)) >0)    //先读取前4个记录长度的数据头
	{
	  leng=ntohs(len);
	}
	else
	{
	  return -1;
	}
char *buf=(char *)malloc(leng);      //根据解析出的长度开辟空间 rv = readn(client_fd,buf,leng);      //封装的readn函数(见下面) if(rv <=0) {   close(client_fd);   return -1; }
printf("%s\n",buf);            //接收成功则输出验证是否 sleep(1);                  //故意设置接收时与发送时速度不一致

  

int readn(int fd,char *buf,int n)
{
  int left = n;
  int index =0;
  int nread;
  while(left >0)
  {
    if((nread = read(fd,buf+index,left)) <=0)     {
      return -1;     }
    index = index+nread;     left = left-nread;   }   return 1; }                                //这个函数与writen区别是没有直接用指针加改变位置,而是定义了一个index变量记录指针的位置,在read时使用,其实功能都一样

 以上就是实现解决粘包问题的简单思路代码,可以人为制造粘包情况验证。

 

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

下面是增加多路复用,多线程功能的完整代码,供参考

发送端:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>

#include "socket.h"
int main()
{
	int fd =createSocket();
	const char* str ="127.0.0.1";
	int rv = connectToHost(fd,str,9928);
	if(rv <0)
	{
		return -1;
	}

	int readfd =open("/home/genm/test/socket/book.txt",O_RDONLY);            //预发送数据保存在该路径
	int length = 0;
	char temp[1000];
	while( (length = read(readfd,temp,rand() % 1000)) > 0)                //随机生成发送数据大小
	{
		sendMsg(fd,temp,length);
		bzero(&temp,sizeof(temp));
		usleep(300);
	}

	sleep(10);
	closeSocket(fd);
	return 0;
	
}

 接收端:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>
#include <sys/socket.h>     
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <unistd.h>
#include "socket.h"

struct sockInfo{
	int fd;
	struct sockaddr_in addr;
};

struct sockInfo infos[100];

void * working(void * arg)
{
	struct sockInfo* pinfo = (struct sockInfo*)arg;
//	char ip[32];
//	inet_ntop(AF_INET,&pinfo->addr.sin_addr,ip,sizeof(ip));
//	ntohl(pinfo->addr.sin_port);
	while(1)
	{
		char *pp;
		int len = recvMsg(pinfo -> fd,&pp);
		printf("shi ji receive chang du :%d\n",len);
		if(len >0)
		{
			printf("%s\n\n\n\n",pp);
			free (pp);
		}
		else
		{
			break;
		}
		sleep(1);
	}
	close(pinfo->fd);
	pinfo ->fd =-1;

	return NULL;

}


int main()
{

	int fd = createSocket();
	if(fd <0)
	{
		printf("socket created failure:%s\n",strerror(errno));
	}

	int rv = setListen(fd,9928);
	if(rv <0)
	{
		printf("set listen failure:%s\n",strerror(errno));
		return -1;
	}

	int max = sizeof(infos) / sizeof(infos[0]);
	for(int i =0;i<max;i++)
	{
		bzero(&infos[i],sizeof(infos[0]));
		infos[i].fd =-1;
	}


	while(1)
	{
		struct sockInfo * pinfo;
		for(int i=0;i<max;i++)
		{
			if(infos[i].fd == -1)
			{
				pinfo = &infos[i];
				break;
			}
		}
		pinfo -> fd = acceptConn(fd,&pinfo -> addr);


		pthread_t pid;
		pthread_create(&pid,NULL,working,pinfo);
		pthread_detach(pid);
	}
	close(fd);
}

socket.c:

int createSocket(){

	int fd = socket(AF_INET,SOCK_STREAM,0);

	if(fd < 0)
	{
		printf("socket created failure:%s\n",strerror(errno));
		return -1;
	}

	return fd;
}


int setListen(int fd,unsigned short port){

	struct sockaddr_in server_addr;
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(port);
	server_addr.sin_addr.s_addr = INADDR_ANY;

	int rv = -1;
	rv = bind(fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(rv < 0)
	{
		printf("bind failure:%s\n",strerror(errno));
		return -1;
	}
	
	rv = listen(fd,128);

	if(rv < 0)
	{
		printf("listen failure:%s\n",strerror(errno));
		return -1;
	}

	return rv;
}


int acceptConn(int fd,struct sockaddr_in *cliaddr){

	int connect_fd = -1;
	
	//socklen_t * len = (socklen_t *)cliaddr;

	if(cliaddr == NULL)
	{
		connect_fd = accept(fd,NULL,NULL); 
	}
	else
	{
		int len =sizeof(struct sockaddr_in);
		connect_fd = accept(fd,(struct sockaddr*)cliaddr,&len);
	}

	if(connect_fd < 0)
	{	
		printf("accept failure:%s\n",strerror(errno));
		return -1;
	}

	return connect_fd;

}


int connectToHost(int fd,const char* ip,unsigned short port){

	struct sockaddr_in server_addr;
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(port);
	inet_aton(ip,&server_addr.sin_addr);//here ok?

	int rv = connect(fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(rv = -1)
	{
		printf(" connect failure :%s\n",strerror(errno));
		return -1;
	}
	return rv;

}


int recvMsg(int fd,char *msg,int size){

	int len =recv(fd,msg,size,0);
	if(len == 0)
	{
		printf("disconnected\n");
		close(fd);
	}
	else if(len <0)
	{
		printf("recv failure:%s\n",strerror(errno));
		close(fd);
	}
	return len;
}


int sendMsg(int fd,const char *Msg,int len){
	
	int rv = send(fd,Msg,len,0);
	if(rv <0)
	{
		printf("send failure:%s\n",strerror(errno));
	}

	return rv;

}


int closeSocket(int fd){

	int rv =close(fd);
	if(rv <0)
	{
		printf("close failure:%s\n",strerror(errno));
	}
	return rv;
}

  

socket.h:

int createSocket();

int Write(int fd,char *msg,int len);
	

int Read(int fd,char* str,int size);


int setListen(int fd,unsigned short port);


int acceptConn(int fd,struct sockaddr_in *cliaddr);


int connectToHost(int fd,const char* ip,unsigned short port);

int recvMsg(int fd,char **msg);


int sendMsg(int fd,const char *Msg,int len);


int closeSocket(int fd);

 socket.c:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>
#include <sys/socket.h>     
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <unistd.h>

int createSocket(){

	int fd = socket(AF_INET,SOCK_STREAM,0);

	if(fd < 0)
	{
		printf("socket created failure:%s\n",strerror(errno));
		return -1;
	}
	printf("socket created success\n");
	return fd;
}

int Write(int fd,char *msg,int len)
	{
		
		int count = len;
		int flag =0;
		while(count > 0)
		{
			int rv = write(fd,msg,count);
			
			if(rv <0)
			{
				return -1;		
			}
			else if(rv == 0)
			{
				continue;
			}
			msg += rv;
			count -=rv;
			flag++;
			printf("f:%d\n",flag);
		}
		return len;
	}



int Read(int fd,char* str,int size){

	char *p = str;
	int count = size;
	while(count > 0)
	{
		int len = recv(fd,p,count,0);
		if(len < 0)
		{

			return -1;
		}
		else if(len == 0)
		{
			return size -count;
		}
		p += len;
		count -= len;

	}
	return size;

}



int setListen(int fd,unsigned short port){

	struct sockaddr_in server_addr;
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(port);
	server_addr.sin_addr.s_addr = INADDR_ANY;

	int rv = -1;
	rv = bind(fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(rv < 0)
	{
		printf("bind failure:%s\n",strerror(errno));
		return -1;
	}
	
	printf("bind created success\n");
	rv = listen(fd,128);

	if(rv < 0)
	{
		printf("listen failure:%s\n",strerror(errno));
		return -1;
	}

	printf("listen success\n");
	return rv;
}


int acceptConn(int fd,struct sockaddr_in *cliaddr){

	int connect_fd = -1;
	

	if(cliaddr == NULL)
	{
		connect_fd = accept(fd,NULL,NULL); 
	}
	else
	{
		int len =sizeof(struct sockaddr_in);
		connect_fd = accept(fd,(struct sockaddr*)cliaddr,&len);
	}

	if(connect_fd < 0)
	{	
		printf("accept failure:%s\n",strerror(errno));
		return -1;
	}
	
	printf("connect_fd created success\n");
	return connect_fd;

}


int connectToHost(int fd,const char* ip,unsigned short port){

	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(port);
	inet_pton(AF_INET,ip,&server_addr.sin_addr.s_addr);

	int rv = connect(fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(rv == -1)
	{
		perror("connect");
		printf("connect failure :%s\n",strerror(errno));
		return -1;
	}
	printf("connect success\n");
	return rv;

}


int recvMsg(int fd,char **msg){
	int len = 0;
	Read(fd,(char *)&len,4);
	len = ntohl(len);
	printf("yu ji jie shou shu ju : %d\n",len);
	char *data = (char *)malloc(len+1);
	int rv = Read(fd,data,len);
	if(rv != len)
	{
		printf("receive failure\n");
		close(fd);
		free(data);
		return -1;
	}
	data[len] = '\0';
	*msg = data;
	return  len;
}



int sendMsg(int fd,const char *Msg,int len){
	
	char * msg = (char *)malloc(len+4);
	int Len  = htonl(len);
	memcpy(msg,&Len,4);
	memcpy(msg+4,Msg,len);

	int rv = Write(fd,msg,len+4);

	if(rv <0)
	{
		printf("send failure:%s\n",strerror(errno));
	}

	return rv;

}



int closeSocket(int fd){

	int rv =close(fd);
	if(rv <0)
	{
		printf("close failure:%s\n",strerror(errno));
	}
	return rv;
}