select,poll和epoll

Posted 仇实

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了select,poll和epoll相关的知识,希望对你有一定的参考价值。

在阅读UNIX环境高级编程中,发现只写了select和poll,关于epoll的回调机制还有所不理解。

IO多路复用

首先要理解LINUX网络IO多路复用,IO多路复用在Linux下包括了三种,select, poll, epoll,抽象来看,他们功能是类似的,但具体细节各有不同:首先都会对一组文件描述符进行相关事件的注册,然后阻塞等待某些事件的发生或等待超时。更多细节详见下面的 "具体怎么用"。IO多路复用都可以关注多个文件描述符,但对于这三种机制而言,不同数量级文件描述符对性能的影响是不同的,下面会详细介绍。

select

select将监听的文件描述符分为三组,每一组监听不同的需要进行的IO操作。readfds是需要进行读操作的文件描述符,writefds是需要进行写操作的文件描述符,exceptfds是需要进行异常事件处理的文件描述符。这三个参数可以用NULL来表示对应的事件不需要监听。

当select返回时,每组文件描述符会被select过滤,只留下可以进行对应IO操作的文件描述符。

  • linux 下 fd_set 是个 1024 位的位图,每个位代表一个 fd 的值,返回后需要扫描位图,这也是效率低的原因。性能问题且不提,正确性问题则更值得重视。因为这是一个 1024 位的位图,因此当进程内的 fd 值 >= 1024 时,就会越界,可能会造成崩溃。对于服务器程序,fd >= 1024 很容易达到,只要连接数 + 打开的文件数足够大即可发生。
  • 再来看fd_set结构体是怎么记录一批文件描述符是否有事件触发的。仔细看fd_set结构的定义可以发现,他其实是一个__int32_t类型的数组,数组所有元素加起来共包含1024bit(由FD_SETSIZE定义)。记录某个文件描述符是否触发事件时,一个bit代表一个文件描述符的状态,0表示没有触发事件,1表示触发,把文件描述的数值映射为下标,计算出哪一bit代表了这个文件描述符的状态。
  • /* 其实就是创建了一个long int位图数组,数组大小就是__FD_SETSIZE / __NFDBITS=64;
  1. 所以只能处理FD值小于1024的描述符,否则就数组越界 ,其行为是未定义的*/

因此可以看出,一旦文件描述符的数值超出1024,计算出的下标就有可能超出__int32_t数组的最大下标位置,所以有可能会出现数组越界的问题。

poll

和select用三组文件描述符不同的是,poll只有一个pollfd数组,数组中的每个元素都表示一个需要监听IO操作事件的文件描述符。events参数是我们需要关心的事件,revents是所有内核监测到的事件。

  • Poll机制突破了Select机制中的文件描述符数量最大为1024的限制。

epoll

epoll_create用于创建一个epoll实例,而epoll_ctl用于往epoll实例中增删改要监测的文件描述符,epoll_wait则用于阻塞的等待可以执行IO操作的文件描述符直到超时。

  • 添加到文件描述符上的实际都会与网卡建立回调机制,也就是实际发生时会自主调用一个回调方法,将事件所在的文件描述符插入到就绪队列中
  • 引用程序调用epoll_wait 就可以直接从就绪队列中将所有就绪的文件描述符拿到,可以说时间复杂度O(1)

关于上述的回调机制如何实现,还在查阅epoll相关源码中

level-triggered and edge-triggered

  • 水平触发:只要套接字可读/可写epollwait都会将描述符返回。即只要套接字的接收缓冲中尚有数据或发送缓冲有空间容纳要发送的数据。这个套接字都会被epoll_wait返回。
  • 边缘触发:当套接字的缓冲状态发生变化时返回。对于读缓冲,有新到达的数据被添加到读缓冲时触发。对于写缓冲,当缓冲发生容量变更的时候触发(对端确认分组,内核删除已经确认的分组,空出空间,写缓冲容量发生变更)。

不同IO多路复用方案优缺点

poll vs select

poll和select基本上是一样的,poll相比select好在如下几点:

  1. poll传参对用户更友好。比如不需要和select一样计算很多奇怪的参数比如nfds(值最大的文件描述符+1),再比如不需要分开三组传入参数。
  2. poll会比select性能稍好些,因为select是每个bit位都检测,假设有个值为1000的文件描述符,select会从第一位开始检测一直到第1000个bit位。但poll检测的是一个数组。
  3. select的时间参数在返回的时候各个系统的处理方式不统一,如果希望程序可移植性更好,需要每次调用select都初始化时间参数。

select比poll好在下面几点

  1. 支持select的系统更多,兼容更强大,有一些unix系统不支持poll
  2. select提供精度更高(到microsecond)的超时时间,而poll只提供到毫秒的精度。

但总体而言 select和poll基本一致。

epoll vs poll&select

epoll优于select&poll在下面几点:

  1. 在需要同时监听的文件描述符数量增加时,select&poll是O(N)的复杂度,epoll是O(1),在N很小的情况下,差距不会特别大,但如果N很大的前提下,一次O(N)的循环可要比O(1)慢很多,所以高性能的网络服务器都会选择epoll进行IO多路复用。
  2. epoll内部用一个文件描述符挂载需要监听的文件描述符,这个epoll的文件描述符可以在多个线程/进程共享,所以epoll的使用场景要比select&poll要多。

参考

https://zhuanlan.zhihu.com/p/...
https://www.jianshu.com/p/019...
https://www.cnblogs.com/Anker...
https://www.cnblogs.com/anker...

以上是关于select,poll和epoll的主要内容,如果未能解决你的问题,请参考以下文章

应对百万访问量的epoll模式

select实现IO多路复用服务器

epoll详解

Python/selectors模块

Linux IO多路复用模型

Linux高性能服务编程(I/O复用)