美文网首页Python
IO多路复用之poll总结

IO多路复用之poll总结

作者: kakukeme | 来源:发表于2017-03-09 14:31 被阅读29次

    参考链接:IO多路复用之poll总结

    搞不懂,为什么Xcode下无法调试了;

    1、基本知识

    poll的机制与select类似,与select在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是poll没有最大文件描述符数量的限制。poll和select同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。

    2、poll函数

    函数格式如下所示:

    # include <poll.h>
    int poll ( struct pollfd * fds, unsigned int nfds, int timeout);
    
    

    pollfd结构体定义如下:

    struct pollfd {
    
    int fd;             /* 文件描述符 */
    short events;           /* 等待的事件 */
    short revents;      /* 实际发生了的事件 */
    } ; 
    

    每一个pollfd结构体指定了一个被监视的文件描述符,可以传递多个结构体,指示poll()监视多个文件描述符。每个结构体的events域是监视该文件描述符的事件掩码,由用户来设置这个域。revents域是文件描述符的操作结果事件掩码,内核在调用返回时设置这个域。events域中请求的任何事件都可能在revents域中返回。合法的事件如下:

    POLLIN         有数据可读。

    POLLRDNORM      有普通数据可读。

    POLLRDBAND      有优先数据可读。

    POLLPRI         有紧迫数据可读。

    POLLOUT       写数据不会导致阻塞。

    POLLWRNORM      写普通数据不会导致阻塞。

    POLLWRBAND      写优先数据不会导致阻塞。

    POLLMSGSIGPOLL     消息可用。

    此外,revents域中还可能返回下列事件:
      POLLER   指定的文件描述符发生错误。

    POLLHUP   指定的文件描述符挂起事件。

    POLLNVAL  指定的文件描述符非法。

    这些事件在events域中无意义,因为它们在合适的时候总是会从revents中返回。

    使用poll()和select()不一样,你不需要显式地请求异常情况报告。
      
      POLLIN | POLLPRI等价于select()的读事件,POLLOUT |POLLWRBAND等价于select()的写事件。POLLIN等价于POLLRDNORM |POLLRDBAND,而POLLOUT则等价于POLLWRNORM。例如,要同时监视一个文件描述符是否可读和可写,我们可以设置 events为POLLIN |POLLOUT。在poll返回时,我们可以检查revents中的标志,对应于文件描述符请求的events结构体。如果POLLIN事件被设置,则文件描述符可以被读取而不阻塞。如果POLLOUT被设置,则文件描述符可以写入而不导致阻塞。这些标志并不是互斥的:它们可能被同时设置,表示这个文件描述符的读取和写入操作都会正常返回而不阻塞。

    timeout参数指定等待的毫秒数,无论I/O是否准备好,poll都会返回。timeout指定为负数值表示无限超时,使poll()一直挂起直到一个指定事件发生;timeout为0指示poll调用立即返回并列出准备好I/O的文件描述符,但并不等待其它的事件。这种情况下,poll()就像它的名字那样,一旦选举出来,立即返回。

    返回值和错误代码
      成功时,poll()返回结构体中revents域不为0的文件描述符个数;如果在超时前没有任何事件发生,poll()返回0;失败时,poll()返回-1,并设置errno为下列值之一:
      EBADF   一个或多个结构体中指定的文件描述符无效。

    EFAULTfds   指针指向的地址超出进程的地址空间。

    EINTR     请求的事件之前产生一个信号,调用可以重新发起。

    EINVALnfds  参数超出PLIMIT_NOFILE值。

    ENOMEM   可用内存不足,无法完成请求。

    3、测出程序

    编写一个echo server程序,功能是客户端向服务器发送信息,服务器接收输出并原样发送回给客户端,客户端接收到输出到终端。

    服务器端程序如下:

    Xcode下报这个错误:
    Conflicting types for 'inet_ntoa'
    是没有引入头文件#include <arpa/inet.h>;

    #include <stdio.h>
    
    #include <stdlib.h>
    #include <string.h>
    #include <errno.h>
    
    #include <arpa/inet.h>
    
    #include <netinet/in.h>
    #include <sys/socket.h>
    #include <poll.h>
    #include <unistd.h>
    #include <sys/types.h>
    
    #define IPADDRESS   "127.0.0.1"
    #define PORT        8787
    #define MAXLINE     1024
    #define LISTENQ     5
    #define OPEN_MAX    1000
    #define INFTIM      -1
    
    // 函数声明
    // 创建套接字并进行绑定
    static int socket_bind(const char* ip,int port);
    
    // IO多路复用poll
    static void do_poll(int listenfd);
    
    // 处理多个连接
    static void handle_connection(struct pollfd *connfds,int num);
    
    int main(int argc,char *argv[])
    {
        int  listenfd,connfd,sockfd;
        struct sockaddr_in cliaddr;
        
        socklen_t cliaddrlen;
        
        // 创建套接字并进行绑定
        listenfd = socket_bind(IPADDRESS, PORT);
        
        // 开始监听
        listen(listenfd,LISTENQ);
        
        
        do_poll(listenfd);
        
        return 0;
    }
    
    static int socket_bind(const char* ip,int port)
    {
        int  listenfd;
        struct sockaddr_in servaddr;
        
        // 创建socket
        listenfd = socket(AF_INET,SOCK_STREAM,0);
        if (listenfd == -1)
        {
            perror("socket error:");
            exit(1);
        }
        bzero(&servaddr,sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        inet_pton(AF_INET,ip,&servaddr.sin_addr);
        servaddr.sin_port = htons(port);
        
        // 绑定端口
        if (bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) == -1)
        {
            perror("bind error: ");
            exit(1);
        }
        
        return listenfd;
    }
    
    static void do_poll(int listenfd)
    {
        int  connfd,sockfd;
        struct sockaddr_in cliaddr;
        socklen_t cliaddrlen;
        
        /**
         *  clientfds为pollfd结构体数组,索引0,保存的是监听端口的服务器socket描述符;
         *  索引1开始,保存的是接受新的客户端连接;
         */
        struct pollfd clientfds[OPEN_MAX];
        int maxi;
        int i;
        int nready;
        
        // 添加监听描述符
        clientfds[0].fd = listenfd;
        clientfds[0].events = POLLIN;
        
        // 初始化客户连接描述符
        for (i = 1;i < OPEN_MAX;i++)
            clientfds[i].fd = -1;
        maxi = 0;
        
        
        // 死循环处理事件
        for ( ; ; )
        {
            // 获取可用描述符的个数
            nready = poll(clientfds, maxi+1, INFTIM);
            
            if (nready == -1) // poll返回出错
            {
                perror("poll error:");
                exit(1);
            }
            
            // 测试监听描述符是否准备好
            if (clientfds[0].revents & POLLIN)
            {
                cliaddrlen = sizeof(cliaddr);
                
                // 接受新的连接
                if ((connfd = accept(listenfd,(struct sockaddr*)&cliaddr,&cliaddrlen)) == -1)
                {
                    if (errno == EINTR)
                        continue;
                    else
                    {
                        perror("accept error:");
                        exit(1);
                    }
                }
                
                fprintf(stdout,"accept a new client: %s:%d\n", inet_ntoa(cliaddr.sin_addr),cliaddr.sin_port);
                
                // 将新的连接描述符添加到数组中
                for (i = 1;i < OPEN_MAX;i++)
                {
                    if (clientfds[i].fd < 0)
                    {
                        clientfds[i].fd = connfd;   // 添加客户端socket
                        
                        break;
                    }
                }
                
                if (i == OPEN_MAX)
                {
                    fprintf(stderr,"too many clients.\n");
                    exit(1);
                }
                
                // 将新的描述符添加到读描述符集合中
                clientfds[i].events = POLLIN;
                
                // 记录客户连接套接字的个数
                maxi = (i > maxi ? i : maxi);
                
                if (--nready <= 0)
                    continue;
            }
            
            // 处理客户连接
            handle_connection(clientfds,maxi);
        }
    }
    
    static void handle_connection(struct pollfd *connfds,int num)
    {
        int i,n;
        char buf[MAXLINE];
        memset(buf,0,MAXLINE);
        
        for (i = 1;i <= num;i++)
        {
            if (connfds[i].fd < 0)
                continue;
            
            // 测试客户描述符是否准备好
            if (connfds[i].revents & POLLIN)
            {
                // 接收客户端发送的信息
                n = read(connfds[i].fd, buf, MAXLINE);
                
                if (n == 0)
                {
                    close(connfds[i].fd);
                    connfds[i].fd = -1;
                    continue;
                }
                
                // printf("read msg is: ");
                write(STDOUT_FILENO,buf,n);
                
                //向客户端发送buf
                write(connfds[i].fd,buf,n);
            }
        }
    }
    
    

    客户端代码如下所示:

    include <netinet/in.h>
    #include <sys/socket.h>
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <poll.h>
    #include <time.h>
    #include <unistd.h>
    #include <sys/types.h>
    
    #define MAXLINE     1024
    #define IPADDRESS   "127.0.0.1"
    #define SERV_PORT   8787
    
    #define max(a,b) (a > b) ? a : b
    
    static void handle_connection(int sockfd);
    
    int main(int argc,char *argv[])
    {
        int                 sockfd;
        struct sockaddr_in  servaddr;
        
        // 创建socket
        sockfd = socket(AF_INET,SOCK_STREAM,0);
        
        bzero(&servaddr,sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_port = htons(SERV_PORT);
        inet_pton(AF_INET,IPADDRESS,&servaddr.sin_addr);
        
        // 连接服务器地址
        connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
        
        // 处理连接描述符
        handle_connection(sockfd);
        
        return 0;
    }
    
    static void handle_connection(int sockfd)
    {
        char    sendline[MAXLINE],recvline[MAXLINE];
        int     maxfdp,stdineof;
        
        struct pollfd pfds[2];
        int n;
        
        // 添加连接描述符
        pfds[0].fd = sockfd;
        pfds[0].events = POLLIN;
        
        // 添加标准输入描述符
        pfds[1].fd = STDIN_FILENO;
        pfds[1].events = POLLIN;
        
        for (; ;)
        {
            poll(pfds, 2, -1);
            
            if (pfds[0].revents & POLLIN)   // 服务器有数据返回
            {
                n = read(sockfd,recvline,MAXLINE);  // 读取服务器返回数据
                
                if (n == 0)
                {
                    fprintf(stderr,"client: server is closed.\n");
                    close(sockfd);
                }
                
                fprintf(stdout, "server return mst...");
                write(STDOUT_FILENO, recvline, n); // 输出到标准输出
            }
            
            // 测试标准输入是否准备好
            if (pfds[1].revents & POLLIN)
            {
                n = read(STDIN_FILENO, sendline, MAXLINE); // 读取输入的数据
                
                if (n  == 0)
                {
                    shutdown(sockfd, SHUT_WR); // 关闭sockfd的写功能
                    continue;
                }
                
                write(sockfd, sendline, n);  // 将输入的数据,发送到服务器端
            }
        }
    }
    
    

    4、程序测试结果

    5、参考资料

    http://blog.endlesscode.com/2010/03/27/select-poll-epoll-intro/

    http://hi.baidu.com/xzf20082004/item/622fb01a1018c7f5746a846f

    相关文章

      网友评论

        本文标题:IO多路复用之poll总结

        本文链接:https://www.haomeiwen.com/subject/fkwwgttx.html