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 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——自定义包体的更多相关文章
- 关于iOS和android自定义包的名字
自定义包名的使用,android的包名和ios的包名都是你的自定义包名!如下以新浪微博SDK自定义包名示例:(官方没的,自己踩过坑,方便后来人吧) 相关技术文档:http://www.apicloud ...
- UNIX网络编程——tcp流协议产生的粘包问题和解决方案
我们在前面曾经说过,发送端可以是一K一K地发送数据,而接收端的应用程序可以两K两K地提走数据,当然也有可能一次提走3K或6K数据,或者一次只提走几个字节的数据,也就是说,应用程序所看到的数据是一个整体 ...
- python 全栈开发,Day35(TCP协议 粘包现象 和解决方案)
一.TCP协议 粘包现象 和解决方案 黏包现象让我们基于tcp先制作一个远程执行命令的程序(命令ls -l ; lllllll ; pwd)执行远程命令的模块 需要用到模块subprocess sub ...
- tcp流协议产生的粘包问题和解决方案
我们在前面曾经说过,发送端可以是一K一K地发送数据,而接收端的应用程序可以两K两K地提走数据,当然也有可能一次提走3K或6K数据,或者一次只提走几个字节的数据,也就是说,应用程序所看到的数据是一个整体 ...
- Netty4实战 - TCP粘包&拆包解决方案
Netty是目前业界最流行的NIO框架之一,它的健壮性.高性能.可定制和可扩展性在同类框架中都是首屈一指.它已经得到了成百上千的商业项目的验证,例如Hadoop的RPC框架Avro就使用了Netty作 ...
- 《精通并发与Netty》学习笔记(14 - 解决TCP粘包拆包(二)Netty自定义协议解决粘包拆包)
一.Netty粘包和拆包解决方案 Netty提供了多个解码器,可以进行分包的操作,分别是: * LineBasedFrameDecoder (换行) LineBasedFrameDecoder是回 ...
- 查漏补缺:socket编程:TCP粘包问题和常用解决方案(上)
1.TCP粘包问题的产生(发送端) 由于TCP协议是基于字节流并且无边界的传输协议,因此很容易产生粘包问题.TCP的粘包可能发生在发送端,也可能发生在接收端.发送端的粘包是TCP协议本身引起的,TCP ...
- TCP 粘包 - 拆包问题及解决方案
目录 TCP粘包拆包问题 什么是粘包 - 拆包问题 为什么存在粘包 - 拆包问题 粘包 - 拆包 演示 粘包 - 拆包 解决方案 方式一: 固定缓冲区大小 方式二: 封装请求协议 方式三: 特殊字符结 ...
- Socket编程(4)TCP粘包问题及解决方案
① TCP是个流协议,它存在粘包问题 TCP是一个基于字节流的传输服务,"流"意味着TCP所传输的数据是没有边界的.这不同于UDP提供基于消息的传输服务,其传输的数据是有边界的.T ...
随机推荐
- 多测师讲解自动化--rf断言(下)--_高级讲师肖sir
1.Page Should Contain 1.1存在页面上的内容 1.2 不存在页面上的内容, 运行抛异常 1.3 Page Should Not Contain 1.4 1.5 输入页面元素,判断 ...
- 关于 Promise 的一些简单理解
一.ES6 中的 Promise 1.JS 如何解决 异步问题? (1)什么是 同步.异步? 同步指的是 需要等待 前一个处理 完成,才会进行 下一个处理. 异步指的是 不需要等待 前一个处理 完成, ...
- java基础知识总结(续写)
1.两个容易搞混的C盘文件夹 文件名 描述 Progrm Files 默认存储的64位软件 Progrm Files(x86) 默认存储32位软件 2.常用基础DOS命令(Windows+R打开命令) ...
- 如何设计一个牛逼的API接口
在日常开发中,总会接触到各种接口.前后端数据传输接口,第三方业务平台接口.一个平台的前后端数据传输接口一般都会在内网环境下通信,而且会使用安全框架,所以安全性可以得到很好的保护.这篇文章重点讨论一下提 ...
- centos8平台使用slabtop监控slab内存的状态
一,slabtop 所属的包: [root@yjweb ~]# whereis slabtop slabtop: /usr/bin/slabtop /usr/share/man/man1/slabto ...
- js 如何获取浏览器的高度?
<SCRIPT LANGUAGE="JavaScript"><!--var s = ""; s += " 网页可见区域宽:" ...
- django—路由相关
django不同版本的路由配置 django 2之前,配置urlpatterns使用的是url方法 django 2之后,配置urlpatterns使用的是path方法 path与url的区别: ur ...
- 三种方式获取SSMS连接密码
内网渗透是有的时候会遇到对方SSMS没断开连接正连着别的机器的mssql此时有两种方法可以获取sa密码 当密码强度较弱时可以使用第一只方式,第一种方式解不开的情况下可以使用后面二种方式 1.直接查询s ...
- sangforEDR 任意命令执行
EDR 使用范围 由于只有POC 没有详细细节.暂时不知道具体细节. 部分EDR 已经完成升级,不存在该漏洞. POC https://XXX:8081/tool/log/c.php?strip_sl ...
- 循序渐进VUE+Element 前端应用开发(22)--- 简化main.js处理代码,抽取过滤器、全局界面函数、组件注册等处理逻辑到不同的文件中
在我们开发代码的时候,一般都喜欢进行一定程度的重构,以达到简化代码.关注点分离.提高代码可读性等等方面的考虑,本篇随笔介绍在VUE+Element 前端应用开发过程中,实现简化main.js处理代码, ...
