前期知识:
在开始接触select之前,你需要先对IO的同步,异步,阻塞,非阻塞有个基本的了解,知道什么是IO多路复用。下面这篇文章可以帮助你快速区分这几种模型:IO - 同步,异步,阻塞,非阻塞 (亡羊补牢篇).
poll的系统调用:
poll系统调用和select类似,也是在指定时间内轮询一定数量的文件描述符,以测试其中是否有就绪者。
poll的函数格式:
#include <poll.h>
int poll (struct pollfd* fds,nfds_t nfds,int timeout);
- fds参数时一个pollfd结构类型的数组,它指定所有我们感兴趣的文件描述符上发送的可读、可写和异常等事件。
pollfd结构体的定义:
struct pollfd
{
int fd; /* 文件描述符 */
short events; /* 注册的事件 */
short revents;/* 实际发生的事件,由内核填充 */
}
成员变量说明:
(1) fd:指定文件描述符
(2) events:告诉poll监听fd上的哪些事件,它是一系列事件的按位或;
(3) revents:由内核修改,以通知应用程序fd上实际发生了哪些事件。
- nfds参数指定被监听事件集合fds的大小。其类型nfds_t的定义如下:
typedef unsigned long int nfds_t;
- timeout参数指定poll的超时值,单位是毫秒。当timeout为 -1时,poll调用将永远阻塞,直到某个事件发生;当timeout为0时,poll调用将立即返回。
poll的事件类型
事件 | 描述 | 是否可作为输入 | 是否可作为输出 |
---|---|---|---|
POLLIN | 数据(包括普通数据和优先数据)可读 | 是 | 是 |
POLLRDNORM | 普通数据可读 | 是 | 否 |
POLLRDBAND | 优先带数据可读(Linux不支持) | 是 | 是 |
POLLPRI | 高优先级数据可读,比如TCP带外数据 | 是 | 是 |
POLLOUT | 数据(包括普通数据和优先数据)可写 | 是 | 是 |
POLLWRNORM | 普通数据可写 | 是 | 是 |
POLLWRBAND | 优先级数据可写 | 是 | 是 |
POLLRDHUP | TCP链接被对方关闭,或者对方关闭写操作。它由GNU引入 | 是 | 是 |
POLLERR | 错误 | 否 | 是 |
POLLHUP | 挂起 | 否 | 是 |
POLLNVAL | 文件描述符没有打开 | 否 | 是 |
poll的文件描述符就绪条件:
读就绪条件:
1. socket内核接收缓存区中的字节数大于或等于其低水位标记SO_RCVLOWAT。此时我们可以无阻塞地读该socket,
并且读操作返回的字节数大于0.
2.socket通信的对方关闭连接。此时对该socket的读操作将返回 0.
3.监听socket上有新的连接请求。
4.socket上有未处理的错误。
写就绪条件:
1.socket内核发生缓存区中的可用字节数大于或等于其低水位标记SO_SNDLOWAT。此时我们可以无阻塞地写该socket,
并且写操作返回的字节数大于0.
2.socket的写操作被关闭, 对一个写操作被关闭的socket进行写操作, 会触发 SIGPIPE信号;
3.socket使⽤非阻塞connect连接成功或失败之后;
4.socket上有未读取的错误;
异常错误处理:
socket上接收到带外数据
poll函数示例
使用poll函数监控标准输入:
用poll监控标准输入是因为当没有输入的时候,进程就一直处于阻塞状态,当有数据输入时时间就立即就绪,如果监听标准输出,由于写缓冲区比较大,可能一直处于就绪状态,不利于观察。
#include<stdio.h>
#include<unistd.h>
#include<poll.h>
int main()
{
struct pollfd poll_fd;
poll_fd.fd = 0;
poll_fd.events = POLLIN;
for (;;)
{
int ret = poll(&poll_fd, 1, 2000);
if (ret < 0)
{
perror("poll");
continue;
}
if (ret == 0)
{
printf("poll timeout!\n");
continue;
}
if (poll_fd.revents == POLLIN)
{
char buf[1024];
read(0, buf, sizeof(buf) - 1);
printf("sdin:%s", buf);
}
}
}
运行结果:
当没有输入时,进程处于阻塞状态(每两秒输出一条poll timeout语句):
当有输入时,系统会告诉用户事件就绪,进行输入工作,即有输入时就打印到屏幕上:
poll函数的优缺点
通过poll函数的结构以及小测试程序的编写,我们不难发现poll函数的一些特点:
1、优点
(1)poll() 不要求开发者计算最大文件描述符加一的大小。
(2)poll() 在应付大数目的文件描述符的时候速度更快,相比于select。
(3)它没有最大连接数的限制,原因是它是基于链表来存储的。
(4)在调用函数时,只需要对参数进行一次设置就好了
2、缺点
(1)大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义。
(2)与select一样,poll返回后,需要轮询pollfd来获取就绪的描述符,这样会使性能下降
(3)同时连接的大量客户端在一时刻可能只有很少的就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降
参考资料:Linux高性能服务器编程,poll方法的基本概念