socket编程之解决流协议的粘包问题(一 )
2017-08-11 14:32
543 查看
流协议和粘包
而我们要怎么解决这种问题呢?
首先第一种定长包:
第二种在包的尾部加上\r,\n等字符
第三种:包头加上包体长度
代码实现
服务器端:
客户端:
这么说吧,TCP在传输数据的时候,是不区分边界的(数据和数据之间没有边界),因为是基于字节流,所以数据对TCP来说就是一大堆没有结构区别的字节块。那意味着什么?意味着TCP并不能对多个数据的整体的信息进行区分(打个比方:就像是你说一堆话没有标点符号全部连在一起,别人很可能弄错)或者对单个整体信息的错误区分(比如你要发送的整块数据被分成了好几块发送,然后这些数据在传输过程中可能由于网络原因,有的大数据块中被分片后的部分片段到了,可能由于接收端缓冲区满了,开始读取,而它们又没有边界之分,这时候就解释错误了)。那样就会使得我们要传输的信息可能粘连在一起,而被计算机解释错误。
而我们要怎么解决这种问题呢?
1.定长包 2.在包的尾部加上\r,\n等字符(ftp使用这种策略,如果包的内容中也包含\r,\n,这时候需要用转义字符 \ 处理) 3.包头加上包体长度(接收时先接收包头根据包头计算出包体的长度来接收包体) 4.更复杂的应用层协议
首先第一种定长包:
有几种可能: 1.包长小于1024,消息包最后面的那部分都用空白字节补全,凑齐1024字节这个长度,发送。 由于这样的数据包都是定长度,不会出现像上面那样的一次发送多个具有不同数据结构的消息,也就意味这接受的数据包不能无脑地组合在一起被解释,因为单个数据包都是独立的数据结构。 最大缺陷就是它并不能发挥TCP协议的高效性,而且极大地浪费了网络流量,很多无效数据在网络上传输,不推荐使用。
第二种在包的尾部加上\r,\n等字符
我们常用的ftp服务器就是这样的设计方式,在区分包内的\r和\n等字符时候使用转义字符\解决,但是这种限制了只能做某种特殊的服务,因为我们必须保证结束符的唯一性,在通常的数据传输中,由于用户的输入是不能限定的,什么样的字符都有,所以不能随便应用。
第三种:包头加上包体长度
这是我们应用得最多的一种传输的方式,保证了TCP的高效性,而且解决了粘包问题。 下面来模拟一下这个传输方式:其实就是封装一个发送和接受的函数,然后在接受数据的时候先对接受的包头数据分析,求出包头告诉我们的实际数据的长度,再进行二次接受,那就是我们要的数据
代码实现
服务器端:
#include<unistd.h>//通过接收包头来确定长度len #include<sys/types.h> #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h> #include<stdio.h> #include<stdlib.h> #include<errno.h> #include<string.h> #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0) struct packet { int len; char buf[1024];// }; ssize_t readn(int fd,void *buf,size_t count) { //ssize_t=int,size_t=unsigned int //接收count个字节数 size_t nleft=count;//剩余字节数 ssize_t nread;//已经接收字节数 char *bufp=(char *)buf; while(nleft>0) { if((nread=read(fd,bufp,nleft))<0) { if(errno==EINTR)//被中断 continue; return -1; } else if(nread==0)//对等方关闭 return count-nleft;//读到EOF,对方关闭 bufp+=nread; nleft-=nread; } return count; } ssize_t writen(int fd,void *buf,size_t count) { size_t nleft=count;//剩余发送字节数 ssize_t nwritten;//已经发送字节数 char *bufp=(char *)buf; while(nleft>0)//一般而言,write缓冲区大于发送数据缓冲区,不阻塞 { if((nwritten=write(fd,bufp,nleft))<0) { if(errno==EINTR)//被中断 continue; return -1; } else if(nwritten==0)//对等方关闭 continue;//读到EOF,对方关闭 bufp+=nwritten; nleft-=nwritten; } return count; } void do_service(int conn) { struct packet recvbuf; int n; while(1){ memset(&recvbuf,0,sizeof(recvbuf));//初始化recvbuf int ret=readn(conn,&recvbuf.len,4);//函数从打开的文件,设备中读取数据,返回读取的字节数。 if(ret==-1)//四个字节先接收到len { ERR_EXIT("read"); } else if(ret<4) { printf("client_close\n"); break; } n=ntohl(recvbuf.len);//转换成主机字节序 ret=readn(conn,recvbuf.buf,n);//接收n个字节到recvbuf if(ret==-1) { ERR_EXIT("read"); } else if(ret<n) { printf("client_close\n"); break; } fputs(recvbuf.buf,stdout);//输出 writen(conn,&recvbuf,4+n);//buf中数据被复制到了TCP发送缓冲区 } } int main(void) { int listenfd; /*if((listenfd=socket(AF_INET,SOCK_STEAM,IPPOTO_TCP))<0) */ if((listenfd=socket(AF_INET,SOCK_STREAM,0))<0) ERR_EXIT("socket"); //IPV4地址结构 struct sockaddr_in servaddr; memset(&servaddr,0,sizeof(servaddr)); servaddr.sin_family=AF_INET;//地址家族 servaddr.sin_port=htons(5188);//端口,主机转网络 /*servaddr.sin_addr.s_addr=inet_addr("127.0.0.1");*/ /*inet_aton("127.0.0.1",&servaddr.sin_addr);*/ servaddr.sin_addr.s_addr=htonl(INADDR_ANY); int on=1; if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0) ERR_EXIT("setsockopt"); if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0) ERR_EXIT("bind"); if(listen(listenfd,SOMAXCONN)<0) ERR_EXIT("listen"); struct sockaddr_in peeraddr; socklen_t peerlen =sizeof(peeraddr);//typedef int socklen_t int conn;//已连接套接字 pid_t pid; while(1) { if((conn=accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen))<0) ERR_EXIT("accept"); printf("ip=%s port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port)); pid=fork(); if(pid==-1) ERR_EXIT("fork"); if(pid==0) { close(listenfd); do_service(conn); exit(EXIT_SUCCESS);//将子进程退出,要不它会fork() } else close(conn); } return 0; }
客户端:
#include<unistd.h> #include<sys/types.h> #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h> #include<stdio.h> #include<stdlib.h> #include<errno.h> #include<string.h> #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0) struct packet { int len; char buf[1024];// }; ssize_t readn(int fd,void *buf,size_t count) { //ssize_t=int,size_t=unsigned int //接收count个字节数 size_t nleft=count;//剩余字节数 ssize_t nread;//已经接收字节数 char *bufp=(char *)buf; while(nleft>0) { if((nread=read(fd,bufp,nleft))<0) { if(errno==EINTR)//被中断 continue; return -1; } else if(nread==0)//对等方关闭 return count-nleft;//读到EOF,对方关闭 bufp+=nread; nleft-=nread; } return count; } ssize_t writen(int fd,void *buf,size_t count) { size_t nleft=count;//剩余发送字节数 ssize_t nwritten;//已经发送字节数 char *bufp=(char *)buf; while(nleft>0)//一般而言,write缓冲区大于发送数据缓冲区,不阻塞 { if((nwritten=write(fd,bufp,nleft))<0) { if(errno==EINTR)//被中断 continue; return -1; } else if(nwritten==0)//对等方关闭 continue;//读到EOF,对方关闭 bufp+=nwritten; nleft-=nwritten; } return count; } //发送定长包 int main() { int sock; /*if((listenfd=socket(AF_INET,SOCK_STEAM,IPPOTO_TCP))<0) */ if((sock=socket(AF_INET,SOCK_STREAM,0))<0) ERR_EXIT("socket"); //IPV4地址结构 struct sockaddr_in servaddr; memset(&servaddr,0,sizeof(servaddr)); servaddr.sin_family=AF_INET;//地址家族 servaddr.sin_port=htons(5188);//端口,主机转网络 servaddr.sin_addr.s_addr=inet_addr("127.0.0.1"); /*inet_aton("127.0.0.1",&servaddr.sin_addr);*/ if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr))<0) ERR_EXIT("connect"); struct packet sendbuf; struct packet recvbuf; memset(&sendbuf,0,sizeof(sendbuf)); memset(&recvbuf,0,sizeof(recvbuf)); int n; while(fgets(sendbuf.buf,sizeof(sendbuf.buf),stdin) !=NULL){ n=strlen(sendbuf.buf); sendbuf.len=htonl(n); writen(sock,&sendbuf,4+n);//发送,4是头部大小,n是包体长度 int ret=readn(s ac8c ock,&recvbuf.len,4);//函数从打开的文件,设备中读取数据,返回读取的字.先接收4个字节 if(ret==-1) { ERR_EXIT("read"); } else if(ret<4) { printf("client_close\n"); break; } n=ntohl(recvbuf.len);//转换成主机字节序 ret=readn(sock,recvbuf.buf,n);//接收n个字节到recvbuf if(ret==-1) { ERR_EXIT("read"); } else if(ret<n) { printf("client_close\n"); break; } fputs(recvbuf.buf,stdout); memset(&sendbuf,0,sizeof(sendbuf)); memset(&recvbuf,0,sizeof(recvbuf)); } close(sock); return 0; }
相关文章推荐
- socket编程之解决流协议的粘包问题(二)
- Linux编程之socket:tcp流协议产生的粘包问题及解决方法
- C语言socket编程的分包和粘包的有关问题解决
- socket04---流协议和粘包问题及解决
- linux网络编程之socket(五):tcp流协议产生的粘包问题和解决方案
- linux网络编程之socket(五):tcp流协议产生的粘包问题和解决方案
- Socket开发之通讯协议及处理(解决粘包问题)
- linux网络编程之socket(五):tcp流协议产生的粘包问题和解决方案
- Socket编程实践(5) --TCP粘包问题与解决
- Socket编程实践(5) --TCP粘包问题与解决
- linux网络编程之socket(五):tcp流协议产生的粘包问题和解决方案
- <转载>Socket开发之通讯协议及处理(解决粘包问题)
- linux网络编程之socket(五):tcp流协议产生的粘包问题和解决方案
- Python案例-网络编程-socket-解决ssh消息粘包问题
- Python3之socket编程解决粘包问题
- linux网络编程之socket(五):tcp流协议产生的粘包问题和解决方案
- JAVA Socket编程学习10--解决TCP粘包分包问题
- Tcp编程常见问题及解决方法总结(粘包,拆包)
- loadrunner测试socket协议时状态判断问题解决办法
- 解决AS3 Socket编程中最令人头疼的问题