一、poll函数概念
  • poll函数类似于select函数,但是接口使用不同
  • poll函数可用于任何类型的文件描述符
二、struct pollfd结构体
struct pollfd {
    int fd; /* file descriptor to check, or <0 to ignore */
    short events; /* events of interest on fd */
    short revents; /* events that occurred on fd */
};

参数:

  • fd:文件描述符
  • events:可以设置为下面表格中值的一个或多个,通过这些值告诉内核文件描述符的哪些事件
  • revnents:返回时,该成员由内核设置,用于说明每个描述符发生了哪些事件,也是下面表格的取值

events/revents成员值:

  • 前4行测试的是可读性,接下来的3行测试可写性,最后3行则是异常条件
  • 最后3行是由内核在返回时设置的。不能够在events中设置。如果相应条件发生,则在revents中返回它们

UNP编程:42---IO管理(poll函数)_描述符

  • 表格中已有的几个常值说明:
    • POLLRDBAND:Linux不支持
    • POLLPRI:高优先级数据可读(比如TCP的带外数据)
    • POLLOUT:数据(包括普通数据和优先数据可写)
    • POLLHUP:挂起。比如管道的写端被关闭后,读端描述符上将收到POLLHUP事件。当一个文件描述符被挂断后,就不能再写该描述符,但是有可能仍然可以从该描述符读到数据
  • 附加表格中没有的常值:
    • POLLRDHUP(新增的):TCP连接被对方关闭,或者对方关闭了写操作(events/revents都可以使用)。这个是Linux内核 2.6.17之后新增的,由于是GNU规定的,所以使用这个选项需要在代码的最开始定义“_GNU_SOURCE”
  • 备注:
    • POLLIN自SVR3实现就存在, 早于SVR4中的优先级带,为了向后兼容,该常值继续保留。POLLRDNORM、POLLRDBAND、POLLWRBADN由XOPEN规范定义。它们实际上是将POLLIN事件和POLLOUT事件分的更加细致以区别对普通数据和优先数据。但Linux并不完全支持它们
    • POLLIN:可被定义为POLLRDNORM和POLLRDBAND的逻辑或。类似地,POLLOUT等同于POLLWRNORM,前者早于后者
二、poll函数结构
#include <poll.h>
int poll(struct pollfd fdarray[], nfds_t nfds, int timeout);

参数1

  • 与select不同,select是创建文件符集,并将文件符集放入参数2,3,4。poll函数是构建struct  pollfd的结构数组,数组中的每个结构含有文件描述符fd和该描述符的特性与条件
  • 一些注意事项:
    • poll没有更改events成员。这与select不同,select修改其参数以指示哪一个描述符已准备好了
    • 如果我们不再关心某个特定描述符,那么可以把它对应的pollfd结构的fd成员设置为一个负值。poll函数将忽略这样的pollfd结构的events成员,并且poll返回时将它的revents成员的值置为0
    • SVR4的SVID上显示,poll的第一个参数是struct pollfdfdarray[],而SVR4手册页上则显示该参数为struct pollfd*fdarray。在C语言中,这两种声明时等价的。我们使用第一种声明是为了重申fdarray指向的是一个结构数组,而不是指向单个结构的指针

参数2

  • 指定了参数1结构体数组的元素
  • 由于历史原因,如何声明nfds参数方面有几种不同的方式:
    • SVR3将nfds的类型指定为unsigned long,这似乎是太大了
    • SVR4手册中,poll原型的第二个参数的数据类型为size_t。但在<poll.h>包含的实际原型中,第二个参数的数据类型仍指定为unsigned long
    • Single UNIX Specification定义了新类型nfds_t,该类型允许实现选择对其合适的类型并隐藏了应用细节
  • 注意:因为poll返回值表示数组中满足事件的项数,所以这种类型必须大得足以保存一个整数

参数3

 此参数最后一个参数说明我们想要等待多少时间。如同select一样,有三种不同的情形:

  • timeout == −1:永远等待。在以下条件下返回:
    • 当所指定的描述符中的一个已准备好返回
    • 捕捉到一个信号返回。如果捕捉到一个信号,则poll返回-1,errno设置为EINTR(大多数系统在<stropts.h>中(POSIX在<poll.h中>)定义了常量INFTIM,其值为-1,也可以用这个常量,Ubuntu似乎还不可以使用) 
  • timeout == 0:不等待。测试所有描述符并立即返回。这是找到多个描述符的状态而不阻塞poll函数的轮询方法
  • timeout > 0:等待timeout毫秒。在以下条件下返回:
    • 当指定的描述符之一已准备好返回
    • 或指定的时间值已超过时立即返回
    • 如果已超时但是还没有一个描述符准备好,则返回值是0。(如果系统不提供毫秒分辨率,则timeout值取整到最近的支持值)

返回值:

  • 若有描述符就绪,返回就绪的描述符的个数
  • 若超时返回0
  • 出错返回-1
三、poll的注意事项

注意事项①

应当理解文件结束与挂断之间的区别:

  • 如果正在终端输入数据,并键入文件结束字符, 那么就会打开POLLIN,于是我们就可以读文件结束指示( read返回0)
  • revents中的POLLHUP没有打开。如果正在读调制解调器,并且电话线已挂断,将在revents中将接到POLLHUP通知

注意事项②

  • 与select相同,一个描述符是否阻塞不会影响poll是否阻塞

注意事项③

  • select和poll的可中断性
  • 中断的系统调用的自动重启是由4.2BSD引入的,但当时select函数是不重启的。这种特性在大多数系统中一直延续了下来,即使指定了SA_RESTART选项也是如此。但是,在SVR4上,如果指定了SA_RESTART,那么select和poll也是自动重启的。为了在将软件移动到SVR4派生的系统上时阻止这一点,如果信号有可能会中断select和poll,就要使用signal_intr函数(见下代码)
  • 本人文章说明的各种实现在接到一信号时都不重启动poll和select,即便使用了SA_RESTART标志也是如此
  • 但是本人博客文章所说明的各种实现在接收到一信号时都不重启poll和select,即便使用了SA_RESTART标志也是如此
Sigfunc *signal_intr(int signo, Sigfunc *func)
{
    struct sigaction act, oact;
    act.sa_handler = func;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
 
#ifdef SA_INTERRUPT
    act.sa_flags |= SA_INTERRUPT;
#endif
 
    if (sigaction(signo, &act, &oact) < 0)
        return(SIG_ERR);
    return(oact.sa_handler);
}
四、针对于网络编程的使用
  • 就TCP和UDP套接字而言,以下条件引起poll返回特定的revent。不幸的是,POSIX在其poll 的定义中留了许多空洞(也就是说有多种方法可返回相同的条件)
    • 所有正规TCP数据和所有UDP数据都被认为是普通数据
    • TCP的带外数据被认为是优先级带数据
    • 当TCP连接的读半部关闭时(譬如收到了一个来自对端的FIN),也被认为是普通数据, 随后的读操作将返回0
    • TCP连接存在错误既可认为是普通数据,也可认为是错误(POLLERR)。无论哪种情况, 随后的读操作将返回-1,并把errno设置成合适的值。这可用于处理诸如接收到RST或 发生超时等条件。
    • 在监听套接字上有新的连接可用既可认为是普通数据,也可认为是优先级数据。大多数 实现视之为普通数据。
    • 非阻塞式connect的完成被认为是使相应套接字可写
五、poll比select改进的地方
  • select文章中我们就FD_SETSIZE以及就每个描述符集中最大描述符数目相比每个进程中最大描述符数目展开的讨论。有了poll就不再有那样的问题了,因为分配一个pollfd结构的 数组并把该数组中元素的数目通知内核成了调用者的责任。内核不再需要知道类似fd_set的固定大小的数据类型
六、演示案例