socket编程之解决流协议的粘包问题(二)
来源:互联网 发布:linux合并两个文件夹 编辑:程序博客网 时间:2024/05/29 02:04
常见的应用层协议都是带有可变长字段的,字段之间的分隔符用换行'\n'的比用'\0'的更常见,如HTTP协议。可变长字段的协议用readn来读就很不方便了,为此我们实现一个类似于fgets的readline函数。在readline函数中,我们先用recv_peek”偷窥“ 一下现在缓冲区有多少个字符。然后查看是否存在换行符'\n',如果存在,则使用readn连通换行符一起读取。如果不存在,则也先将前面的数据读取进bufp, 且移动bufp的位置,回到while循环开头。再从当前bufp位置窥看,注意,当我们调用readn读取数据时,那部分缓冲区是会被清空的。因为readn调用了read函数,还需注意一点是,如果第二次才读取到了'\n'。则先用count保存了第一次读取的字符个数,然后返回的ret需加上原先的数据大小。使用 readline函数也可以认为是解决粘包问题的一个办法,即以'\n'为结尾当作一条消息
代码:
#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)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;}ssize_t recv_peek(int sockfd,void *buf,size_t len){ while(1){ int ret=recv(sockfd,buf,len,MSG_PEEK); if(ret==-1&&errno==EINTR)//操作被信号中断,recv认为链接正常,继续执行。 continue; return ret;//返回读取的字节数 }}ssize_t readline(int sockfd,void *buf,size_t maxline){ int ret; int nread; char *bufp=buf; int nleft=maxline;//读取遇到\n返回,不会超过maxline while(1){ ret=recv_peek(sockfd,bufp,nleft); if(ret<=0) return ret; nread=ret; int i;//接下来判断接收的缓冲区是否有\n for(i=0;i<nread;i++){ if(bufp[i]=='\n'){ ret=readn(sockfd,bufp,i+1); if(ret!=i+1) exit(EXIT_FAILURE);//偷窥方法 return ret; } } if(nread>nleft){ //偷窥到的数据不能大于maxline exit(EXIT_FAILURE); } nleft-=nread;//剩余字节数 ret=readn(sockfd,bufp,nread);//将nread数据从缓冲区移除 if(ret!=nread) exit(EXIT_FAILURE); bufp+=nread;//继续偷窥 } return 1;}void do_service(int conn){ char recvbuf[1024]; while(1){ memset(recvbuf,0,sizeof(recvbuf));//初始化recvbuf int ret=readline(conn,recvbuf,1024);//一行一行接收数据。 if(ret==-1) { ERR_EXIT("readine"); } if(ret==0) { printf("client close\n"); break; } fputs(recvbuf,stdout);//读取一行数据,就将其输出到标准输出 writen(conn,recvbuf,strlen(recvbuf));//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)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;}ssize_t recv_peek(int sockfd,void *buf,size_t len){ while(1){ int ret=recv(sockfd,buf,len,MSG_PEEK); if(ret==-1&&errno==EINTR)//操作被信号中断,recv认为链接正常,继续 continue; return ret;//返回读取的字节数 }}ssize_t readline(int sockfd,void *buf,size_t maxline){ int ret; int nread; char *bufp=buf; int nleft=maxline;//读取遇到\n返回,不会超过maxline while(1){ ret=recv_peek(sockfd,bufp,nleft); if(ret<=0) return ret; nread=ret; int i;//接下来判断接收的缓冲区是否有\n for(i=0;i<nread;i++){ if(bufp[i]=='\n'){ ret=readn(sockfd,bufp,i+1); if(ret!=i+1) exit(EXIT_FAILURE);//偷窥方法 return ret; } } if(nread>nleft){ //偷窥到的数据不能大于maxline exit(EXIT_FAILURE); } nleft-=nread;//剩余字节数 ret=readn(sockfd,bufp,nread);//将nread数据从缓冲区移除 if(ret!=nread) exit(EXIT_FAILURE); bufp+=nread;//继续偷窥 } return 1; }//发送定长包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"); //getsockname char sendbuf[1024]={0}; char recvbuf[1024]={0}; while(fgets(sendbuf,sizeof(sendbuf),stdin) !=NULL){//从文件读取一行,送到缓冲区 writen(sock,&sendbuf,strlen(sendbuf));//将接收到的数据发送出去 int ret=readline(sock,recvbuf,sizeof(recvbuf));//函数从打开的文件,设备中读取数据 if(ret==-1) { ERR_EXIT("readline"); } else if(ret==0) { printf("client_close\n"); break; } fputs(recvbuf,stdout);//发送数据到文件 memset(sendbuf,0,sizeof(sendbuf)); memset(recvbuf,0,sizeof(recvbuf)); } close(sock); return 0;}
阅读全文
0 0
- socket编程之解决流协议的粘包问题(二)
- socket编程之解决流协议的粘包问题(一 )
- Linux编程之socket:tcp流协议产生的粘包问题及解决方法
- linux网络编程之socket(五):tcp流协议产生的粘包问题和解决方案
- linux网络编程之socket(五):tcp流协议产生的粘包问题和解决方案
- linux网络编程之socket(五):tcp流协议产生的粘包问题和解决方案
- linux网络编程之socket(五):tcp流协议产生的粘包问题和解决方案
- linux网络编程之socket(五):tcp流协议产生的粘包问题和解决方案
- linux网络编程之socket(五):tcp流协议产生的粘包问题和解决方案
- linux网络编程之socket(五):tcp流协议产生的粘包问题和解决方案
- linux网络编程之socket(五):tcp流协议产生的粘包问题和解决方案
- Python3之socket编程解决粘包问题
- Socket开发之通讯协议及处理(解决粘包问题)
- Socket编程实践(5) --TCP粘包问题与解决
- Socket编程实践(5) --TCP粘包问题与解决
- Socket编程实践(5) --TCP粘包问题与解决
- 网络协议(二)Socket编程之TCP
- 【网络】(二)流协议粘包问题
- js添加、移除、移动、复制、创建和查找节点
- 做表要点
- <h1>测试博客</h1><script type="text/javascript">alert(456);</script>
- Shader 遮罩纹理贴图参考代码笔记
- Dubbo架构设计详解
- socket编程之解决流协议的粘包问题(二)
- jQuery 监听修改文本框事件
- windows开机后一键启动应用程序
- C#多线程编程之线程池的使用 (ThreadPool)
- Failed to load JavaHL Library
- A
- container和container-fluid之间的区别
- [Leetcode] 344. Reverse String 解题报告
- Spring 之AOP AspectJ切入点语法详解