【如何理解select、poll、epoll?】

select、poll、epoll

select、poll、epoll都是Linux中常见的I/O多路复用技术,他们可以用于同时监听多个文件描述符(filedescriptor,后文我会简称为fd,不好看到fd大家看不明白),当任意一个文件描述符就绪时,就能够非阻塞的读写数据。

  • select是最原始的I/O多路复用技术,它的缺点是最多只能监听1024个文件描述符。
  • poll在select的基础之上增加了支持监听更多的文件描述符的能力,但是复杂度随着监听的文件描述符数量的增加而增加。
  • epoll在poll的基础之上进一步优化了复杂度,可以支持更多的文件描述符,并且具有更高的效率。

select

函数签名如下:

int select (int n,fd_set * readfds, fd_set * writefds, fd_set * exceptfds, struct timeVal * timeout);

select函数可以监听read、write、except的fd。当select返回后,可以遍历对应的fd_set来寻找就绪的fd,从而进行业务处理。

select诞生比较早,几乎在所有的平台中都支持。但是select有个缺点就是单个进程能够监听的文件描述符的数量存在最大限制,在Linux上一般为1024,可以通过修改宏定义甚至重新编译内核的方式提升这一限制,但是这样也会造成效率的降低。

除此之外,包含大量的fd的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,其开销也随着文件描述符数量增加而线性增大。

poll

函数签名如下:

int poll (struct pollfd *fds,unsigned int nfds, int timeout);
struct pollfd {
   
    int fd;/*file descriptor*/
    short events;/*requested events to watch*/
    short revents;/*returned events witnessed*/
}

同select一样,poll返回后,也需要轮询pollfd来获取就绪的fd。不仅如此,所有的fds也是在内核态和用户态中来回切换,也会影响效率。

但是,因为fds基于链表,所以就没有了最长1024的限制。

epoll

epoll基于Linux2.4.5,函数签名如下:

//创建一个epoll的句柄,size用来告诉内核,这个监听的数目一共有多大
int epoll_create(int size);
//注册要监听的事件类型
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
//等待事件发生
int epoll_wait(int epfd, struct epoll_event *events, int maxeents, int timeout)

每次注册新的事件调用epoll_ctl时,epoll会把所有的fd拷贝进内核,而不是在epoll_wait的时候重复拷贝。epoll保证了每个fd在整个过程中只会拷贝一次。

同时,epoll会通过epoll_wait查看是否有就绪的fd,如果有就绪的fd,就会直接使用(O(1))。而不是像之前两个一个,每次需要手动遍历才能得到就绪的fd(O(n))

除此之外,它所支持的fd上线是最大可以打开文件的数目,这个数据一般来讲会远大于2048,举个例子,在1Gb内存的机器上大约是10万左右,具体数目可以看cat/proc/sys/fs/file-max查看,一般来讲这个数目和内存关系是很大的。

知识扩展

三者之间的主要区别是什么?

在这里插入图片描述

epoll的两种模式是什么?

我们直到epoll是通过epoll_wait来获取就绪的fd,那么如果就绪的fd一直没有被消费,该如何处理呢?这就有了两种模式。
LT(level trigger)和ET(edge trigger)。LT模式是默认模式,LT模式与ET模式的区别如下:

  • LT模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件
  • ET模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件,如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。

因为ET模式在很大程度上减少了epoll事件被重置触发的次数,因此效率要比LT模式高。epoll工作在ET模式的时候,必须使用非阻塞socket,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。

相关推荐

  1. 如何理解闭包

    2023-12-14 07:18:04       22 阅读
  2. 如何理解AI Agent

    2023-12-14 07:18:04       15 阅读
  3. 如何理解React

    2023-12-14 07:18:04       19 阅读
  4. 如何理解JVM

    2023-12-14 07:18:04       14 阅读
  5. 如何理解数据库事务

    2023-12-14 07:18:04       14 阅读
  6. 如何理解 HTTP 状态码?

    2023-12-14 07:18:04       20 阅读

最近更新

  1. TCP协议是安全的吗?

    2023-12-14 07:18:04       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2023-12-14 07:18:04       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2023-12-14 07:18:04       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2023-12-14 07:18:04       18 阅读

热门阅读

  1. 【Python爬虫】Python爬虫入门教程&注意事项

    2023-12-14 07:18:04       41 阅读
  2. Ceph入门到精通-ceph二次开发开源协议考虑

    2023-12-14 07:18:04       36 阅读
  3. Android 12.0 默认相机视频画质1080p

    2023-12-14 07:18:04       35 阅读
  4. Mysql - 引擎介绍

    2023-12-14 07:18:04       32 阅读