函数select()和poll()分析(转)

1. 我如何使用select()函数?

select()函数的接口主要是建立在一种叫'fd_set'类型的基础上。它('fd_set')是一组文件描述符(fd)的集合。由于fd_set类型的长度在不同平台上不同,因此应该用一组标准的宏定义来处理此类变量:

fd_set set;

FD_ZERO(&set); /* 将set清零 */

FD_SET(fd, &set); /* 将fd加入set */

FD_CLR(fd, &set); /* 将fd从set中清除 */

FD_ISSET(fd, &set); /* 如果fd在set中则真 */

在过去,一个fd_set通常只能包含少于等于32个文件描述符,因为fd_set其实只用了一个int的比特矢量来实现,在大多数情况下,检查fd_set能包括任意值的文件描述符是系统的责任,但确定你的fd_set到底能放多少有时你应该检查/修改宏FD_SETSIZE的值。*这个值是系统相关的*,同时检查你的系统中的select()的man手册。有一些系统对多于1024个文件描述符的支持有问题。[译者注: Linux就是这样的系统!你会发现sizeof(fd_set)的结果是128(*8 =FD_SETSIZE=1024) 尽管很少你会遇到这种情况。]

select的基本接口十分简单:

int select(int nfds, fd_set *readset, fd_set *writeset,

fd_set *exceptset, struct timeval *timeout);

其中:

nfds : 需要检查的文件描述符个数,数值应该比是三组fd_set中最大数更大,而不是实际文件描述符的总数。

readset: 用来检查可读性的一组文件描述符。

writeset: 用来检查可写性的一组文件描述符。

exceptset: 用来检查意外状态的文件描述符。(注:错误并不是意外状态)

timeout: NULL指针代表无限等待,否则是指向timeval结构的指针,代表最长等待时间。(如果其中tv_sec和tv_usec都等于0, 则文件描述符的状态不被影响,但函数并不挂起)函数将返回响应操作的对应操作文件描述符的总数,且三组数据均在恰当位置被修改,只有响应操作的那一些没有修改。接着应该用FD_ISSET宏来查找返回的文件描述符组。

这里是一个简单的测试单个文件描述符可读性的例子:

int isready(int fd)

{

int rc;

fd_set fds;

struct timeval tv;

 

FD_ZERO(&fds);

FD_SET(fd,&fds);

tv.tv_sec = tv.tv_usec = 0;

 

rc = select(fd+1, &fds, NULL, NULL, &tv);

if (rc < 0)

return -1;

 

return FD_ISSET(fd,&fds) ? 1 : 0;

}

 

当然如果我们把NULL指针作为fd_set传入的话,这就表示我们对这种操作的发生不感兴趣,但select() 还是会等待直到其发生或者超过等待时间。

 [译者注:在Linux中,timeout指的是程序在非sleep状态中度过的时间,而不是实际上过去的时间,这就会引起和非Linux平台移植上的时间不等问题。移植问题还包括在System V风格中select()在函数退出前会把timeout设为未定义的NULL状态,而在BSD中则不是这样,Linux在这点上遵从System V,因此在重复利用timeout指针问题上也应该注意。]

2 我如何使用poll()

poll()接受一个指向结构'struct pollfd'列表的指针,其中包括了你想测试的文件描述符和事件。事件由一个在结构中事件域的比特掩码确定。当前的结构在调用后将被填写并在事件发生后返回。在SVR4(可能更早的一些版本)中"poll.h"文件中包含了用于确定事件的一些宏定义。事件的等待时间精确到毫秒(但令人困惑的是等待时间的类型却是int),当等待时间为0时,poll()函数立即返回,-1则使poll()一直挂起直到一个指定事件发生。下面是pollfd的结构。

struct pollfd {

int fd; /* 文件描述符 */

short events; /* 等待的事件 */

short revents; /* 实际发生了的事件 */

};

于select()十分相似,当返回正值时,代表满足响应事件的文件描述符的个数,如果返回0则代表在规定事件内没有事件发生。如发现返回为负则应该立即查看errno,因为这代表有错误发生。

如果没有事件发生,revents会被清空,所以你不必多此一举。

这里是一个例子:

/* 检测两个文件描述符,分别为一般数据和高优先数据。如果事件发生则用相关描述符和优先度调用函数handler(),无时间限制等待,直到错误发生或描述符挂起。*/

 

#include <stdlib.h>

#include <stdio.h>

 

#include <sys/types.h>

#include <stropts.h>

#include <poll.h>

 

#include <unistd.h>

#include <errno.h>

#include <string.h>

 

#define NORMAL_DATA 1

#define HIPRI_DATA 2

 

int poll_two_normal(int fd1,int fd2)

{

struct pollfd poll_list[2];

int retval;

 

poll_list[0].fd = fd1;

poll_list[1].fd = fd2;

poll_list[0].events = POLLIN|POLLPRI;

poll_list[1].events = POLLIN|POLLPRI;

 

while(1)

{

retval = poll(poll_list,(unsigned long)2,-1);

/* retval 总是大于0或为-1,因为我们在阻塞中工作 */

 

if(retval < 0)

{

fprintf(stderr,"poll错误: %s\n",strerror(errno));

return -1;

}

 

if(((poll_list[0].revents&POLLHUP) == POLLHUP) ||

((poll_list[0].revents&POLLERR) == POLLERR) ||

((poll_list[0].revents&POLLNVAL) == POLLNVAL) ||

((poll_list[1].revents&POLLHUP) == POLLHUP) ||

((poll_list[1].revents&POLLERR) == POLLERR) ||

((poll_list[1].revents&POLLNVAL) == POLLNVAL))

return 0;

 

if((poll_list[0].revents&POLLIN) == POLLIN)

handle(poll_list[0].fd,NORMAL_DATA);

if((poll_list[0].revents&POLLPRI) == POLLPRI)

handle(poll_list[0].fd,HIPRI_DATA);

if((poll_list[1].revents&POLLIN) == POLLIN)

handle(poll_list[1].fd,NORMAL_DATA);

if((poll_list[1].revents&POLLPRI) == POLLPRI)

handle(poll_list[1].fd,HIPRI_DATA);

}

}

3 我是否可以同时使用SysV IPCselect()/poll()

*不能。* (除非在AIX上,因为它用一个无比奇怪的方法来实现这种组合)

一般来说,同时使用select()或poll()和SysV 消息队列会带来许多麻烦。SysVIPC的对象并不是用文件描述符来处理的,所以它们不能被传递给select()和poll()。这里有几种解决方法,其粗暴程度各不相同:

- 完全放弃使用SysV IPC。 :-)

- 用fork(),然后让子进程来处理SysV IPC,然后用管道或套接口和父进程说话。父进程则使用select()。

- 同上,但让子进程用select(),然后和父亲用消息队列交流。

- 安排进程发送消息给你,在发送消息后再发送一个信号。*警告*:要做好这个并不简单,非常容易写出会丢失消息或引起死锁的程序。

……另外还有其他方法。

 

posted @ 2012-07-31 22:57  Drobobls  阅读(122)  评论(0)    收藏  举报