上一篇我们说了关于select的相关信息,我们可以看到select是有弊端的,所以为了解决select的弊端,UNIX又在后期提出了poll。
select的弊端这里就不多说了,上一篇博客有提及。
poll
poll和select类似,不过在一些方面改善了select的弊端。它也是在指定的时间进行轮询文件描述符,查看是否有就绪时间发生。
和上次一样,我们先来看一下poll系统调用。
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
fds是一个pollfd的结构体数组。
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
这就是这个结构体数组每个元素。fd用来记录对应的文件描述符,events用来表示poll所监听的事件,这个由用户来设置。revents用来表示返回的事件。revents是通过内核来进行操作修改。
这里提供了一些合法事件。
事件 | 说明 |
---|---|
POLLIN | 普通或优先级带数据可读 |
POLLRDNORM | 普通数据可读 |
POLLRDBAND | 优先级带数据可读 |
POLLPRI | 高优先级数据可读 |
POLLOUT | 普通数据可写 |
POLLWRNORM | 普通数据可写 |
POLLWRBAND | 优先级带数据可写 |
POLLERR | 发生错误 |
POLLHUP | 发生挂起 |
POLLNVAL | 描述字不是一个打开的文件 |
后面的三个参数在events无意义,只能作为返回结果存储在revents。
另外,这里需要说的,这些参数如何设置给events,这些宏相当于每一个占用一个比特位,我们可以去想一下位图,所以,如果我们要进行设置两个事件,就使用|
操作,另外,当我们去查看事件是否发生的时候,这个时候我们可以使用revents&
事件,如果事件发生了,那么结果大于1。这就是一个简单的位运算的,相信你仔细想想就能够理解。
第二个参数nfds,用来监视的文件描述符的数目。
第三个参数是timeout,用来设置超时时间。
参数 | 说明 |
---|---|
-1 | poll将永远阻塞,等待知道某个时间发生 |
0 | 立即返回 |
大于0的值 | 设置正常时间值 |
返回值
poll返回revents不为0的文件描述符的个数。
失败返回-1
总结
poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有fd后没有发现就绪设备,则挂起当前进程,直到设备就绪或者主动超时,被唤醒后它又要再次遍历fd。这个过程经历了多次无谓的遍历。
poll使用了events和revents分流的特点,这样可以使得对关心事件只进行注册一次。
poll基于链表进行存储,没有最大连接数的限制,只取决于内存大小。
poll还有一个特点是“水平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd。
poll的实现机制与select类似,其对应内核中的sys_poll,只不过poll向内核传递pollfd数组,然后对pollfd中的每个描述符进行poll,相比处理fdset来说,poll效率更高。poll返回后,需要对pollfd中的每个元素检查其revents值,来得指事件是否发生。
poll的缺点
1、大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样是不是有意义。
2、poll依然需要进行轮询,所消耗的时间太多。
3、水平触发,效率低
示例程序
聊天室程序: