在发送数据头部加上内容长度解决TCP 数据粘包
在学习网络编程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;
}

浙公网安备 33010602011771号