poll模型详解

1.poll模型原理

poll模型是基于select最大文件描述符限制提出的,跟select一样,只是将select使用的三个基于位的文件描述符改为使用一个数组的形式,对于各种可能的事件进行了一个包装

#include 
int poll (struct pollfd *fds, unsigned int nfds, int timeout);

参数说明:

第一个参数fds为一个pollfd结构数组,用来保存文件描述符

第二个参数nfds为pollfd结构体数组+1

第三个参数timeout为poll等待时间

返回值:

正常返回值为轮询文件描述符结构有事件发送的个数,-1返回失败

和select()不一样,poll()没有使用低效的三个基于位的文件描述符set,而是采用了一个单独的结构体pollfd数组,由fds指针指向这个组。pollfd结构体定义如下:

#include 

struct pollfd {
int fd; /* file descriptor */
short events; /* requested events to watch */
short revents; /* returned events witnessed */
};

每一个pollfd结构体指定了一个被监视的文件描述符,可以传递多个结构体,指示poll()监视多个文件描述符。每个结构体的events域是监视该文件描述符的事件掩码,由用户来设置这个域。revents域是文件描述符的操作结果事件掩码。内核在调用返回时设置这个域。events域中请求的任何事件都可能在revents域中返回。合法的事件如下:
POLLIN 有数据可读。
POLLRDNORM 有普通数据可读。
POLLRDBAND 有优先数据可读。
POLLPRI 有紧迫数据可读。
POLLOUT 写数据不会导致阻塞。
POLLWRNORM 写普通数据不会导致阻塞。
POLLWRBAND 写优先数据不会导致阻塞。
POLLMSG SIGPOLL消息可用。

此外,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指定为负数值表示无限超时;timeout为0指示poll调用立即返回并列出准备好I/O的文件描述符,但并不等待其它的事件。这种情况下,poll()就像它的名字那样,一旦选举出来,立即返回。
返回值和错误代码
成功时,poll()返回结构体中revents域不为0的文件描述符个数;如果在超时前没有任何事件发生,poll()返回0;失败时,poll()返回-1,并设置errno为下列值之一:
EBADF 一个或多个结构体中指定的文件描述符无效。
EFAULT fds指针指向的地址超出进程的地址空间。
EINTR 请求的事件之前产生一个信号,调用可以重新发起。
EINVAL nfds参数超出PLIMIT_NOFILE值。
ENOMEM 可用内存不足,无法完成请求。

2.poll工作流程

1.定义一个客户端连接数组

struct pollfd clients[OPEN_MAX];

2.调用poll轮询文件描述符事件

nready = poll(clients, maxi + 1, -1);

3.使用实例

引用一个简单的poll服务器例子

    #include    
    #include         /* basic system data types */  
    #include        /* basic socket definitions */  
    #include        /* sockaddr_in{} and other Internet defns */  
    #include         /* inet(3) functions */  
      
    #include   
    #include   
    #include   
    #include   
      
      
    #include  /* poll function */  
    #include   
      
    #define MAXLINE 10240  
      
    #ifndef OPEN_MAX  
    #define OPEN_MAX 40960  
    #endif  
      
    void handle(struct pollfd* clients, int maxClient, int readyClient);  
      
    int  main(int argc, char **argv)  
    {  
        int servPort = 6888;  
        int listenq = 1024;  
        int listenfd, connfd;  
        struct pollfd clients[OPEN_MAX];  
        int  maxi;  
        socklen_t socklen = sizeof(struct sockaddr_in);  
        struct sockaddr_in cliaddr, servaddr;  
        char buf[MAXLINE];  
        int nready;  
      
        bzero(&servaddr, socklen);  
        servaddr.sin_family = AF_INET;  
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);  
        servaddr.sin_port = htons(servPort);  
      
        listenfd = socket(AF_INET, SOCK_STREAM, 0);  
        if (listenfd < 0) {  
            perror("socket error");  
        }  
      
        int opt = 1;  
        if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {  
            perror("setsockopt error");  
        }  
      
        if(bind(listenfd, (struct sockaddr *) &servaddr, socklen) == -1) {  
            perror("bind error");  
            exit(-1);  
        }  
        if (listen(listenfd, listenq) < 0) {  
            perror("listen error");      
        }  
      
        clients[0].fd = listenfd;  
        clients[0].events = POLLIN;  
        int i;  
        for (i = 1; i< OPEN_MAX; i++)   
            clients[i].fd = -1;   
        maxi = listenfd + 1;  
      
        printf("pollechoserver startup, listen on port:%d\n", servPort);  
        printf("max connection is %d\n", OPEN_MAX);  
      
        for ( ; ; )  {  
            nready = poll(clients, maxi + 1, -1);  
            //printf("nready is %d\n", nready);  
            if (nready == -1) {  
                perror("poll error");  
            }  
            if (clients[0].revents & POLLIN) {  
                connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &socklen);  
                sprintf(buf, "accept form %s:%d\n", inet_ntoa(cliaddr.sin_addr), cliaddr.sin_port);  
                printf(buf, "");  
      
                for (i = 0; i < OPEN_MAX; i++) {  
                    if (clients[i].fd == -1) {  
                        clients[i].fd = connfd;  
                        clients[i].events = POLLIN;  
                        break;  
                    }  
                }  
      
                if (i == OPEN_MAX) {  
                    fprintf(stderr, "too many connection, more than %d\n", OPEN_MAX);  
                    close(connfd);  
                    continue;  
                }  
      
                if (i > maxi)  
                    maxi = i;  
      
                --nready;  
            }  
      
            handle(clients, maxi, nready);  
        }  
    }  
      
    void handle(struct pollfd* clients, int maxClient, int nready) {  
        int connfd;  
        int i, nread;  
        char buf[MAXLINE];  
      
        if (nready == 0)  
            return;  
      
        for (i = 1; i< maxClient; i++) {  
            connfd = clients[i].fd;  
            if (connfd == -1)   
                continue;  
            if (clients[i].revents & (POLLIN | POLLERR)) {  
                nread = read(connfd, buf, MAXLINE);//读取客户端socket流  
                if (nread < 0) {  
                    perror("read error");  
                    close(connfd);  
                    clients[i].fd = -1;  
                    continue;  
                }  
                if (nread == 0) {  
                    printf("client close the connection");  
                    close(connfd);  
                    clients[i].fd = -1;  
                    continue;  
                }  
      
                write(connfd, buf, nread);//响应客户端    
                if (--nready <= 0)//没有连接需要处理,退出循环  
                    break;  
            }  
        }  
    }  

4.总结讨论

1.poll与select,epoll比较

poll主要是解决select的最大文件描述符限制提出的,与select一样都是轮询文件描述符,所以效率方便也无法与epoll相比,另外poll不具备移植性,只有在linux系统上有实现,在windows系统没有poll模型的实现
2.poll模型效率

poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有fd后没有发现就绪设备,则挂起当前进程,直到设备就绪或者主动超时,被唤醒后它又要再次遍历fd。这个过程经历了多次无谓的遍历。

它没有最大连接数的限制,原因是它是基于链表来存储的,但是同样有如下缺点:
1.大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义。   
2.poll还有一个特点是“水平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd。

你可能感兴趣的:(poll模型详解)