对于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 函数该数组的大小。