详解 select、poll、epoll


详解 select、poll、epoll

select、poll

我们熟悉的 select/poll/epoll 是内核提供给用户态的多路复用系统调用,进程可以通过一个系统调用函数从内核中获取多个事件。

select/poll/epoll 是如何获取网络事件的呢?在获取事件时,先把所有连接(文件描述符)传给内核,再由内核返回产生了事件的连接,然后在用户态中再处理这些连接对应的请求即可。

select 实现多路复用的方式是,将已连接的 Socket 都放到一个文件描述符集合,然后调用 select 函数将文件描述符集合拷贝到内核里,让内核来检查是否有网络事件产生,检查的方式很粗暴,就是通过遍历文件描述符集合的方式,当检查到有事件产生后,将此 Socket 标记为可读或可写,接着再把整个文件描述符集合拷贝回用户态里,然后用户态还需要再通过遍历的方法找到可读或可写的 Socket,然后再对其处理。

所以,对于 select 这种方式,需要进行 2 次遍历文件描述符集合,一次是在内核态里,一次是在用户态里,而且还会发生 2 次拷贝文件描述符集合,先从用户空间传入内核空间,由内核修改后,再传出到用户空间中。

select 使用固定长度的 BitsMap,表示文件描述符集合,而且所支持的文件描述符的个是有限制的,在 Linux 系统中,有内核中的额FD_SETSIZE 限制,默认最大值为 1024,只能监听 0~1023 的文件描述符。

poll 不再用 BitsMap 来存储所关注的文件描述符,取而代之用动态数组,以链表形式来组织,突破了 select 的文件描述符个数限制,当然还会受到系统文件描述符的限制。

但是 poll 和 select 并没有太大的本质区别,都是使用线性结构存储进程关注的 Socket 集合,因此都需要遍历文件描述符集合来找到可读或可写的 Socket,时间复杂度为 O(n),而且也需要在用户态与内核态之间拷贝文件描述符集合,这种方式随着并发数上来,性能损耗会呈指数级增长。

epoll

epoll 通过两个方面,很好的解决了 select/poll 的问题。

  1. epoll 在内核里使用红黑树来跟踪进程所有待检测的文件描述字,把需要监控的 socket 通过 epoll_ctl()函数加入到内核中的红黑树里,红黑树是个高效的数据结构,增删查一般时间复杂度都是 O(logn),通过对这颗红黑树进行操作,这样就不需要像 select/poll 每次操作时都传入整个 socket 集合,只需要传入一个待检测的 socket,减少了内核和用户空间大量的数据拷贝和内存分配。
  2. epoll 使用事件驱动的机制,内核里面维护了一个链表来记录就绪事件,当某个 socket 有事件发生时,通过回调函数内核会将其将入到这个就绪事件列表中,当用户调用epoll_wait()时,只会返回有事件发生的文件描述符的个数,不需要像 select/poll 那样轮询扫描整个 socket 集合,大大提高了检测的效率。

epoll 相关接口的作用如下:

epoll 的方式监听的 Socket 数量越多的时候,效率不会大幅度降低,能够同时监听的 Socket 的数目也非常多,上限就为系统定义的进程打开的最大文件描述符个数。

epoll 支持两种事件触发模式,分别是边缘触发和水平触发。

  • 使用边缘触发时,当被监控的 Socket 描述符有可读事件发生时,服务器端只会从 epoll_wait 中苏醒一次,即时进程没有调用 read 函数从内核读取数据,也依然只苏醒一次,因此我们程序要保证一次性将内核缓冲区的数据读取完。
  • 使用水平触发模式时,当被监控的 Socket 上有可读事件发生时,服务端不断地从 epoll_wait 中苏醒,知道内核缓冲区数据被 read 函数读完才结束,目的是告诉我们有数据需要读取。

select/poll 只有水平触发模式,epoll 默认的触发模式是水平触发,但是可以根据应用场景设置为边缘触发模式。

巨人的肩膀:

https://mp.weixin.qq.com/s?__biz=MzUxODAzNDg4NQ==&mid=2247489558&idx=1&sn=7a96604032d28b8843ca89cb8c129154&chksm=f98e5cbccef9d5aa249c02489614d81ce865eacb165846df84721636cd4717d1aaa830dbec56&scene=178&cur_album_id=1408057986861416450#rd


文章作者: Gtwff
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Gtwff !
  目录