1.  
  1. 粘包问题:应用层要发送数据,需要调用write函数将数据发送到套接口发送缓冲区。如果应用层数据大小大于SO_SNDBUF,
    那么,可能产生这样一种情况,应用层的数据一部分已经被发送了,还有一部分还在套接口缓冲区待发送。此时,对方延迟接收,就容易产生粘包。
  1. 另一方面,TCP传输有MSS限制,也会对数据进行分割。第三个原因,由于MTU存在,也可能分割数据。都会产生粘包问题


  粘包问题解决方案:本质上是要在应用层维护消息与消息的边界。

1、定长包

2、包尾加\r\n(FTP协议)

3、包头加上包体长度

4、更加复杂的应用层协议

  利用发送定常包解决粘包问题时,对于定长包的接收,是一个主要问题,在程序中,封装了readn(接收确切数目的读操作)与writen(发送。。。)函数来解决这个问题。

   

  1. 定长包发送程序:
  1. 1 /*
  2. 2 客户端程序中发送定长包解决粘包问题:
  3. 3 */
  4. 4 #include<unistd.h>
  5. 5 #include<sys/types.h>
  6. 6 #include<sys/socket.h>
  7. 7 #include<string.h>
  8. 8 #include<stdlib.h>
  9. 9 #include<stdio.h>
  10. 10 #include<errno.h>
  11. 11 #include<netinet/in.h>
  12. 12 #include<arpa/inet.h>
  13. 13 #include<signal.h>
  14. 14 #define ERR_EXIT(m)\
  15. 15 do\
  16. 16 {\
  17. 17 perror(m);\
  18. 18 exit(EXIT_FAILURE);\
  19. 19 }while(0)
  20. 20 struct packet
  21. 21 {
  22. 22 int len;//包头
  23. 23 char buf[1024];//包体
  24. 24 };
  25. 25 //接收确切数目的读操作
  26. 26 ssize_t readn(int fd,void *buf,size_t count)
  27. 27 {
  28. 28 size_t nleft=count;
  29. 29 ssize_t nread;
  30. 30 char *bufp=(char*)buf;
  31. 31 //剩余字节数大于0就循环
  32. 32 while(nleft>0)
  33. 33 {
  34. 34 if((nread=read(fd,bufp,nleft))<0)
  35. 35 {
  36. 36 if(errno==EINTR)
  37. 37 continue; //被信号中断
  38. 38 else
  39. 39 return -1;//失败
  40. 40 }
  41. 41 //对等方关闭了
  42. 42 else if(nread==0)
  43. 43 return (count-nleft);//已经读取的字节数
  44. 44 bufp+=nread;
  45. 45 nleft-=nread;
  46. 46 }
  47. 47 return count;
  48. 48 }
  49. 49 //发送确切数目的写操作
  50. 50 ssize_t writen(int fd, const void *buf, size_t count)
  51. 51 {
  52. 52 size_t nleft=count;
  53. 53 ssize_t nwritten;
  54. 54 char *bufp=(char*)buf;
  55. 55 while(nleft>0)
  56. 56 {
  57. 57 if((nwritten=write(fd,bufp,nleft))<=0)
  58. 58 {
  59. 59 if(errno==EINTR)
  60. 60 continue;//信号中断
  61. 61 return -1;
  62. 62 }else if(nwritten==0)//write返回0,此时write()什么也不做,好像什么都没发生
  63. 63 continue;
  64. 64 bufp+=nwritten;
  65. 65 nleft-=nwritten;
  66. 66 }
  67. 67 return count;
  68. 68
  69. 69 }
  70. 70 int main(void)
  71. 71 {
  72. 72 int sock;//客户端创建套接字
  73. 73 if((sock=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0)
  74. 74 ERR_EXIT("socket error");
  75. 75
  76. 76 struct sockaddr_in servaddr;//本地协议地址赋给一个套接字
  77. 77 memset(&servaddr,0,sizeof(servaddr));
  78. 78 servaddr.sin_family=AF_INET;
  79. 79 servaddr.sin_port=htons(5188);
  80. 80
  81. 81 servaddr.sin_addr.s_addr=inet_addr("127.0.0.1");//服务器段地址
  82. 82 //inet_aton("127.0.0.1",&servaddr.sin_addr);
  83. 83
  84. 84 if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
  85. 85 ERR_EXIT("connect");
  86. 86 struct packet sendbuf;//从标准输入接收发送包
  87. 87 struct packet recvbuf;//获得服务器端的回射包
  88. 88 memset(&sendbuf,0,sizeof(sendbuf));
  89. 89 memset(&recvbuf,0,sizeof(recvbuf));
  90. 90 int n;//fgets() 函数中的 size 如果小于字符串的长度,那么字符串将会被截取;如果 size 大于字符串的长度则多余的部分系统会自动用 '\0' 填充。所以假如你定义的字符数组长度为 n,那么 fgets() 中的 size 就指定为 n–1,留一个给 '\0' 就行了。
  91. 91 while(fgets(sendbuf.buf,sizeof(sendbuf.buf),stdin)!=NULL)//默认有换行符
  92. 92 {
  93. 93 n=strlen(sendbuf.buf);
  94. 94 //包体长度要转成网络字节序。
  95. 95 sendbuf.len=htonl(n);
  96. 96 writen(sock,&sendbuf,4+n);
  97. 97 //客户端接收回射数据包头内容。
  98. 98 int ret=readn(sock,&recvbuf.len,4);
  99. 99 if(ret==-1)
  100. 100 ERR_EXIT("readn");
  101. 101 else if(ret<4) //可能中途中断了
  102. 102 {
  103. 103 printf("clinet close\n");
  104. 104 break;
  105. 105 }
  106. 106 n=ntohl(recvbuf.len);
  107. 107 //接收包体内容
  108. 108 ret=readn(sock,recvbuf.buf,n);
  109. 109 if(ret==-1)
  110. 110 ERR_EXIT("readn");
  111. 111 else if(ret<n)
  112. 112 {
  113. 113 printf("clinet close\n");
  114. 114 break;
  115. 115 }
  116. 116 fputs(recvbuf.buf,stdout);
  117. 117 memset(&sendbuf,0,sizeof(sendbuf));
  118. 118 memset(&recvbuf,0,sizeof(recvbuf));
  119. 119 }
  120. 120 close(sock);
  121. 121
  122. 122 return 0;
  123. 123 }

  定长包的接收服务器程序:

  1. 1 /*
  2. 2 流协议与粘包
  3. 3 粘包产生的原因:若SO_SNDBUF的大小没有应用层一条消息大,可能产生粘包问题,因为因为应用层消息被分割,一部分发送了,一部分还在应用层缓冲区。详见UNP48页
  4. 4 粘包处理方案:本质上是要在应用层维护消息与消息的边界
  5. 5 1、定长包 2、包尾加 \r\n (ftp) 3、包头加上包体长度(例如包头定长4字节,收取时先读取包头算出包体长度) 4、更复杂的应用层协议
  6. 6
  7. 7 封装readn writen程序
  8. 8 ssize_t read(int fd, void *buf, size_t count);
  9. 9 ssize_t write(int fd, const void *buf, size_t count);
  10. 10 */
  11. 11 #include<unistd.h>
  12. 12 #include<sys/types.h>
  13. 13 #include<sys/socket.h>
  14. 14 #include<string.h>
  15. 15 #include<stdlib.h>
  16. 16 #include<stdio.h>
  17. 17 #include<errno.h>
  18. 18 #include<netinet/in.h>
  19. 19 #include<arpa/inet.h>
  20. 20 #define ERR_EXIT(m)\
  21. 21 do\
  22. 22 {\
  23. 23 perror(m);\
  24. 24 exit(EXIT_FAILURE);\
  25. 25 }while(0)
  26. 26 struct packet
  27. 27 {
  28. 28 int len;//包头
  29. 29 char buf[1024];//包体
  30. 30 };
  31. 31 //接收确切数目的读操作
  32. 32 ssize_t readn(int fd,void *buf,size_t count)
  33. 33 {
  34. 34 size_t nleft=count;
  35. 35 ssize_t nread;
  36. 36 char *bufp=(char*)buf;
  37. 37 //剩余字节数大于0就循环
  38. 38 while(nleft>0)
  39. 39 {
  40. 40 if((nread=read(fd,bufp,nleft))<0)
  41. 41 {
  42. 42 if(errno==EINTR)
  43. 43 continue;
  44. 44 else
  45. 45 return -1;
  46. 46 }
  47. 47 //对等方关闭了
  48. 48 else if(nread==0)
  49. 49 return (count-nleft);//已经读取的字节数
  50. 50 bufp+=nread;
  51. 51 nleft-=nread;
  52. 52 }
  53. 53 return count;
  54. 54 }
  55. 55 //发送确切数目的写操作
  56. 56 ssize_t writen(int fd, const void *buf, size_t count)
  57. 57 {
  58. 58 size_t nleft=count;
  59. 59 ssize_t nwritten;
  60. 60 char *bufp=(char*)buf;
  61. 61 while(nleft>0)
  62. 62 {
  63. 63 if((nwritten=write(fd,bufp,nleft))<=0)
  64. 64 {
  65. 65 if(errno==EINTR)
  66. 66 continue;
  67. 67 return -1;
  68. 68 }else if(nwritten==0)//好像什么都没发生
  69. 69 continue;
  70. 70 bufp+=nwritten;
  71. 71 nleft-=nwritten;
  72. 72 }
  73. 73 return count;
  74. 74
  75. 75 }
  76. 76 //服务器回射。
  77. 77 void do_service(int conn)
  78. 78 {
  79. 79 struct packet recvbuf;
  80. 80 int n;
  81. 81 while(1)
  82. 82 {
  83. 83 memset(&recvbuf,0,sizeof(recvbuf));
  84. 84 //使用readn之后客户端发送的数据不足n会阻塞
  85. 85 //在客户端程序中确定消息的边界,发送定长包
  86. 86 int ret=readn(conn,&recvbuf.len,4);
  87. 87 //客户端关闭
  88. 88 if(ret==-1)
  89. 89 ERR_EXIT("read error");
  90. 90 else if(ret<4)//中途中断了。
  91. 91 {
  92. 92 printf("client close\n");
  93. 93 break;//不用继续循环等待客户端数据
  94. 94 }
  95. 95 //接收包体
  96. 96 n=ntohl(recvbuf.len);//包体长度
  97. 97 ret=readn(conn,recvbuf.buf,n);
  98. 98 if(ret==-1)
  99. 99 ERR_EXIT("read error");
  100. 100 else if(ret<n)//接收到的字节数不足,对端中途关闭
  101. 101 {
  102. 102 printf("client close\n");
  103. 103 break;//不用继续循环等待客户端数据
  104. 104 }
  105. 105 fputs(recvbuf.buf,stdout);
  106. 106 writen(conn,&recvbuf,4+n);
  107. 107 }
  108. 108 }
  109. 109 int main(void)
  110. 110 {
  111. 111 int listenfd;
  112. 112 if((listenfd=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0)
  113. 113 ERR_EXIT("socket error");
  114. 114 //if((listenfd=socket(PF_INET,SOCK_STREAM,0))<0)
  115. 115
  116. 116
  117. 117 //本地协议地址赋给一个套接字
  118. 118 struct sockaddr_in servaddr;
  119. 119 memset(&servaddr,0,sizeof(servaddr));
  120. 120 servaddr.sin_family=AF_INET;
  121. 121 servaddr.sin_port=htons(5188);
  122. 122 servaddr.sin_addr.s_addr=htonl(INADDR_ANY);//表示本机地址
  123. 123 //servaddr.sin_addr.s_addr=inet_addr("127.0.0.1");
  124. 124 //inet_aton("127.0.0.1",&servaddr.sin_addr);
  125. 125
  126. 126 //开启地址重复使用,关闭服务器再打开不用等待TIME_WAIT
  127. 127 int on=1;
  128. 128 if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0)
  129. 129 ERR_EXIT("setsockopt error");
  130. 130 //绑定本地套接字
  131. 131 if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
  132. 132 ERR_EXIT("bind error");
  133. 133 if(listen(listenfd,SOMAXCONN)<0)//设置监听套接字(被动套接字)
  134. 134 ERR_EXIT("listen error");
  135. 135
  136. 136 struct sockaddr_in peeraddr;//对方套接字地址
  137. 137 socklen_t peerlen=sizeof(peeraddr);
  138. 138 int conn;//已连接套接字(主动套接字)
  139. 139 pid_t pid;
  140. 140 while(1){
  141. 141 if((conn=accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen))<0)
  142. 142 ERR_EXIT("accept error");
  143. 143 //连接好之后就构成连接,端口是客户端的。peeraddr是对端
  144. 144 printf("ip=%s port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));
  145. 145 pid=fork();
  146. 146 if(pid==-1)
  147. 147 ERR_EXIT("fork");
  148. 148 if(pid==0){
  149. 149 close(listenfd);
  150. 150 do_service(conn);
  151. 151 //某个客户端关闭,结束该子进程,否则子进程也去接受连接
  152. 152 exit(EXIT_SUCCESS);
  153. 153 }else close(conn);
  154. 154 }
  155. 155 return 0;
  156. 156 }

TCP粘包问题的解决方案01——自定义包体的更多相关文章

  1. 关于iOS和android自定义包的名字

    自定义包名的使用,android的包名和ios的包名都是你的自定义包名!如下以新浪微博SDK自定义包名示例:(官方没的,自己踩过坑,方便后来人吧) 相关技术文档:http://www.apicloud ...

  2. UNIX网络编程——tcp流协议产生的粘包问题和解决方案

    我们在前面曾经说过,发送端可以是一K一K地发送数据,而接收端的应用程序可以两K两K地提走数据,当然也有可能一次提走3K或6K数据,或者一次只提走几个字节的数据,也就是说,应用程序所看到的数据是一个整体 ...

  3. python 全栈开发,Day35(TCP协议 粘包现象 和解决方案)

    一.TCP协议 粘包现象 和解决方案 黏包现象让我们基于tcp先制作一个远程执行命令的程序(命令ls -l ; lllllll ; pwd)执行远程命令的模块 需要用到模块subprocess sub ...

  4. tcp流协议产生的粘包问题和解决方案

    我们在前面曾经说过,发送端可以是一K一K地发送数据,而接收端的应用程序可以两K两K地提走数据,当然也有可能一次提走3K或6K数据,或者一次只提走几个字节的数据,也就是说,应用程序所看到的数据是一个整体 ...

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

    Netty是目前业界最流行的NIO框架之一,它的健壮性.高性能.可定制和可扩展性在同类框架中都是首屈一指.它已经得到了成百上千的商业项目的验证,例如Hadoop的RPC框架Avro就使用了Netty作 ...

  6. 《精通并发与Netty》学习笔记(14 - 解决TCP粘包拆包(二)Netty自定义协议解决粘包拆包)

    一.Netty粘包和拆包解决方案 Netty提供了多个解码器,可以进行分包的操作,分别是: * LineBasedFrameDecoder (换行)   LineBasedFrameDecoder是回 ...

  7. 查漏补缺:socket编程:TCP粘包问题和常用解决方案(上)

    1.TCP粘包问题的产生(发送端) 由于TCP协议是基于字节流并且无边界的传输协议,因此很容易产生粘包问题.TCP的粘包可能发生在发送端,也可能发生在接收端.发送端的粘包是TCP协议本身引起的,TCP ...

  8. TCP 粘包 - 拆包问题及解决方案

    目录 TCP粘包拆包问题 什么是粘包 - 拆包问题 为什么存在粘包 - 拆包问题 粘包 - 拆包 演示 粘包 - 拆包 解决方案 方式一: 固定缓冲区大小 方式二: 封装请求协议 方式三: 特殊字符结 ...

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

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

随机推荐

  1. 【C语言入门】"为什么这个又错了啊"来自编程初学者常见错误合辑!

    C语言的最大特点是:功能强,使用方便灵活. C编译的程序对语法检查并不象其它高级语言那么严格,这就给编程人员留下"灵活的 余地",但还是由于这个灵活给程序的调试带来了许多不便,尤其 ...

  2. 洛谷 CF1012C Hills(动态规划)

    题目大意: 有几座山,如果一座山左右两边的山比它矮,那么可以在这个山上建房子,你有一台挖掘机,每天可以挖一座山一米,问你需要花多少代价可以分别盖1.2.3--座房子.(给出山的数量,以及每座山的高度) ...

  3. linux(centos8): 临时关闭/永久关闭交换分区swap?

    一,为什么要关闭swap? 1,swap的用途? swap 分区就是交换分区,(windows平台叫虚拟内存) 在物理内存不够用时, 操作系统会从物理内存中把部分暂时不被使用的数据转移到交换分区, 从 ...

  4. Linux关机时执行指定脚本

    要实现在Linux关机时执行某个脚本的具体思路 1.在文件夹 /etc/init.d/ 下创建关机时需要执行的脚本 file_name; 2.分别在文件夹 /etc/rc0.d/ 和 /etc/rc6 ...

  5. 边界层吞吸技术(BLI)

    气流在机体表面前进时,由于受到摩擦,其速度会不断降低,从而会产生湍流甚至气流分离,而流动分离又会造成大量紊流.涡,使升力大量损失,同时也会造成阻力急剧增加.边界层吞吸技术就是一种对附面层气流" ...

  6. Linux操作系统的介绍和安装教程(Centos6.4)

    路漫漫其修远兮,吾将上下而求 Linux的简单介绍 Linux最初是由芬兰赫尔辛基大学学生Linus Torvalds开发的,由于自己不满意教学中使用的MINIX操作系统, 所以在1990年底由于个人 ...

  7. drf ( 学习第四部 )

    目录 DRF框架中常用的组件 分页Pagination 异常处理Exceptions 自动生成接口文档 安装依赖 设置接口文档访问路径 访问接口文档网页 Admin 列表页配置 详情页配置 Xadmi ...

  8. C2. Power Transmission (Hard Edition) 解析(思維、幾何)

    Codeforce 1163 C2. Power Transmission (Hard Edition) 解析(思維.幾何) 今天我們來看看CF1163C2 題目連結 題目 給一堆點,每兩個點會造成一 ...

  9. Python ( 高级 第二部)

    目录 模块和包 面向对象 部分一: 面向对象程序开发 面向对象封装: 对象的相关操作 面向对象封装: 类的相关操作 实例化的对象/ 定义的类删除公有成员属性和公有成员方法 部分二: 单继承 多继承 菱 ...

  10. 4G DTU采用的4G通信模块介绍

      4g通信模块一种基于4G网络进行数据传输的工业级通讯终端,其主要作用是将采集到的传感器数据.仪表数据,传输至服务器/上位机.监控中心.众山研发生产的4g无线通讯设备--4G DTU是一款物联网数据 ...