粘包问题:应用层要发送数据,需要调用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. linux centos 02

    1.PS1变量,命令提示符的修改 PS1="[\u@\h \W]\$" \u  代表 用户 @   占位符 \h  主机名 \W   工作路径的最后一位 \t  \w   工作路径 ...

  2. Spring Aop 详解一

    Aop 是一个编程思想,最初是一个理论,最后落地成了很多的技术实现. 我们写一个系统,都希望尽量少写点儿重复的东西.而很多时候呢,又不得不写一些重复的东西.比如访问某些方法的权限,执行某些方法性能的日 ...

  3. goland 注册

    注意:本教程已过期  请使用其他人教程激活最新版   https://www.789zhao.com/blog/JC08EIFBS9TM.html https://shimo.im/docs/dKYC ...

  4. centos8使用timedatectl管理时间

    一,centos8中默认使用chronyd来做时间服务 1,查看chronyd服务的状态 [root@blog ~]# systemctl status chronyd ● chronyd.servi ...

  5. CentOS 6编译安装Redis

    [root@localhost ~]# vim /etc/sysconfig/iptables # 添加如下:-A INPUT -m state –state NEW -m tcp -p tcp –d ...

  6. Docker 也是本地开发的一神器:部署单机版 Pulsar 和集群架构 Redis

    原文链接:Docker 也是本地开发的一神器:部署单机版 Pulsar 和集群架构 Redis 一.前言: 现在互联网的技术架构中,不断出现各种各样的中间件,例如 MQ.Redis.Zookeeper ...

  7. Promise 配合 axios 使用

    Promise是一个构造函数,自己身上有all.reject.resolve这几个眼熟的方法,原型上有then.catch等同样很眼熟的方法 很细致的Promise使用详解 自己脑补 vue 工程化的 ...

  8. Foundation 用于开发响应

    Foundation 用于开发响应式的 HTML, CSS and JavaScript 框架. Foundation 是一个易用.强大而且灵活的框架,用于构建基于任何设备上的 Web 应用. Fou ...

  9. confluence 4.2 升级至 6.10.x 记录

    confluence 4.2 升级至 6.10.x 记录 首先将线上环境中的 confluence 安装目录.数据目录以及数据库进行备份,相关信息如下: 安装目录:/opt/atlassian/con ...

  10. 应用LORAWAN技术的好处是什么

    LoRaWAN现在一种非常流行的LPWA通信标准,在ISM(工业.科学.医疗)频段使用未经许可的无线电频谱,频率约为900MHz到430MHz(世界各地的标准各不相同). 物联网连接环境除了智能家庭联 ...