粘包问题:应用层要发送数据,需要调用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 packet
21 {
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 else
39 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 }

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. 多测师讲解自动化--rf断言(下)--_高级讲师肖sir

    1.Page Should Contain 1.1存在页面上的内容 1.2 不存在页面上的内容, 运行抛异常 1.3 Page Should Not Contain 1.4 1.5 输入页面元素,判断 ...

  2. 关于 Promise 的一些简单理解

    一.ES6 中的 Promise 1.JS 如何解决 异步问题? (1)什么是 同步.异步? 同步指的是 需要等待 前一个处理 完成,才会进行 下一个处理. 异步指的是 不需要等待 前一个处理 完成, ...

  3. java基础知识总结(续写)

    1.两个容易搞混的C盘文件夹 文件名 描述 Progrm Files 默认存储的64位软件 Progrm Files(x86) 默认存储32位软件 2.常用基础DOS命令(Windows+R打开命令) ...

  4. 如何设计一个牛逼的API接口

    在日常开发中,总会接触到各种接口.前后端数据传输接口,第三方业务平台接口.一个平台的前后端数据传输接口一般都会在内网环境下通信,而且会使用安全框架,所以安全性可以得到很好的保护.这篇文章重点讨论一下提 ...

  5. centos8平台使用slabtop监控slab内存的状态

    一,slabtop 所属的包: [root@yjweb ~]# whereis slabtop slabtop: /usr/bin/slabtop /usr/share/man/man1/slabto ...

  6. js 如何获取浏览器的高度?

    <SCRIPT LANGUAGE="JavaScript"><!--var s = ""; s += " 网页可见区域宽:" ...

  7. django—路由相关

    django不同版本的路由配置 django 2之前,配置urlpatterns使用的是url方法 django 2之后,配置urlpatterns使用的是path方法 path与url的区别: ur ...

  8. 三种方式获取SSMS连接密码

    内网渗透是有的时候会遇到对方SSMS没断开连接正连着别的机器的mssql此时有两种方法可以获取sa密码 当密码强度较弱时可以使用第一只方式,第一种方式解不开的情况下可以使用后面二种方式 1.直接查询s ...

  9. sangforEDR 任意命令执行

    EDR 使用范围 由于只有POC 没有详细细节.暂时不知道具体细节. 部分EDR 已经完成升级,不存在该漏洞. POC https://XXX:8081/tool/log/c.php?strip_sl ...

  10. 循序渐进VUE+Element 前端应用开发(22)--- 简化main.js处理代码,抽取过滤器、全局界面函数、组件注册等处理逻辑到不同的文件中

    在我们开发代码的时候,一般都喜欢进行一定程度的重构,以达到简化代码.关注点分离.提高代码可读性等等方面的考虑,本篇随笔介绍在VUE+Element 前端应用开发过程中,实现简化main.js处理代码, ...