I/O 复用之 poll 函数

poll 函数提供的功能与 select 类似,不过在处理流设备时,它能提供额外的信息。

#include <poll.h>
int poll(struct pollfd *fdarray, unsigned long nfds, int timeout);
/* 返回值:若有就绪描述符则为其数目,若超时则为 0,若出错则为 -1 */
struct pollfd{
int fd; // descriptor to check
short events; // events of interest on fd
short revents; // events that occurred on fd
};

其中参数 fdarray 数组中的每一个元素都是一个 pollfd 结构,用于指定测试某个给定描述符 fd 的条件。要测试的条件由 events 成员指定,函数在相应的 revents 成员中返回该描述符的状态。这两个成员中的每一个都由指定某个特定条件的一位或多位构成。下表列出了用于指定 events 标志以及测试 revents 标志的一些常值。
[img]http://dl2.iteye.com/upload/attachment/0128/1469/09382b44-a833-3040-bd71-9aa02583d061.png[/img]
该表被分为三个部分:第一部分是处理输入的四个常值,第二部分是处理输出的三个常值,第三部分是处理错误的三个常值。其中第三部分的三个常值不能在 events 中设置,但是当相应条件存在时就在 revents 中返回。
poll 识别三类数据:普通、优先级带(priority band)和高优先级。
就 TCP 和 UDP 套接字而言,以下条件会引起 poll 返回特定的 revents。
(1)所有正规 TCP 数据和所有 UDP 数据都被认为是普通数据。
(2)TCP 的带外数据被认为是优先级带数据。
(3)当 TCP 连接的读半部关闭时(譬如收到了一个来自对端的 FIN),也被认为是普通数据,随后的读操作将返回 0。
(4)TCP 连接存在错误既可以认为是普通数据,也可以认为是错误(POLLERR)。无论哪种情况,随后的读操作将返回 -1,并把 errno 设置成合适的值。这可以用于处理诸如接收到 RST 或发生超时等条件。
(5)在监听套接字上有新的的连接可用既可以认为是普通数据,也可以认为是优先级数据。大多数实现视为普通数据。
(6)非阻塞式 connect 的完成被认为是使相应套接字可写。
结构数组中元素的个数是由参数 nfds 指定的。历史上该参数被定义为无符号长整型,Unix98 为该参数定义了名为 nfds_t 的新的数据类型。
timeout 参数指定 poll 返回前至多等待多长时间,它是一个指定应等待毫秒数的正值。它的可能取值如下:
(1)INFTIM:表示永远等待。POSIX 规范要求在头文件 <poll.h> 中定义 INFTIM,不过许多系统仍然把它定义在 <sys/stropts.h> 中。
(2)0:立即返回,不阻塞进程。
(3)> 0:等待指定数目的毫秒数。
如果不关心某个特定描述符,那么可以把与它对应的 pollfd 结构的 fd 成员设置成一个负数。poll 函数将忽略这样的 pollfd 结构的 events 成员,返回时将它的 revents 成员的值置为 0。
下面是使用 poll 函数实现的一个阻塞式的 TCP 回射服务器。它使用了一个 pollfd 结构的数组来维护客户信息,另外提供了一个 clients 数组来保存连接的客户端描述符,当其中的值为 -1 时表示所在项未使用。

#include <stdio.h>
#include <errno.h>
#include <poll.h>
#include <strings.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/socket.h>

#ifndef INFTIM
#define INFTIM -1
#endif

typedef struct sockaddr SA;

#define PORT 49877
#define LISTENQ 5
#define MAXLINE 1024

int main(void){
struct sockaddr_in servaddr;
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(PORT);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

int listenfd = socket(AF_INET, SOCK_STREAM, 0);
bind(listenfd, (SA *)&servaddr, sizeof(servaddr));
listen(listenfd, LISTENQ);

struct pollfd clients[FOPEN_MAX];
clients[0].fd = listenfd;
clients[0].events = POLLRDNORM;
int i = 0;
for(i=1; i<FOPEN_MAX; i++)
clients[i].fd = -1; // -1 indicates available entry
int maxi = 0; // max index into clients[] array
char buf[MAXLINE];
for(;;){
int nready = poll(clients, maxi+1, INFTIM);
if(clients[0].revents & POLLRDNORM){ // new client connection
struct sockaddr_in cliaddr;
socklen_t clilen = sizeof(cliaddr);
int clifd = accept(listenfd, (SA *)&cliaddr, &clilen);
// Or: int clifd = accept(listenfd, NULL, NULL);
for(i=1; i<FOPEN_MAX; i++){
if(clients[i].fd == -1){
clients[i].fd = clifd; // save descriptor
clients[i].events = POLLRDNORM;
break;
}
}
if(i == FOPEN_MAX){
printf("too many clients\n");
close(clifd);
continue;
}
if(i > maxi)
maxi = i;
if(--nready <= 0) // no more readable descriptors
continue;
}
for(i=1; i<=maxi; i++){ // check all clients for data
if(clients[i].fd == -1)
continue;
if(clients[i].revents & (POLLRDNORM | POLLERR)){
ssize_t n;
if((n = read(clients[i].fd, buf, MAXLINE)) < 0){
if(errno == ECONNRESET){ //connection reset by client
close(clients[i].fd);
clients[i].fd = -1;
}else{
printf("read error\n");
}
}else if(n == 0){ // connection closed by client
close(clients[i].fd);
clients[i].fd == -1;
}else{
write(clients[i].fd, buf, n);
}
if(--nready <= 0) // no more readable descriptors
break;
}
}
}
return 0;
}

这里检查某个现有连接的返回事件包含 POLLERR 的原因在于:有些实现在一个连接上接收到 RST 时返回的是 POLLERR 事件,而其他的返回的只是 POLLRDNORM 事件。不论哪一种情绪,这里都调用 read。当有错误发生时,read 将返回这个错误。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值