zoukankan      html  css  js  c++  java
  • select模型(一 改进客户端)

    一、改程序使用select来改进客户端对标准输入和套接字输入的处理,否则关闭服务器之后
    循环中的内容都要被gets阻塞。原程序中https://www.cnblogs.com/wsw-seu/p/8413290.html,若服务器端先关闭发送FIN,客户端处于CLOSE WAIT状态,服务端到FIN_WAIT2。由于程序阻塞在fgets,因此无法到readline,也就无法break循环从而调用close。所有套接口状态不能再往前推进了。

    二、select管理多个I/O,一旦其中一个I/O或者多个I/O检测出我们所感兴趣的事件,select函数返回
    返回值是检测到的事件个数。并且可以返回哪些I/O发生了事件,进而遍历这些事件去处理。


    三、int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
    参数1、读、写、异常集合中的文件描述符的最大值+1(例如读集合放入描述符3,4,5 ,写集合放入7,9,异常集合填空,那么nfds为10
    参数2、可读集合(是一个输入输出参数,例如我们对3,4,5描述符的读感兴趣,当3,5发生读事件,select函数改变集合内容为3,5返回)
    参数5、超时时间
    后四个参数是输入输出参数,参数2,3,4返回发生的I/O事件。参数5返回剩余时间


    四、修改描述符集的宏
    void FD_CLR(int fd, fd_set *set);  //将fd描述符从集合set中移除
    int FD_ISSET(int fd, fd_set *set);
    void FD_SET(int fd, fd_set *set);  //将fd描述符添加到集合set中
    void FD_ZERO(fd_set *set);

    下面利用select来改造之前的客户端服务器程序,防止服务器端关闭,客户端还在等stdin输入,而不能退出。

    客户端程序:

    #include<unistd.h>
    #include<sys/types.h>
    #include<sys/socket.h>
    #include<string.h>
    #include<stdlib.h>
    #include<stdio.h>
    #include<errno.h>
    #include<netinet/in.h>
    #include<arpa/inet.h>
    #include<signal.h>
    #include <sys/time.h>
    
    #define ERR_EXIT(m)
        do
        {
            perror(m);
            exit(EXIT_FAILURE);
        }while(0)
    ssize_t readn(int fd,void *buf,size_t 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;
                else
                    return -1;
            }
            else if(nread==0)
                return (count-nleft);
            bufp+=nread;
            nleft-=nread;
        }
        return count;
    }
    ssize_t writen(int fd, const void *buf, size_t count)
    {
        size_t nleft=count;
        ssize_t nwritten;
        char *bufp=(char*)buf;
        while(nleft>0)
        {
            if((nwritten=write(fd,bufp,nleft))<=0)
            {
                if(errno==EINTR)
                    continue;
                return -1;
            }else if(nwritten==0)
                continue;
            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);//从sockfd读取内容到buf,但不去清空sockfd,偷窥
            if(ret==-1&&errno==EINTR)
                continue;
            return ret;
        }
    }
    //偷窥方案实现readline避免一次读取一个字符
    ssize_t readline(int sockfd,void * buf,size_t maxline)
    {
        int ret;
        int nread;
        size_t nleft=maxline;
        char *bufp=(char*)buf;
        while(1)
        {
            ret=recv_peek(sockfd,bufp,nleft);//不清除sockfd,只是窥看
            if(ret<0)
                return ret;
            else if(ret==0)
                return ret;
            nread=ret;
            int i;
            for(i=0;i<nread;i++)
            {
                if(bufp[i]=='
    ')
                {
                    ret=readn(sockfd,bufp,i+1);//读出sockfd中的一行并且清空
                    if(ret!=i+1)
                        exit(EXIT_FAILURE);
                    return ret;
                }
            }
            if(nread>nleft)
                exit(EXIT_FAILURE);
            nleft-=nread;
            ret=readn(sockfd,bufp,nread);
            if(ret!=nread)
                exit(EXIT_FAILURE);
            bufp+=nread;//移动指针继续窥看
        }
        return -1;
    }
    void echo_cli(int sock)
    {
    /*
        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,1024);
            if(ret==-1)
                ERR_EXIT("readline");
            else if(ret==0)
            {
                printf("service closed
    ");
                break;
            }
            fputs(recvbuf,stdout);
            memset(sendbuf,0,sizeof(sendbuf));
            memset(recvbuf,0,sizeof(recvbuf));
        }
    */
        char sendbuf[1024]={0};
        char recvbuf[1024]={0};    
        fd_set rset;
        FD_ZERO(&rset);//初始化
        int nready;//准备好的个数
        int maxfd;
        int fd=fileno(stdin);//为何不使用STDIN_FILLENO(0),防止标准输入被重定向
        if(fd>sock)
            maxfd=fd;
        else 
            maxfd=sock;
        while(1)
        {
            FD_SET(fd,&rset);//循环中。每次要重新设置rset。
            FD_SET(sock,&rset);
            nready=select(maxfd+1,&rset,NULL,NULL,NULL);
            if(nready==-1)
                ERR_EXIT("select error");
            if(nready==0)
                continue;
            if(FD_ISSET(sock,&rset))
            {
                int ret=readline(sock,recvbuf,sizeof(recvbuf));
                if(ret==-1)
                    ERR_EXIT("readline error");
                else if(ret==0)
                {
                    ERR_EXIT("serve closed");
                    break;
                }
                fputs(recvbuf,stdout);
                memset(recvbuf,0,sizeof(recvbuf));
            }
            if(FD_ISSET(fd,&rset))
            {
                if(fgets(sendbuf,sizeof(sendbuf),stdin)==NULL)
                    break;
                writen(sock,sendbuf,strlen(sendbuf));
                memset(sendbuf,0,sizeof(sendbuf));
            }
        }
        close(sock);//当服务器端关闭,客户端readline读取到FIN退出循环,执行close
        
    }
    void handle_sigpipe(int sig)
    {
        printf("recive a signal=%d
    ",sig);
    
    }
    int main(void)
    {
            signal(SIGPIPE,handle_sigpipe);//捕捉第二次write的SIGPIPE信号,默认终止进程
            int sock;
            if((sock=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0)
                ERR_EXIT("socket error");
        
            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获取客户端本身地址和端口,即为对方accept中的对方套接口
            struct sockaddr_in localaddr;
            socklen_t addrlen=sizeof(localaddr);
            if(getsockname(sock,(struct sockaddr *)&localaddr,&addrlen)<0)
                ERR_EXIT("getsockname error");
            printf("local IP=%s, local port=%d
    ",inet_ntoa(localaddr.sin_addr),ntohs(localaddr.sin_port));    
            //使用getpeername获取对方地址
        echo_cli(sock);//选择一个与服务器通信
        return 0;
    }

    服务器程序不变,先关闭服务器程序,客户端也能正常退出了。(客户端select同时监测套接口和标准输入的读事件)

    /*
    主动关闭服务器端,客户端不会再while(fgets())处阻塞,而是会
    接收到服务器的FIN从而进入TIME_WAIT状态
    
    */
    
    #include<unistd.h>
    #include<sys/types.h>
    #include<sys/socket.h>
    #include<string.h>
    #include<stdlib.h>
    #include<stdio.h>
    #include<errno.h>
    #include<netinet/in.h>
    #include<arpa/inet.h>
    #include<signal.h>
    #include<sys/wait.h>
    #define ERR_EXIT(m)
        do
        {
            perror(m);
            exit(EXIT_FAILURE);
        }while(0)
    ssize_t readn(int fd,void *buf,size_t 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;
                else
                    return -1;
            }
            else if(nread==0)
                return (count-nleft);
            bufp+=nread;
            nleft-=nread;
        }
        return count;
    }
    ssize_t writen(int fd, const void *buf, size_t count)
    {
        size_t nleft=count;
        ssize_t nwritten;
        char *bufp=(char*)buf;
        while(nleft>0)
        {
            if((nwritten=write(fd,bufp,nleft))<=0)
            {
                if(errno==EINTR)
                    continue;
                return -1;
            }else if(nwritten==0)
                continue;
            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);//从sockfd读取内容到buf,但不去清空sockfd,偷窥
            if(ret==-1&&errno==EINTR)
                continue;
            return ret;
        }
    }
    //偷窥方案实现readline避免一次读取一个字符
    ssize_t readline(int sockfd,void * buf,size_t maxline)
    {
        int ret;
        int nread;
        size_t nleft=maxline;
        char *bufp=(char*)buf;
        while(1)
        {
            ret=recv_peek(sockfd,bufp,nleft);//不清除sockfd,只是窥看
            if(ret<0)
                return ret;
            else if(ret==0)
                return ret;
            nread=ret;
            int i;
            for(i=0;i<nread;i++)
            {
                if(bufp[i]=='
    ')
                {
                    ret=readn(sockfd,bufp,i+1);//读出sockfd中的一行并且清空
                    if(ret!=i+1)
                        exit(EXIT_FAILURE);
                    return ret;
                }
            }
            if(nread>nleft)
                exit(EXIT_FAILURE);
            nleft-=nread;
            ret=readn(sockfd,bufp,nread);
            if(ret!=nread)
                exit(EXIT_FAILURE);
            bufp+=nread;//移动指针继续窥看
        }
        return -1;
    }
    void echo_srv(int conn)
    {
            int ret;
            char recvbuf[1024];
            while(1)
            {
                memset(&recvbuf,0,sizeof(recvbuf));
                //使用readn之后客户端发送的数据不足1024会阻塞
                //在客户端程序中确定消息的边界,发送定长包
                ret=readline(conn,recvbuf,1024);                                                               
                //客户端关闭
                if(ret==-1)
                    ERR_EXIT("readline");            
                else if(ret==0)
                {
                    printf("client close
    ");
                    break;//不用继续循环等待客户端数据
                }
                fputs(recvbuf,stdout);
                writen(conn,recvbuf,strlen(recvbuf));
            }
    }
    void handle_sigchld(int sig)
    {
        
        while(waitpid(-1,NULL, WNOHANG)>0)
            ;
            
    }
    int main(void)
    {    
        
        signal(SIGCHLD,handle_sigchld);
        int listenfd;
        if((listenfd=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0)
            ERR_EXIT("socket error");
        //if((listenfd=socket(PF_INET,SOCK_STREAM,0))<0)
        
    
        //本地协议地址赋给一个套接字
        struct sockaddr_in servaddr;
        memset(&servaddr,0,sizeof(servaddr));
        servaddr.sin_family=AF_INET;
        servaddr.sin_port=htons(5188);
        servaddr.sin_addr.s_addr=htonl(INADDR_ANY);//表示本机地址
    
        //开启地址重复使用,关闭服务器再打开不用等待TIME_WAIT
        int on=1;
        if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0)
            ERR_EXIT("setsockopt error");
        //绑定本地套接字
        if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
            ERR_EXIT("bind error");
        if(listen(listenfd,SOMAXCONN)<0)//设置监听套接字(被动套接字)
            ERR_EXIT("listen error");
        
        struct sockaddr_in peeraddr;//对方套接字地址
        socklen_t peerlen=sizeof(peeraddr);
        int conn;//已连接套接字(主动套接字)
        pid_t pid;
        while(1){
            if((conn=accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen))<0)
                ERR_EXIT("accept error");
            //连接好之后就构成连接,端口是客户端的。peeraddr是对端
            printf("ip=%s port=%d
    ",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));
            pid=fork();
            if(pid==-1)
                ERR_EXIT("fork");
            if(pid==0){    
                    close(listenfd);
                    echo_srv(conn);
                    //某个客户端关闭,结束该子进程,否则子进程也去接受连接
                    //虽然结束了exit退出,但是内核还保留了其信息,父进程并未为其收尸。
                    exit(EXIT_SUCCESS);
            }else     close(conn);
        }
        return 0;
    }
  • 相关阅读:
    CF516E Drazil and His Happy Friends
    洛谷P4228 [清华集训2017] 榕树之心
    洛谷P5404 [CTS2019] 重复
    洛谷P4229 [清华集训2017] 某位歌姬的故事
    CF1286E Fedya the Potter Strikes Back
    CF1239
    洛谷P5892 [IOI2014] holiday 假期
    AT5202 [AGC038E] Gachapon
    库默尔定理
    UOJ37 [清华集训2014] 主旋律
  • 原文地址:https://www.cnblogs.com/wsw-seu/p/10951014.html
Copyright © 2011-2022 走看看