I/O复用二:poll

poll

函数原型
#include 

int poll(struct pollfd *fdarray, unsigned long nfds, int timeout);

        若有描述符就绪,则返回其数目,若超时则为0
        若出错则为-1,并设置errno为下列值之一:
              EBADF 一个或多个结构体中指定的文件描述符无效。
              EFAULTfds  指针指向的地址超出进程的地址空间。
              EINTR  请求的事件之前产生一个信号,调用可以重新发起。
              EINVALnfds  参数超出PLIMIT_NOFILE值。
              ENOMEM  可用内存不足,无法完成请求。
  • 第一个参数
    struct pollfd {
        int fd;         // 文件描述符
        short events;   // 等待发生的事件
        short revents;  // 实际发生了的事件
    }
    
    • pollfd结构体指定了一个被监视的文件描述符,可以传递多个结构体,指示poll()监视多个文件描述符。
    • events是监视该文件描述符的事件掩码,由用户来设置这个域。
    • revents是文件描述符的操作结果事件掩码,内核在调用返回时设置这个域。
  • 第二个参数
    数组元素个数

  • 第三个参数

    timeout参数指定等待的毫秒数,无论I/O是否准备好,poll()都会返回。

    • 负数值表示无限超时,使poll()一直挂起直到一个指定事件发生。
    • 0指示poll()调用立即返回并列出准备好的I/O的文件描述符,但并不等待其它的事件。
合法事件
  • 输入

    常值 可以作为events输入 可以作为revents结果 说明
    POLLIN 普通或优先级带数据可读
    POLLRDNORM 普通数据可读
    POLLRDBAND 优先级带数据可读
    POLLPRI 高优先级数据可读
  • 输出

    常值 可以作为events输入 作为revents结果 说明
    POLLOUT 普通数据可写
    POLLWRNORM 普通数据可写
    POLLWRBAND 优先级带数据可写
  • 错误

    常值 可以作为events输入 作为revents结果 说明
    POLLERR 发生错误
    POLLHUP 发生挂起
    POLLNVAL 描述符不是一个打开的文件
  • poll识别三种数据类型:普通, 优先级带,高优先级

    • 所有正规的TCP数据和所有UDP数据被认为是普通数据
    • 读半部关闭(收到对端的FIN)时,也是普通数据,随后读操作返回0
    • 监听套接字上有新的连接既可以认为是普通数据也可以认为是优先级数据
    • TCP连接存在错误既可以认为是普通数据,也可以认为是优先级数据
    • TCP带外数据被认为是优先级带数据
    • 非阻塞式connect的完成被认为是相应套接字可写

    poll函数中参数pollfd结构包含了要监视的event和发生的event,不再使用select"参数-值"传递的方式。同时,pollfd并没有最大数量限制(但是数量过大后性能也是会下降)。 和select函数一样,poll返回后,需要轮询pollfd来获取就绪的描述符。

代码示例
  • 回射服务器采用poll
int main()
{
    listenfd = make_listenfd(argv[1], SERVER_PORT);
    do_poll(listenfd, buf);
}

void do_poll(int listenfd, char *buf)
{
    ......
    poll_array[0].fd = listenfd;    // 首先添加监听描述符至poll数组
    poll_array[0].events = POLLRDNORM;
    ......
        
    /* 完成事件分发 */
    for ( ; ; ) {
        nready = poll(poll_array, maxi + 1, -1);
        
        /* 对于监听描述符,POLLRDNORM(普通数据可读)表示有新的连接 */
        if (poll_array[0].revents & POLLRDNORM) {  
            handle_accept(listenfd, poll_array, &maxi);    
            ......
        }

        for (i = 1; i <= maxi; i++) {
            ......
            /* POLLRDNORM(普通数据可读) POLLERP(发生错误) */
            if (poll_array[i].revents & (POLLRDNORM | POLLERR)) {
                read_socket(connfd, poll_array, i, buf);
                ......
            }
        }
    }
}
/*
 * 处理新的连接到来
 * 1 把新的连接添加到poll
 */
void handle_accept(int listenfd, struct pollfd *client, int* maxiPtr);
/*
 * 处理连接套接字有数据可读
 * 1 把数据从连接套接字读出来
 * 2 把数据写入到连接套接字中
 */
void read_socket(int connfd, struct pollfd* poll_array, int index, char* buf);

select 和 poll比较

select()poll()函数本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理。

poll()没有最大文件描述符数量的限制。

select()返回后,之前没有准备好的文件描述符会从集合当中删除,这样如果下次需要再次添加所有文件描述符或者使用两个相同的文件描述符集合,一个用于备份,一个用于监听,比较复杂。poll不需要这个复杂的操作。

poll()select()同样存在一个缺点就是包含大量文件描述符的数组被整体复制于用户和内核的地址空间之间,而无论这些文件描述符是否就绪。它的开销随着文件描述符数量的增加而线性增加

参考资料
[1]《UNIX 网络编程》3th [美] W.Richard Stevens,Bill Fenner,Andrew M. Rudoff
[2] http://www.cnblogs.com/Anker/archive/2013/08/15/3261006.html

你可能感兴趣的:(I/O复用二:poll)