TCP粘包问题的解决方案01——自定义包体
粘包问题:应用层要发送数据,需要调用write函数将数据发送到套接口发送缓冲区。如果应用层数据大小大于SO_SNDBUF,那么,可能产生这样一种情况,应用层的数据一部分已经被发送了,还有一部分还在套接口缓冲区待发送。此时,对方延迟接收,就容易产生粘包。
另一方面,TCP传输有MSS限制,也会对数据进行分割。第三个原因,由于MTU存在,也可能分割数据。都会产生粘包问题
粘包问题解决方案:本质上是要在应用层维护消息与消息的边界。
1、定长包
2、包尾加\r\n(FTP协议)
3、包头加上包体长度
4、更加复杂的应用层协议
利用发送定常包解决粘包问题时,对于定长包的接收,是一个主要问题,在程序中,封装了readn(接收确切数目的读操作)与writen(发送。。。)函数来解决这个问题。
定长包发送程序:
1 /*2 客户端程序中发送定长包解决粘包问题:3 */4 #include<unistd.h>5 #include<sys/types.h>6 #include<sys/socket.h>7 #include<string.h>8 #include<stdlib.h>9 #include<stdio.h>10 #include<errno.h>11 #include<netinet/in.h>12 #include<arpa/inet.h>13 #include<signal.h>14 #define ERR_EXIT(m)\15 do\16 {\17 perror(m);\18 exit(EXIT_FAILURE);\19 }while(0)20 struct packet21 {22 int len;//包头23 char buf[1024];//包体24 };25 //接收确切数目的读操作26 ssize_t readn(int fd,void *buf,size_t count)27 {28 size_t nleft=count;29 ssize_t nread;30 char *bufp=(char*)buf;31 //剩余字节数大于0就循环32 while(nleft>0)33 {34 if((nread=read(fd,bufp,nleft))<0)35 {36 if(errno==EINTR)37 continue; //被信号中断38 else39 return -1;//失败40 }41 //对等方关闭了42 else if(nread==0)43 return (count-nleft);//已经读取的字节数44 bufp+=nread;45 nleft-=nread;46 }47 return count;48 }49 //发送确切数目的写操作50 ssize_t writen(int fd, const void *buf, size_t count)51 {52 size_t nleft=count;53 ssize_t nwritten;54 char *bufp=(char*)buf;55 while(nleft>0)56 {57 if((nwritten=write(fd,bufp,nleft))<=0)58 {59 if(errno==EINTR)60 continue;//信号中断61 return -1;62 }else if(nwritten==0)//write返回0,此时write()什么也不做,好像什么都没发生63 continue;64 bufp+=nwritten;65 nleft-=nwritten;66 }67 return count;68 69 }70 int main(void)71 {72 int sock;//客户端创建套接字73 if((sock=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0)74 ERR_EXIT("socket error");75 76 struct sockaddr_in servaddr;//本地协议地址赋给一个套接字77 memset(&servaddr,0,sizeof(servaddr));78 servaddr.sin_family=AF_INET;79 servaddr.sin_port=htons(5188);80 81 servaddr.sin_addr.s_addr=inet_addr("127.0.0.1");//服务器段地址82 //inet_aton("127.0.0.1",&servaddr.sin_addr);83 84 if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)85 ERR_EXIT("connect");86 struct packet sendbuf;//从标准输入接收发送包87 struct packet recvbuf;//获得服务器端的回射包88 memset(&sendbuf,0,sizeof(sendbuf));89 memset(&recvbuf,0,sizeof(recvbuf));90 int n;//fgets() 函数中的 size 如果小于字符串的长度,那么字符串将会被截取;如果 size 大于字符串的长度则多余的部分系统会自动用 '\0' 填充。所以假如你定义的字符数组长度为 n,那么 fgets() 中的 size 就指定为 n–1,留一个给 '\0' 就行了。91 while(fgets(sendbuf.buf,sizeof(sendbuf.buf),stdin)!=NULL)//默认有换行符92 {93 n=strlen(sendbuf.buf);94 //包体长度要转成网络字节序。95 sendbuf.len=htonl(n);96 writen(sock,&sendbuf,4+n);97 //客户端接收回射数据包头内容。98 int ret=readn(sock,&recvbuf.len,4);99 if(ret==-1) 100 ERR_EXIT("readn"); 101 else if(ret<4) //可能中途中断了 102 { 103 printf("clinet close\n"); 104 break; 105 } 106 n=ntohl(recvbuf.len); 107 //接收包体内容 108 ret=readn(sock,recvbuf.buf,n); 109 if(ret==-1) 110 ERR_EXIT("readn"); 111 else if(ret<n) 112 { 113 printf("clinet close\n"); 114 break; 115 } 116 fputs(recvbuf.buf,stdout); 117 memset(&sendbuf,0,sizeof(sendbuf)); 118 memset(&recvbuf,0,sizeof(recvbuf)); 119 } 120 close(sock); 121 122 return 0; 123 }
定长包的接收服务器程序:
1 /* 2 流协议与粘包 3 粘包产生的原因:若SO_SNDBUF的大小没有应用层一条消息大,可能产生粘包问题,因为因为应用层消息被分割,一部分发送了,一部分还在应用层缓冲区。详见UNP48页 4 粘包处理方案:本质上是要在应用层维护消息与消息的边界 5 1、定长包 2、包尾加 \r\n (ftp) 3、包头加上包体长度(例如包头定长4字节,收取时先读取包头算出包体长度) 4、更复杂的应用层协议 6 7 封装readn writen程序 8 ssize_t read(int fd, void *buf, size_t count); 9 ssize_t write(int fd, const void *buf, size_t count); 10 */ 11 #include<unistd.h> 12 #include<sys/types.h> 13 #include<sys/socket.h> 14 #include<string.h> 15 #include<stdlib.h> 16 #include<stdio.h> 17 #include<errno.h> 18 #include<netinet/in.h> 19 #include<arpa/inet.h> 20 #define ERR_EXIT(m)\ 21 do\ 22 {\ 23 perror(m);\ 24 exit(EXIT_FAILURE);\ 25 }while(0) 26 struct packet 27 { 28 int len;//包头 29 char buf[1024];//包体 30 }; 31 //接收确切数目的读操作 32 ssize_t readn(int fd,void *buf,size_t count) 33 { 34 size_t nleft=count; 35 ssize_t nread; 36 char *bufp=(char*)buf; 37 //剩余字节数大于0就循环 38 while(nleft>0) 39 { 40 if((nread=read(fd,bufp,nleft))<0) 41 { 42 if(errno==EINTR) 43 continue; 44 else 45 return -1; 46 } 47 //对等方关闭了 48 else if(nread==0) 49 return (count-nleft);//已经读取的字节数 50 bufp+=nread; 51 nleft-=nread; 52 } 53 return count; 54 } 55 //发送确切数目的写操作 56 ssize_t writen(int fd, const void *buf, size_t count) 57 { 58 size_t nleft=count; 59 ssize_t nwritten; 60 char *bufp=(char*)buf; 61 while(nleft>0) 62 { 63 if((nwritten=write(fd,bufp,nleft))<=0) 64 { 65 if(errno==EINTR) 66 continue; 67 return -1; 68 }else if(nwritten==0)//好像什么都没发生 69 continue; 70 bufp+=nwritten; 71 nleft-=nwritten; 72 } 73 return count; 74 75 } 76 //服务器回射。 77 void do_service(int conn) 78 { 79 struct packet recvbuf; 80 int n; 81 while(1) 82 { 83 memset(&recvbuf,0,sizeof(recvbuf)); 84 //使用readn之后客户端发送的数据不足n会阻塞 85 //在客户端程序中确定消息的边界,发送定长包 86 int ret=readn(conn,&recvbuf.len,4); 87 //客户端关闭 88 if(ret==-1) 89 ERR_EXIT("read error"); 90 else if(ret<4)//中途中断了。 91 { 92 printf("client close\n"); 93 break;//不用继续循环等待客户端数据 94 } 95 //接收包体 96 n=ntohl(recvbuf.len);//包体长度 97 ret=readn(conn,recvbuf.buf,n); 98 if(ret==-1) 99 ERR_EXIT("read error"); 100 else if(ret<n)//接收到的字节数不足,对端中途关闭 101 { 102 printf("client close\n"); 103 break;//不用继续循环等待客户端数据 104 } 105 fputs(recvbuf.buf,stdout); 106 writen(conn,&recvbuf,4+n); 107 } 108 } 109 int main(void) 110 { 111 int listenfd; 112 if((listenfd=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0) 113 ERR_EXIT("socket error"); 114 //if((listenfd=socket(PF_INET,SOCK_STREAM,0))<0) 115 116 117 //本地协议地址赋给一个套接字 118 struct sockaddr_in servaddr; 119 memset(&servaddr,0,sizeof(servaddr)); 120 servaddr.sin_family=AF_INET; 121 servaddr.sin_port=htons(5188); 122 servaddr.sin_addr.s_addr=htonl(INADDR_ANY);//表示本机地址 123 //servaddr.sin_addr.s_addr=inet_addr("127.0.0.1"); 124 //inet_aton("127.0.0.1",&servaddr.sin_addr); 125 126 //开启地址重复使用,关闭服务器再打开不用等待TIME_WAIT 127 int on=1; 128 if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0) 129 ERR_EXIT("setsockopt error"); 130 //绑定本地套接字 131 if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0) 132 ERR_EXIT("bind error"); 133 if(listen(listenfd,SOMAXCONN)<0)//设置监听套接字(被动套接字) 134 ERR_EXIT("listen error"); 135 136 struct sockaddr_in peeraddr;//对方套接字地址 137 socklen_t peerlen=sizeof(peeraddr); 138 int conn;//已连接套接字(主动套接字) 139 pid_t pid; 140 while(1){ 141 if((conn=accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen))<0) 142 ERR_EXIT("accept error"); 143 //连接好之后就构成连接,端口是客户端的。peeraddr是对端 144 printf("ip=%s port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port)); 145 pid=fork(); 146 if(pid==-1) 147 ERR_EXIT("fork"); 148 if(pid==0){ 149 close(listenfd); 150 do_service(conn); 151 //某个客户端关闭,结束该子进程,否则子进程也去接受连接 152 exit(EXIT_SUCCESS); 153 }else close(conn); 154 } 155 return 0; 156 }
转载于:https://www.cnblogs.com/wsw-seu/p/8214422.html
TCP粘包问题的解决方案01——自定义包体相关推荐
- python包导入方式_python导包的几种方法(自定义包的生成以及导入详解)
python 导入数据包的几种方法 1.直接导入整个数据包:improt 数据包 2.导入数据包中的某一个函数: from 数据包 improt 函数(当函数这一项为 * 时为导入整个数据包) 3. ...
- 20-Netty TCP 粘包和拆包及解决方案
TCP粘包和拆包的基本介绍 TCP是面向连接的, 面向流的, 提供可靠性服务, 收发两端(客户端和服务器端) 都有一一成对的Socket,因此发送端为了将多个发给接收端的包, 更有效的发给对方, 使用 ...
- TCP 粘包和拆包及解决方案
TCP 粘包和拆包基本介绍 1.TCP 是面向连接的,面向流的,提供高可靠性服务.收发两端(客户端和服务器端)都要有一一成对的 socket,因此,发送端为了将多个发给接收端的包,更有效的发给对方,使 ...
- TCP粘包|拆包和解决方案
1 产生原因 TCP是面向连接的,面向流的,提供高可靠性服务.收发两端(客户端和服务端)都要有一一成对的socket,因此,发送端为了将多个发给接收端的包,更有效的发给对方,使用了优化算法(Nagle ...
- TCP粘包、半包原理及解决方案
引言:TCP协议是网络通信协议中十分重要的协议,相比于UDP协议来说,它是一个可靠的传输协议,并且是一个面向数据流的协议:所谓面向数据流,其实是指数据传输是以流式的方式传输,这些传输的数据就像一条河里 ...
- Socket编程(4)TCP粘包问题及解决方案
① TCP是个流协议,它存在粘包问题 TCP是一个基于字节流的传输服务,"流"意味着TCP所传输的数据是没有边界的.这不同于UDP提供基于消息的传输服务,其传输的数据是有边界的.T ...
- Netty4实战 - TCP粘包拆包解决方案
Netty4实战 - TCP粘包&拆包解决方案 参考文章: (1)Netty4实战 - TCP粘包&拆包解决方案 (2)https://www.cnblogs.com/hunrry/p ...
- TCP粘包和拆包的定义,产生的原因以及解决方案
TCP粘包和拆包的定义,产生的原因以及解决方案 参考文章: (1)TCP粘包和拆包的定义,产生的原因以及解决方案 (2)https://www.cnblogs.com/yinbiao/p/110150 ...
- tcp 粘包 丢包 解决方案
1.分析tcp粘包和丢包的原因 发送数据的时候有 发送缓冲区senBuff, 接收数据的时候有 接收缓冲区recvBuff, 假如接收数据方一直不recv, 则recvBuff就会堆满, 这个时候 ...
最新文章
- 程序模拟电影院窗口卖票,多线程Demo
- SAP MM IV中的Duplicated Invoice Check功能的测试
- Loki 2.0.0 发布,ELK之外的优秀日志聚合系统!
- 人工智能历经风雨二十载 AI专用芯片成蓝海
- UA MATH566 统计理论 推导卡方拟合优度检验
- IOS 开发中判断NSString是否为空字符
- 使用Yeoman定制前端脚手架
- 以高并发著称的 Go 如何与 MySQL 搭档应对千亿级数据?
- CF1090F - How to Learn You Score(构造)
- kl散度的理解_以曲率的视角理解自然梯度优化
- 常州工学院计算机毕业论文多少字,研究常州工学院毕业论文(设计)系统快速使用指南.doc...
- php dom手册,DOM 元素 - JavaScript中文参考手册 - php中文网手册
- 化工企业数据分析中心项目之采购模块分析
- 计算机组装小游戏,电脑组装店游戏
- AMD OpenCL Programming Guide - OpenCL Architecture
- 水果店的开业活动怎么做,水果店开业活动朋友圈怎么发
- qt保存文件的默认路径_Qt将文件保存到指定目录下(另存为的功能)
- 【历史上的今天】8 月 25 日:Linux 诞生;我国第一个计算机科学技术研究所成立
- 王半仙儿的日记-0002
- C++11 bind函数
热门文章
- Ubuntu OpenCV 自定义环境变量 pkg-config / PKGCONFIGPATH
- linux记录iptables日志,linux – 如何配置syslog.conf文件,在单独的文件中记录iptables消息?...
- json爬虫获取列表数据不全,已解决
- jacket for matlab,打印本页 - 在联想系统上使用Jacket For Matlab
- 监控某个dll被修改_浅谈动力环境监控系统技术标准
- JavaWeb那些事儿(二)--java中类、成员和方法的访问权限
- Docker Swarm架构、特性与基本实践
- 《刀塔传奇》付费设计分析
- Wamp环境下集成【禅道】管理软件
- payload的使 常用xss_跨站脚本XSS Payloads生成器