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 ...
随机推荐
- c# 常用帮助类
C#常用帮助类 因为小土现在还是处于小白阶段,所以自己的知识技术还达不到要求,但是小土在网上找到一个大神的,等以后小土技术有了一定提升以后,在走自己的路,啥也不说了上货. 地址 :https://gi ...
- python 读取文件时报错UnicodeDecodeError
python 读取文件时报错UnicodeDecodeError: 'gbk' codec can't decode byte 0x80 in position 205: illegal multib ...
- CentOS 7操作系统目录结构介绍
CentOS 7操作系统目录结构介绍 操作系统存在着大量的数据文件信息,相应文件信息会存在于系统相应目录中,为了更好的管理数据信息,会将系统进行一些目录规划,不同目录存放不同的资源. 根下目录结构说明 ...
- Navicat Premium_11.2.7简体中文版 破解版本 windows版本 失效
亲测可用 自己一直在用的 https://pan.baidu.com/s/1VVKKQoIKVB0BgNXBK4YTrQ
- 壹佰文章最全总结| 《关于ASP.NETCore的分享之路》
学习路线图 (关于学习ASP.NET Core需要了解和掌握的知识点图) 一言不合就来图,各位博客园小伙伴大家好,感觉好久没有写文章了,自从春节开始,中间经历种种,慢慢的就开始微信公众号发文了,原因有 ...
- 一份超全的Python学习资料汇总
一.学习Python必备技能图谱二.0基础如何系统学习Python?一.Python的普及入门1.1 Python入门学习须知和书本配套学习建议1.2 Python简史1.3 Python的市场需求及 ...
- Eureka整合sidecar异构调用
本次使用nodejs脚本生成的异构程序测试: node-server.js var http = require('http'); var url = require('url'); var path ...
- JWT实现过程及应用
jwt实现过程 # 用户登录,返回给客户端token(服务端不保存),用户带着token,服务端拿到token再校验; 1,提交用户名和密码给服务端,如果登陆成功,jwt会创建一个token,并返回; ...
- 【Luogu】 P6274 [eJOI2017]六 题解
首先,题目说了最多\(6\)个质因数. 如此小的数据范围,不是状压还是啥? 然后,我们可以发现一个性质:只要两个因数有相同的质因数(不管次数是多少),两者就不互质. 这启示我们用一个二进制数来表示一类 ...
- Android NurReaderView 阅读器 (字符串-.txt文件)
有些地方还没配置好.2/3天后在更新.... 功能 支持字符串和<.txt>文件 文字自动分各个页面 支持从右到左-(从右边开始的语言.比如维吾尔语哈扎克语...外国的阿拉伯语等) 支持自 ...