poll方法的基本概念

一、select函数简单回顾

在上篇博客中,我们详细了解了关于select接口的用法,在学习poll函数之前,我们先对select函数的内容做一个简单的回顾:
select优点:
目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点
select缺点:
(1)每次调用 select(),都需要把 fd 集合从用户态拷贝到内核态,这个开销在 fd 很多时会很大,同时每次调用 select() 都需要在内核遍历传递进来的所有 fd,这个开销在 fd 很多时也很大。
(2)单个进程能够监视的文件描述符的数量存在最大限制,在 Linux 上一般为 1024,可以通过修改宏定义甚至重新编译内核的方式提升这一限制,但是这样也会造成效率的降低
(3)select函数在每次调用之前都要对参数进行重新设定,这样做比较麻烦,而且会降低性能

二、poll函数的基本概念

1、什么是poll?

select() 和 poll() 系统调用的本质一样,poll() 的机制与 select() 类似,与 select() 在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理。

2、函数原型

这里写图片描述
参数解释:
(1)fds:指向一个结构体数组的第0个元素的指针,每个数组元素都是一个struct pollfd结构,用于指定测试某个给定的fd的条件
(2)nfds:表示fds结构体数组的长度
(3)timeout:表示poll函数的超时时间,单位是毫秒
函数功能:
监视并等待多个文件描述符的属性变化
函数返回值:
(1)返回值小于0,表示出错
(2)返回值等于0,表示poll函数等待超时
(3)返回值大于0,表示poll由于监听的文件描述符就绪返回,并且返回结果就是就绪的文件描述符的个数。

在该函数的第一个参数中,我们知道存放文件描述符的数组中每一个元素都是一个struct pollfd结构,那么pollfd结构到底是什么样的呢?我们来一起了解一下:

3、pollfd结构

结构体内容:
这里写图片描述
成员变量说明:
(1)fd:每一个 pollfd 结构体指定了一个被监视的文件描述符,可以传递多个结构体,指示 poll() 监视多个文件描述符。
(2)events:表示要告诉操作系统需要监测fd的事件(输入、输出、错误),每一个事件有多个取值
(3)revents:revents 域是文件描述符的操作结果事件,内核在调用返回时设置这个域。events 域中请求的任何事件都可能在 revents 域中返回。
events&revents的取值如下:

事件 描述 是否可作为输入(events) 是否可作为输出(revents)
POLLIN 数据可读(包括普通数据&优先数据)
POLLOUT 数据可写(普通数据&优先数据)
POLLRDNORM 普通数据可读
POLLRDBAND 优先级带数据可读(linux不支持)
POLLPRI 高优先级数据可读,比如TCP带外数据
POLLWRNORM 普通数据可写
POLLWRBAND 优先级带数据可写
POLLRDHUP TCP连接被对端关闭,或者关闭了写操作,由GNU引入
POPPHUP 挂起
POLLERR 错误
POLLNVAL 文件描述符没有打开

注意:
每个结构体的 events 域是由用户来设置,告诉内核我们关注的是什么,而 revents 域是返回时内核设置的,以说明对该描述符发生了什么事件

4、socket就绪条件

与select中socket就绪条件一致:
读就绪:

1)socket内核中,接收缓冲区中的字节数,大于等于低水位标记SO_RCVLOWAT. 此时可以无阻塞的读该文件描述符, 并且返回值大于0;
2)socket TCP通信中, 对端关闭连接, 此时对该socket读, 则返回0;
3)监听的socket上有新的连接请求;
4)socket上有未处理的错误;

写就绪:

1)socket内核中, 发送缓冲区中的可用字节数(发送缓冲区的空闲位置大⼩), 大于等于低水位标记 SO_SNDLOWAT, 此时可以无阻塞的写, 并且返回值大于0;
2)socket的写操作被关闭(close或者shutdown). 对一个写操作被关闭的socket进行写操作, 会触发 SIGPIPE信号;
3)socket使⽤非阻塞connect连接成功或失败之后;
4)socket上有未读取的错误;
异常就绪:
socket上收到带外数据.

三、poll函数示例

使用poll函数监控标准输入:
用poll监控标准输入是因为当没有输入的时候,进程就一直处于阻塞状态,当有数据输入时时间就立即就绪,如果监听标准输出,由于写缓冲区比较大,可能一直处于就绪状态,不利于观察。
程序代码:

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<poll.h>
  4 
  5 int main()
  6 {
  7     struct pollfd poll_fd;
  8     poll_fd.fd=0;
  9     poll_fd.events=POLLIN;
 10 
 11     for(;;)
 12     {
 13         int ret=poll(&poll_fd,1,2000);
 14         if(ret<0)
 15         {
 16             perror("poll");
 17             continue;
 18         }
 19         if(ret==0)
 20         {
 21             printf("poll timeout!\n");
 22             continue;
 23         }
 24         if(poll_fd.revents==POLLIN)
 25         {
 26             char buf[1024];
 27             read(0,buf,sizeof(buf)-1);
 28             printf("sdin:%s",buf);
 29         }
 30     }
 31 }

运行结果:
当没有输入时,进程处于阻塞状态(每两秒输出一条poll timeout语句):
这里写图片描述
当有输入时,系统会告诉用户事件就绪,进行输入工作,即有输入时就打印到屏幕上:
这里写图片描述

四、poll函数的优缺点

通过poll函数的结构以及小测试程序的编写,我们不难发现poll函数的一些特点:

1、优点

(1)poll() 不要求开发者计算最大文件描述符加一的大小。
(2)poll() 在应付大数目的文件描述符的时候速度更快,相比于select。
(3)它没有最大连接数的限制,原因是它是基于链表来存储的。
(4)在调用函数时,只需要对参数进行一次设置就好了

2、缺点

(1)大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义。
(2)与select一样,poll返回后,需要轮询pollfd来获取就绪的描述符,这样会使性能下降
(3)同时连接的大量客户端在一时刻可能只有很少的就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降

猜你喜欢

转载自blog.csdn.net/qq_37964547/article/details/80697530