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
原创粉丝点击