pollfd

int poll (struct pollfd *fds, size_t nfds , int timeout);



struct pollfd {
int fd; /* 文件描述符 /
short events; /
等待的事件 /
short revents; /
实际发生了的事件 /
};
#include <sys/poll.h>
int poll(struct pollfd *ufds, unsigned int nfds, int timeout);
struct pollfd
{
int fd; /
想查询的文件描述符. /
short int events; /
fd 上,我们感兴趣的事件/
short int revents; /
Types of events that actually occurred. */
};



ufds 指向 struct pollfd 数组
nfds 指定 pollfd 数组元素的个数,也就是要监测几个 pollfd




参数说明:



fds:是一个struct pollfd结构类型的数组,用于存放需要检测其状态的Socket描述符;每当调用这个函数之后,系统不会清空这个数组,操作起来比较方便;特别是对于 socket连接比较多的情况下,在一定程度上可以提高处理的效率;这一点与select()函数不同,调用select()函数之后,select() 函数会清空它所检测的socket描述符集合,导致每次调用select()之前都必须把socket描述符重新加入到待检测的集合中;因 此,select()函数适合于只检测一个socket描述符的情况,而poll()函数适合于大量socket描述符的情况;



nfds:nfds_t类型的参数,用于标记数组fds中的结构体元素的总数量;



timeout:是poll函数调用阻塞的时间,单位:毫秒;



返回值:




0:数组fds中准备好读、写或出错状态的那些socket描述符的总数量;




==0:数组fds中没有任何socket描述符准备好读、写,或出错;此时poll超时,超时时间是timeout毫秒;换句话说,如果所检测的 socket描述符上没有任何事件发生的话,那么poll()函数会阻塞timeout所指定的毫秒时间长度之后返回,如果timeout==0,那么 poll() 函数立即返回而不阻塞,如果timeout==INFTIM,那么poll() 函数会一直阻塞下去,直到所检测的socket描述符上的感兴趣的事件发 生是才返回,如果感兴趣的事件永远不发生,那么poll()就会永远阻塞下去;



-1: poll函数调用失败,同时会自动设置全局变量errno;



如果待检测的socket描述符为负值,则对这个描述符的检测就会被忽略,也就是不会对成员变量events进行检测,在events上注册的事件也会被忽略,poll()函数返回的时候,会把成员变量revents设置为0,表示没有事件发生;



另外,poll() 函数不会受到socket描述符上的O_NDELAY标记和O_NONBLOCK标记的影响和制约,也就是说,不管socket是阻塞的还是非阻塞 的,poll()函数都不会收到影响;而select()函数则不同,select()函数会受到O_NDELAY标记和O_NONBLOCK标记的影 响,如果socket是阻塞的socket,则调用select()跟不调用select()时的效果是一样的,socket仍然是阻塞式TCP通讯,相 反,如果socket是非阻塞的socket,那么调用select()时就可以实现非阻塞式TCP通讯;



所以poll() 函数的功能和返回值的含义与 select() 函数的功能和返回值的含义是完全一样的,两者之间的差别就是内部实现方式不一样,select()函数基本上可以在所有支持文件描述符操作的系统平台上运 行(如:Linux 、Unix 、Windows、MacOS等),可移植性好,而poll()函数则只有个别的的操作系统提供支持(如:SunOS、Solaris、AIX、HP提供 支持,但是Linux不提供支持),可移植性差;



strust pollfd结构说明:



typedef struct pollfd {
int fd; /* 需要被检测或选择的文件描述符/
short events; /
对文件描述符fd上感兴趣的事件 /
short revents; /
文件描述符fd上当前实际发生的事件*/
} pollfd_t;



typedef unsigned long nfds_t;



经常检测的事件标记: POLLIN/POLLRDNORM(可读)、POLLOUT/POLLWRNORM(可写)、POLLERR(出错)



如果是对一个描述符上的多个事件感兴趣的话,可以把这些常量标记之间进行按位或运算就可以了;



比如:对socket描述符fd上的读、写、异常事件感兴趣,就可以这样做:struct pollfd fds;











fds[nIndex].events=POLLIN POLLOUT POLLERR;


当 poll()函数返回时,要判断所检测的socket描述符上发生的事件,可以这样做: struct pollfd fds;



检测可读TCP连接请求:



if((fds[nIndex].revents & POLLIN) == POLLIN){//接收数据/调用accept()接收连接请求}



检测可写:



if((fds[nIndex].revents & POLLOUT) == POLLOUT){//发送数据}



检测异常:



if((fds[nIndex].revents & POLLERR) == POLLERR){//异常处理}




  1. 头文件

    include < sys/ poll. h>



  2. 参数说明
    int poll ( struct pollfd * fds, unsigned int nfds, int timeout);



和select()不一样,poll()没有使用低效的三个基于位的文件描述符set,而是采用了一个单独的结构体pollfd数组,由fds指针指向这个组。pollfd结构体定义如下:



struct pollfd



{



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



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



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



} ;



typedef unsigned long nfds_t;



struct pollfd * fds:是一个struct pollfd结构类型的数组,用于存放需要检测其状态的socket描述符;每当调用这个函数之后,系统不需要清空这个数组,操作起来比较方便;特别是对于 socket连接比较多的情况下,在一定程度上可以提高处理的效率;这一点与select()函数不同,调用select()函数之后,select() 函数需要清空它所检测的socket描述符集合,导致每次调用select()之前都必须把socket描述符重新加入到待检测的集合中;因此,select()函数适合于只检测少量socket描述符的情况,而poll()函数适合于大量socket描述符的情况;



如果待检测的socket描述符为负值,则对这个描述符的检测就会被忽略,也就是不会对成员变量events进行检测,在events上注册的事件也会被忽略,poll()函数返回的时候,会把成员变量revents设置为0,表示没有事件发生;


经常检测的事件标记:



POLLIN/POLLRDNORM(可读)、



POLLOUT/POLLWRNORM(可写)、



POLLERR(出错)



合法的事件如下:



POLLIN 有数据可读。



POLLRDNORM 有普通数据可读。



POLLRDBAND 有优先数据可读。



POLLPRI 有紧迫数据可读。



POLLOUT 写数据不会导致阻塞。



POLLWRNORM 写普通数据不会导致阻塞。



POLLWRBAND 写优先数据不会导致阻塞。



POLLMSG SIGPOLL 消息可用。



此外,revents域中还可能返回下列事件:



POLLER 指定的文件描述符发生错误。



POLLHUP 指定的文件描述符挂起事件。



POLLNVAL 指定的文件描述符非法。



这些事件在events域中无意义,因为它们在合适的时候总是会从revents中返回。使用poll()和select()不一样,你不需要显式地请求异常情况报告。










POLLIN POLLPRI等价于select()的读事件,









POLLOUT POLLWRBAND等价于select()的写事件。









POLLIN等价于POLLRDNORM POLLRDBAND,


而POLLOUT则等价于POLLWRNORM。



如果是对一个描述符上的多个事件感兴趣的话,可以把这些常量标记之间进行按位或运算就可以了;



比如:对socket描述符fd上的读、写、异常事件感兴趣,就可以这样做:



struct pollfd fds;











fds[nIndex].events=POLLIN POLLOUT POLLERR;


当 poll()函数返回时,要判断所检测的socket描述符上发生的事件,可以这样做:



struct pollfd fds;



检测可读TCP连接请求:



if((fds[nIndex].revents & POLLIN) == POLLIN){//接收数据/调用accept()接收连接请求}



检测可写:



if((fds[nIndex].revents & POLLOUT) == POLLOUT){//发送数据}



检测异常:



if((fds[nIndex].revents & POLLERR) == POLLERR){//异常处理}



nfds_t nfds:用于标记数组fds中的结构体元素的总数量;



timeout:是poll函数调用阻塞的时间,单位:毫秒;



如果timeout==0,那么 poll() 函数立即返回而不阻塞,



如果timeout==INFTIM,那么poll() 函数会一直阻塞下去,直到所检测的socket描述符上的感兴趣的事件发 生是才返回,如果感兴趣的事件永远不发生,那么poll()就会永远阻塞下去;




  1. 返回值:

    0:数组fds中准备好读、写或出错状态的那些socket描述符的总数量;






==0:数组fds中没有任何socket描述符准备好读、写,或出错;此时poll超时,超时时间是timeout毫秒;换句话说,如果所检测的 socket描述符上没有任何事件发生的话,那么poll()函数会阻塞timeout所指定的毫秒时间长度之后返回,



-1: poll函数调用失败,同时会自动设置全局变量errno;errno为下列值之一:




  1. 错误代码
    EBADF 一个或多个结构体中指定的文件描述符无效。



EFAULTfds 指针指向的地址超出进程的地址空间。



EINTR 请求的事件之前产生一个信号,调用可以重新发起。



EINVALnfds 参数超出PLIMIT_NOFILE值。



ENOMEM 可用内存不足,无法完成请求。



  1. 实现机制
    poll是一个系统调用,其内核入口函数为sys_poll,sys_poll几乎不做任何处理直接调用do_sys_poll,do_sys_poll的执行过程可以分为三个部分:
    1),将用户传入的pollfd数组拷贝到内核空间,因此拷贝操作和数组长度相关,时间上这是一个O(n)操作,这一步的代码在do_sys_poll中包括从函数开始到调用do_poll前的部分。



2),查询每个文件描述符对应设备的状态,如果该设备尚未就绪,则在该设备的等待队列中加入一项并继续查询下一设备的状态。查询完所有设备后如果没有一个设备就绪,这时则需要挂起当前进程等待,直到设备就绪或者超时,挂起操作是通过调用schedule_timeout执行的。设备就绪后进程被通知继续运行,这时再次遍历所有设备,以查找就绪设备。这一步因为两次遍历所有设备,时间复杂度也是O(n),这里面不包括等待时间。相关代码在do_poll函数中。

3),将获得的数据传送到用户空间并执行释放内存和剥离等待队列等善后工作,向用户空间拷贝数据与剥离等待队列等操作的的时间复杂度同样是O(n),具体代码包括do_sys_poll函数中调用do_poll后到结束的部分。



  1. 注意事项
    1). poll() 函数不会受到socket描述符上的O_NDELAY标记和O_NONBLOCK标记的影响和制约,也就是说,不管socket是阻塞的还是非阻塞 的,poll()函数都不会收到影响;



   2). poll()函数则只有个别的的操作系统提供支持(如:SunOS、Solaris、AIX、HP提供 支持,但是Linux不提供支持),可移植性差;

Category linux