在学习网络编程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; }
标签:rv,addr,int,len,粘包,fd,TCP,发送数据,include From: https://www.cnblogs.com/genm/p/17181934.html