网络编程学习16--I/O多路复用(poll)

452 阅读4分钟

对于select函数,其所支持的文件描述符的个数是有限的,在Linux系统中,select的默认最大值为1024。

而另一种I/O复用技术,poll可以突破文件描述符的个数限制。

poll函数

poll函数和内核交互的数据结构有所变化,并突破了文件描述符的限制。

函数原型

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

参数

1)fds参数是一个pollfd结构类型的数组,它指定所有我们感兴趣的文件描述符上发生的可读、可写和异常等事件。pollfd结构体定义如下:

 struct pollfd {
     int fd;  /*文件描述符*/
     short events;  /*注册的事件(即待检测的事件类型,可以表示多个不同的事件)*/
     short revents;  /*实际发生的事件,由内核填充*/
 };

fd成员指定文件描述符;events成员告诉poll监听fd上的哪些事件,它是一系列事件的按位或revents成员由内核修改,以通知应用程序 fd上实际发生了哪些事件。(当想要判断发生的事件时,将revents和对应的事件类型进行位与操作即可,一个文件描述符可以对应多个事件类型

和select的不同在于,poll每次检测之后,不会修改原来的传入值,而是将结果保留再revents字段中,这样就不需要每次检测完重置待检测的描述符和感兴趣的事件。

poll的事件类型可分为三类:可读、可写、异常

第一类:可读事件

 #define POLLIN 0x0001  /* 数据(包括普通数据和优先数据)可读 */
 #define POLLPRI 0x0002  /* 高优先级数据可读,比如TCP带外数据 */
 #define POLLRDNORM 0x0040  /* 普通数据可读 */
 #define POLLRDBAND 0x0080  /* 优先级带数据可读(Linux不支持) */

一般在程序里使用 POLLIN 即可,套接字可读事件和 select 的 readset 基本一致,是系统内核通知应用程序有数据可以读,通过 read 函数执行读操作不会被阻塞

第二类:可写事件

 #define POLLOUT    0x0004    /* 数据(包括普通数据和优先数据)可写 */
 #define POLLWRNORM POLLOUT   /* 普通数据可写 */
 #define POLLWRBAND 0x0100    /* 优先级带数据可写 */

一般在程序里使用 POLLOUT 即可。套接字可写事件和 select 的 writeset 基本一致,是系统内核通知套接字缓冲区已准备好,通过 write 函数执行写操作不会被阻塞

注意:以上两类事件都可以在 revents 得到复用。另一类异常事件,没有办法通过 poll 向系统内核递交检测请求,只能通过 revents 来加以检测(即只能通过revents的值来检测是否发生异常事件)

第三类:异常事件

 #define POLLERR    0x0008    /* 一些错误发送 */
 #define POLLHUP    0x0010   /* 描述符挂起(比如管道的写端被关闭后,读端描述符上接收到POLLHUP事件)*/
 #define POLLNVAL   0x0020    /* 请求的事件无效(文件描述符没有打开)*/

还有一种自Linux内核2.6.17开始新增的事件

 #define POLLRDHUP

通常,应用程序需要根据 recv 调用的返回值来区分 socket 上接收到的是有效数据还是对方关闭连接的请求,并做相应的处理。(recv返回值为0时,表示对方关闭连接)

POLLRDHUP事件,会在socket上接收到对方关闭连接的请求之后触发。这样就为我们区分上述两种情况提供了一种更简单的方式。但是,使用POLLRDHUP事件时,需要在代码最开始处定义 _GNU_SOURCE

2)nfds参数指定被监听文件描述符集合fds的大小,即数组fds的大小。类型nfds_t的定义如下:

 typedef unsigned long int nfds_t;  /* 32位系统中,该类型为4字节 */

3)timeout参数指定poll的超时值,单位是毫秒。当timeout为-1时,poll调用将永远阻塞,直到某个事件发生;当timeout为0时,poll调用将立即返回;当timeout大于0时,poll调用方等待指定的毫秒数后返回。

返回值

成功时返回就绪文件描述符的数目,超时时返回0,失败时返回-1。

对于poll函数,如果我们不想对某个 pollfd 结构进行事件检测,可以将该pollfd结构的成员变量fd的值设置成一个负值,这样,poll 函数将忽略其events事件,并在检测完成后,将其revents成员值设置为0(表示无事件发生)

对于select函数,其文件描述符的数量在定义fd_set 对象时就已经指定(如果想要修改默认数量的话,需要对fd_set结构体的源码进行修改);而在poll函数里,我们可以控制 pollfd 结构的数组的大小,这样就可以突破select最大文件描述符的限制,在这种情况下,需要调用者分配 pollfd 数组 并通知 poll 函数该数组的大小