粘包问题:应用层要发送数据,需要调用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——自定义包体相关推荐

  1. python包导入方式_python导包的几种方法(自定义包的生成以及导入详解)

    python 导入数据包的几种方法 1.直接导入整个数据包:improt 数据包 2.导入数据包中的某一个函数: from 数据包 improt 函数(当函数这一项为 * 时为导入整个数据包) 3. ...

  2. 20-Netty TCP 粘包和拆包及解决方案

    TCP粘包和拆包的基本介绍 TCP是面向连接的, 面向流的, 提供可靠性服务, 收发两端(客户端和服务器端) 都有一一成对的Socket,因此发送端为了将多个发给接收端的包, 更有效的发给对方, 使用 ...

  3. TCP 粘包和拆包及解决方案

    TCP 粘包和拆包基本介绍 1.TCP 是面向连接的,面向流的,提供高可靠性服务.收发两端(客户端和服务器端)都要有一一成对的 socket,因此,发送端为了将多个发给接收端的包,更有效的发给对方,使 ...

  4. TCP粘包|拆包和解决方案

    1 产生原因 TCP是面向连接的,面向流的,提供高可靠性服务.收发两端(客户端和服务端)都要有一一成对的socket,因此,发送端为了将多个发给接收端的包,更有效的发给对方,使用了优化算法(Nagle ...

  5. TCP粘包、半包原理及解决方案

    引言:TCP协议是网络通信协议中十分重要的协议,相比于UDP协议来说,它是一个可靠的传输协议,并且是一个面向数据流的协议:所谓面向数据流,其实是指数据传输是以流式的方式传输,这些传输的数据就像一条河里 ...

  6. Socket编程(4)TCP粘包问题及解决方案

    ① TCP是个流协议,它存在粘包问题 TCP是一个基于字节流的传输服务,"流"意味着TCP所传输的数据是没有边界的.这不同于UDP提供基于消息的传输服务,其传输的数据是有边界的.T ...

  7. Netty4实战 - TCP粘包拆包解决方案

    Netty4实战 - TCP粘包&拆包解决方案 参考文章: (1)Netty4实战 - TCP粘包&拆包解决方案 (2)https://www.cnblogs.com/hunrry/p ...

  8. TCP粘包和拆包的定义,产生的原因以及解决方案

    TCP粘包和拆包的定义,产生的原因以及解决方案 参考文章: (1)TCP粘包和拆包的定义,产生的原因以及解决方案 (2)https://www.cnblogs.com/yinbiao/p/110150 ...

  9. tcp 粘包 丢包 解决方案

    1.分析tcp粘包和丢包的原因 发送数据的时候有  发送缓冲区senBuff, 接收数据的时候有  接收缓冲区recvBuff, 假如接收数据方一直不recv, 则recvBuff就会堆满, 这个时候 ...

最新文章

  1. 程序模拟电影院窗口卖票,多线程Demo
  2. SAP MM IV中的Duplicated Invoice Check功能的测试
  3. Loki 2.0.0 发布,ELK之外的优秀日志聚合系统!
  4. 人工智能历经风雨二十载 AI专用芯片成蓝海
  5. UA MATH566 统计理论 推导卡方拟合优度检验
  6. IOS 开发中判断NSString是否为空字符
  7. 使用Yeoman定制前端脚手架
  8. 以高并发著称的 Go 如何与 MySQL 搭档应对千亿级数据?
  9. CF1090F - How to Learn You Score(构造)
  10. kl散度的理解_以曲率的视角理解自然梯度优化
  11. 常州工学院计算机毕业论文多少字,研究常州工学院毕业论文(设计)系统快速使用指南.doc...
  12. php dom手册,DOM 元素 - JavaScript中文参考手册 - php中文网手册
  13. 化工企业数据分析中心项目之采购模块分析
  14. 计算机组装小游戏,电脑组装店游戏
  15. AMD OpenCL Programming Guide - OpenCL Architecture
  16. 水果店的开业活动怎么做,水果店开业活动朋友圈怎么发
  17. qt保存文件的默认路径_Qt将文件保存到指定目录下(另存为的功能)
  18. 【历史上的今天】8 月 25 日:Linux 诞生;我国第一个计算机科学技术研究所成立
  19. 王半仙儿的日记-0002
  20. C++11 bind函数

热门文章

  1. Ubuntu OpenCV 自定义环境变量 pkg-config / PKGCONFIGPATH
  2. linux记录iptables日志,linux – 如何配置syslog.conf文件,在单独的文件中记录iptables消息?...
  3. json爬虫获取列表数据不全,已解决
  4. jacket for matlab,打印本页 - 在联想系统上使用Jacket For Matlab
  5. 监控某个dll被修改_浅谈动力环境监控系统技术标准
  6. JavaWeb那些事儿(二)--java中类、成员和方法的访问权限
  7. Docker Swarm架构、特性与基本实践
  8. 《刀塔传奇》付费设计分析
  9. Wamp环境下集成【禅道】管理软件
  10. payload的使 常用xss_跨站脚本XSS Payloads生成器