文章目录
poll 多路复用
poll 的机制与 select 类似,与 select 在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是 poll 没有最大文件描述符数量的限制。
poll 和 select 同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符的增加而线性增加。
poll 编程模型
1. 函数原型
#include <poll.h>
int poll(struct poolfd *fds, unsigned int nfds, int timeout);
struct poolfd{
int fd; // 文件描述符
short events; // 请求的事件
short rebents; // 返回的事件
};
2. 参数说明
参数一:fds
是一个 struct pollfd 结构类型的数组,用于存放需要检测其状态的 Socket 描述符;每当调用这个函数之后,系统不会清空这个数组,操作起来比较方便;特别是对于 socket 连接比较多的情况下,在一定程度上可以提高效率;这一点与 select 函数不同,调用 select 函数之后,select 函数会清空它所检测的 socket 描述符集合,导致每次调用 select 之前都必须把 socket 描述符重新加入到待检测的 socket 描述符集合中;因此,select 函数适合于只检测一个 socket 描述符的情况,而 poll 函数适合于大量 socket 描述符的情况。参数二:nfds
nfds_t 类型的参数,用于标记数组 fds 中的结构体元素的总数量。参数三:timeout
是 poll 函数调用阻塞的时间,单位:毫秒
和 select 一样,最后一个参数 timeout 指定 poll 将在超时前等待一个事件多长时间。
这里有3种情况:
timeout = -1
这会造成 poll 永远等待。poll 只有在一个描述符就绪时返回,或者在调用进程捕捉到信号时返回(在这里,poll 返回-1),并且设置 errno 为 EINTR。-1 可以用宏定义常量 INFTIM 来代替(在 pth.h 中有定义)。timeout = 0
在这种情况下,测试所有的描述符,并且 poll 立即返回。这允许在 poll 中没有阻塞的情况下找出多个文件描述符的状态。timeout > 0
这将以毫秒为单位指定超时周期。poll 只有在超时到期时返回,除非一个描述符变为就绪,在这种情况下,它立刻返回。如果超时周期到,poll 返回 0.这里也可能会因为某个信号而中断该等待。
和 select 一样,文件描述符是否阻塞对 poll 是否阻塞没有影响。
3. 返回值和错误代码
成功时,poll 返回结构体中 revents 不为 0 的文件描述符个数;如果在超时前没有任何事件发生,poll 返回 0;失败时,poll 返回 -1;
> 0
数组 fds 中准备好读、写或出错状态的那些 socket i描述符的总数量;== 0
数组 fds 中没有任何 socket 描述符准备好读、写或出错;此时 poll 超时,超时时间 timeout 毫秒;换句话说,如果检测的 socket 描述符上没有任何事件发生的话,那么 poll 函数会阻塞 timeout 毫秒之后返回,如果 timeout == 0,那么 poll 函数立即返回不阻塞,如果 timeout == INFTIM,那么 poll 函数会一直阻塞下去,直到所检测的 socket 描述符上的感兴趣的事情发生才返回,如果感兴趣的事件永远不发生,那么 poll 就会永远阻塞下去。-1
poll 函数调用失败,同时会自动设置全局变量 errno 为下列值之一
errno | 描述 |
---|---|
EBADF | 一个或多个结构体中指定的文件描述符无效 |
EFAULTfds | 指针指向的地址超出进程的地址空间 |
EINTR | 请求的事件之前产生一个信号,调用可以重新发起 |
EINVALnfds | 参数超出 PLIMIT_NOFILE |
ENOMEM | 可用内存不足,无法完成请求 |
4. poll 结构体
struct pollfd{
int fd; // 文件描述符
short events; // 等待的事件
short revents; // 实际发生了的事件
};
fd
:表示感兴趣的,且打开了的文件描述符;events
:位掩码,用于指定针对这个文件描述符感兴趣的事件;revents
:位掩码,用于指定当 poll 返回时,在该文件描述符上已经发生了哪些事情。
每一个 pollfd 结构体指定了一个被监视的文件描述符,可以传递多个结构体,指示 poll 监视多个文件描述符。
5. 事件
在 poll 返回时,我们可以检查 revents 中的标志,对应于文件描述符请求的 events。如果 POLLIN 事件被设置,则文件描述符可以被读取而不阻塞;如果 POLLOUT 被设置,则文件描述符可以写入而不导致阻塞。这些标志并不是互斥的;它们可能被同时设置,表示这个文件描述符的读取和写入操作都会正常返回而不阻塞。
timeout 参数指定等待的毫秒数,无论 I/O 是否准备好,poll 都会返回。timeout 指定为负数表示无限等待超时,使 poll 一直挂起直到一个指定事件发生;timeout 为 0 表示 poll 调用立即返回并列出准备好的 I/O 的文件描述符,但并不等待其它的事件。这种情况下,poll 就像它的名字一样,一旦选举出来,立即返回。
events 注册的事件,通过 revents 返回
- 每个结构体的 events 域是监视该文件描述符的事件掩码,由用户来设置这个域。
- revents域是文件描述符的操作结果事件掩码,内核在调用返回时设置这个域。
- events域中请求的任何事件都可能在 revents 域中返回。
例如:
fds[0].events = POLLIN; // 将测试条件设置成普通或优先级带数据可读
int pollres = poll(fds, xx, xx); // 这样就可以监听fds里面的文件描述符了,当满足特定条件就返回,并将结果保存在 revents 中。
6. 事件描述法
合法的事件如下:
事件 | 描述 |
---|---|
POLLIN | 有数据可读 |
POLLRDNORM | 有普通数据可读 |
POLLRDBAND | 有优先数据可读 |
POLLPRI | 有紧迫数据可读 |
POLLOUT | 写数据不会导致阻塞 |
POLLWRNORM | 写普通数据不会导致阻塞 |
POLLWRBAND | 写优先数据不会导致阻塞 |
POLLMSGSIGPOLL | 消息可用 |
此外,revents 域中还可能返回下列事件:
事件 | 描述 |
---|---|
POLLER | 指定的文件描述符发生错误 |
POLLHUP | 指定的文件描述符挂起事件 |
POLLNVAL | 指定的文件描述符非法 |
这些事件在 events 域中无意义,因为它们在合适的时候总是会从 revents 中返回。
7. 事件使用技巧
- POLLIN
events 中使用该宏常数,能够在设备文件可读的情况下,结束 poll 函数。相反,revents 上使用该宏常数,在检查 poll 函数结束后,可依此判断设备文件是否处于可读状态(即使消息长度是 0)。 - POLLPRI
在 events 域中使用该宏常数,能够在设备文件的高优先级数据读取状态下,结束 poll 函数。相反,revents 上使用该宏常数,在检查 poll 函数结束后,可依此判断设备文件是否处于可读高优先级数据的状态(即使消息长度是 0).该宏常数用于处理网络信息包(packet)的数据传递。 - POLLOUT
在 events 域中使用该宏常数,能够在设备文件的写入状态下,结束 poll 函数。相反,revents 域上使用该宏常数,在检查 poll 结束后,可依此判断设备文件是否处于可写状态。 - POLLERR
在 events 域中使用该宏常数,能够在设备文件上发生错误时,结束 poll 函数。相反,revents 域上使用该宏函数,在检查 poll 函数后,可依此判断设备文件是否出错。 - POLLHUP
在 events 域中使用该宏常数,能够在设备文件上发生 hungup 时,结束 poll 函数。在检查 poll 函数结束后,可依此判断设备文件是否发生 hungup。 - POLLNVAL
在 events 域中使用该宏常数,能够在文件描述符的值无效时,结束 poll 函数。相反,revents 域上使用该宏函数,在检查 poll 函数结束后,可依此判断文件描述符是否有效。可用于处理网络信息时,检查 socket handler 是否已经无效。
poll 与 select 的区别和联系
使用 poll 和 select 不一样,你不需要显式地请求异常情况报告。
POLLIN | POLLPRI
等价于 select 的读事件POLLOUT | POLLWRBAND
等价于 select 的写事件POLLIN
等价于 POLLRDNORM | POLLRDBANDPOLLOUT
等价于 POLLWRNORM
例如,要同时监视一个文件描述符是否可读或可写,我们可以设置 events 为 POLLIN | POLLOUT。