函数原型
#include <poll.h>
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是监视该文件描述符的事件掩码,由用户来设置这个域。#define POLLIN 0x0001 #define POLLPRI 0x0002 #define POLLOUT 0x0004 -
revents是文件描述符的操作结果事件掩码,内核在调用返回时设置这个域。
这点 和 select 不同,poll 每次检测之后的结果不会修改原来的传入值,而是将结果保留在 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 实现:
#define INIT_SIZE 128
int main(int argc, char **argv)
{
int listen_fd, connected_fd;
int ready_number;
ssize_t n;
char buf[MAXLINE];
struct sockaddr_in client_addr;
listen_fd = tcp_server_listen(SERV_PORT);
// 初始化pollfd数组,这个数组的第一个元素是listen_fd,其余的用来记录将要连接的connect_fd
struct pollfd event_set[INIT_SIZE];
event_set[0].fd = listen_fd;
event_set[0].events = POLLRDNORM;
// 用-1表示这个数组位置还没有被占用
int i;
for (i = 1; i < INIT_SIZE; i++)
{
event_set[i].fd = -1;
}
for ( ; ; )
{
if ((ready_number = poll(event_set, INIT_SIZE, -1)) < 0) {
error(1, errno, "poll failed ");
}
if (event_set[0].revents & POLLRDNORM) {
socklen_t client_len = sizeof(client_addr);
connected_fd = accept(listen_fd, (struct sockaddr *) &client_addr, &client_len);
//找到一个可以记录该连接套接字的位置
for (i = 1; i < INIT_SIZE; i++) {
if (event_set[i].fd < 0) {
event_set[i].fd = connected_fd;
event_set[i].events = POLLRDNORM;
break;
}
}
if (i == INIT_SIZE) {
error(1, errno, "can not hold so many clients");
}
if (--ready_number <= 0)
continue;
}
for (i = 1; i < INIT_SIZE; i++) {
int socket_fd;
if ((socket_fd = event_set[i].fd) < 0)
continue;
if (event_set[i].revents & (POLLRDNORM | POLLERR)) {
if ((n = read(socket_fd, buf, MAXLINE)) > 0) {
if (write(socket_fd, buf, n) < 0) {
error(1, errno, "write error");
}
} else if (n == 0 || errno == ECONNRESET) {
close(socket_fd);
event_set[i].fd = -1;
} else {
error(1, errno, "read error");
}
if (--ready_number <= 0)
break;
}
}
}
}
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