select、poll、epoll
创始人
2025-05-30 17:50:33
0

I/O多路复用这块内容最近找实习被提问率挺高的,重新梳理了下。

文章目录

  • select、poll、epoll
    • 1.select
    • 2.poll
    • 3.epoll
      • epoll的工作原理
      • epoll的优点
      • ET VS LT
  • epoll 具体工作流程
    • 简答版本:
    • 具体实例:
  • 参考资料

select、poll、epoll

I/O多路复用是指通过一种机制,让单个进程可以同时处理多个I/O事件,而不需要通过多线程或多进程来实现。这种机制可以提高网络应用程序的并发性能和响应速度。

在Linux下,常见的I/O多路复用机制有select、poll和epoll。

1.select

原理是在内核中维护一个文件描述符集合,通过调用select函数来监控这个集合中的文件描述符是否有可读、可写或异常等事件发生。当有事件发生时,select函数会返回,并将就绪的文件描述符返回给应用程序。

select 实现多路复用的方式是,将已连接的 Socket 都放到一个文件描述符集合,然后调用 select 函数将文件描述符集合拷贝到内核里,让内核来检查是否有网络事件产生,检查的方式很粗暴,就是通过遍历文件描述符集合的方式,当检查到有事件产生后,将此 Socket 标记为可读或可写, 接着再把整个文件描述符集合拷贝回用户态里,然后用户态还需要再通过遍历的方法找到可读或可写的 Socket,然后再对其处理。所以,对于 select 这种方式,需要进行 2 次「遍历」文件描述符集合,一次是在内核态里,一个次是在用户态里 ,而且还会发生 2 次「拷贝」文件描述符集合,先从用户空间传入内核空间,由内核修改后,再传出到用户空间中。

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

总结下select有以下几个缺点:

  • 文件描述符集合大小有限,一般默认是1024
  • 每次调用select函数都会发生文件描述符集合在用户态和内核态间的拷贝,效率较低。
  • 如果同时处理大量的文件描述符,select的效率会变得非常低。

2.poll

poll也是Unix下的I/O多路复用机制之一,与select类似,它的原理是在内核中维护一个文件描述符集合,通过调用poll函数来监控这个集合中的文件描述符是否有可读、可写或异常等事件发生。当有事件发生时,poll函数会返回,并将就绪的文件描述符返回给应用程序。

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

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

3.epoll

epoll是Linux下的新一代I/O多路复用机制,它采用了事件驱动(Event-Driven)方式,可以处理大量的文件描述符,并且只需遍历那些发生事件的文件描述符。这使得epoll具有更高的效率和更低的延迟。

epoll的工作原理

epoll的工作原理是在内核中维护一个红黑树和一个双向链表。

红黑树用于存储要监控的文件描述符,它的每个节点都对应一个文件描述符,每个节点中存储有文件描述符的相关信息,比如是否可读、是否可写等等。

双向链表用于存储就绪的文件描述符及其对应的事件类型,当某个文件描述符就绪时,它会被加入到这个链表中,等待应用程序调用epoll_wait函数来处理。

epoll_wait函数在等待就绪的文件描述符时,实际上是将红黑树中所有就绪的文件描述符节点取出来,然后将它们对应的文件描述符和事件类型存储到双向链表中,最后将链表中的数据返回给应用程序。

由于红黑树的查找和插入操作的时间复杂度都是O(logN),所以epoll能够高效地处理大量的文件描述符。同时,由于双向链表只用于存储就绪的文件描述符,所以它的长度通常比较短,而且每次只需要遍历一次就能够获取所有就绪的文件描述符,因此epoll的性能也比较高。

总结一下:epoll 通过两个方面解决了 select/poll 的问题。

图片

1、epoll 在内核里使用「红黑树」来关注进程所有待检测的 Socket,红黑树是个高效的数据结构,增删改一般时间复杂度是 O(logn),通过对这棵黑红树的管理,不需要像 select/poll 在每次操作时都传入整个 Socket 集合,减少了内核和用户空间大量的数据拷贝和内存分配。

2、epoll 使用事件驱动的机制,内核里维护了一个「链表」来记录就绪事件,只将有事件发生的 Socket 集合传递给应用程序,不需要像 select/poll 那样轮询扫描整个集合(包含有和无事件的 Socket ),大大提高了检测的效率。

epoll的优点

相对于select和poll,epoll具有以下几个优点:

  • 没有文件描述符集合大小的限制,支持大量的文件描述符。
  • 只需要在初始化时拷贝一次文件描述符集合到内核态,效率比select和poll更高。
  • 可以处理大量的文件描述符,无论是活跃的还是不活跃的。
  • 可以处理多个事件类型,包括可读、可写、异常等。
  • 支持边缘触发和水平触发两种模式。

ET VS LT

边缘触发(Edge-Triggered)是指当文件描述符上有新的事件发生时,内核会通知应用程序,只通知一次,应用程序需要立即对文件描述符进行操作,否则会导致数据丢失。这种模式需要应用程序具有较高的并发处理能力和良好的状态机设计。(需要一次性处理完!)

水平触发(Level-Triggered)是指当文件描述符上有新的事件发生时,内核会通知应用程序,并且一直通知,直到事件处理完毕。这种模式比较适合于普通的应用程序。

epoll 具体工作流程

简答版本:

  1. 通过epoll_create创建epoll对象,此时epoll对象的内核结构包含就绪链表和红黑树,就绪队列是用于保存所有读写事件到来的socket。红黑树用于保存所有待检测的socket。
  2. 通过 epoll_crt 将待检测的socket,加入到红黑树中,并注册一个事件回调函数,当有事件到来的之后,会调用这个回调函数,进而通知到 epoll 对象。
  3. 调用 epoll_wait 等待事件的发生,当内核检测到事件发生后,调用该socket注册的回调函数,执行回调函数就能找到socket对应的epoll对象,然后会将事件加入到epoll对象的绪队列中,最后将就绪队列返回给应用层。

具体实例:

下面通过一个简单的例子来介绍epoll的使用。假设我们有一个服务器程序,需要同时处理多个客户端的连接请求。为了实现这个功能,我们可以使用epoll来监控多个套接字的I/O事件,并在事件触发后及时进行处理。

当调用epoll_wait()返回时,可以根据事件的类型进行处理。下面是使用epoll的一些常见步骤:

1.创建一个 epoll 句柄(文件描述符)

int epoll_fd = epoll_create(max_events);// 创建epoll文件描述符,max_events表示要监听的文件描述符数量

创建一个 epoll 文件描述符,max_events 是该 epoll 对象能监听的最大文件描述符数量

2.将需要监听的文件描述符添加到 epoll 集合中

struct epoll_event event;
event.events = EPOLLIN | EPOLLET;
event.data.fd = fd;// 监听的文件描述符
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event);// 将fd添加到epoll_fd中进行监听

创建一个 epoll_event 结构体,设置需要监听的事件类型和文件描述符,然后将其添加到 epoll 中。

其中,EPOLLIN 表示可读事件,EPOLLOUT 表示可写事件,EPOLLET 表示采用边缘触发方式。EPOLL_CTL_ADD 表示添加一个文件描述符到 epoll 中。

3.等待事件的发生

struct epoll_event events[max_events];
int nfds = epoll_wait(epoll_fd, events, max_events, timeout);// 等待事件发生,最多返回max_events个事件

调用 epoll_wait 等待事件的发生。max_events 表示一次最多能够处理的事件数,timeout 表示超时时间。函数返回时,返回的是发生事件的文件描述符的个数。

4.处理事件

for (int i = 0; i < nfds; i++) {if (events[i].data.fd == fd) {// 处理文件描述符为 fd 的事件}
}

根据返回的事件类型和文件描述符,处理相应的事件。可以使用 events[i].events & EPOLLIN 来判断事件类型,events[i].data.fd 来获取文件描述符。

5.删除文件描述符

epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, &event);

当不再需要监听某个文件描述符时,可以将其从 epoll 中删除。

其中,EPOLL_CTL_DEL 表示删除一个文件描述符从 epoll 中。

6.关闭 epoll 对象

close(epoll_fd);

当不再需要 epoll 对象时,需要将其关闭以释放资源。

以上就是使用 epoll 的常见步骤。使用 epoll 可以大大提高网络编程的效率和可靠性。

参考资料

1.《Linux多线程服务端编程:使用muduo C++网络库》
2.《Linux高性能服务器编程》
3. 小林coding的图解系列

相关内容

热门资讯

ProxySQL集成MHA的单... 说明:MHA为主从复制的MySQL集群提供了主节点故障转移的功能,但是如...
职业生涯感悟随笔   我们从事工作这么久,怎么才能知道自己做的怎么样呢?这个时候就需要你多做一些职业上的感悟,下面就由...
我的开学第一天随笔日记的 我的开学第一天随笔日记的范本  引导语:开学,汉语解释有开设学校、启作学者、学期开始三个释义。但在日...
读《桃花心木》的读有感 读《桃花心木》的读有感  【篇一:读《桃花心木》有感】  当我一看到《桃花心木》这个题目时,我心想:...
小学信息技术教学随笔 小学信息技术教学随笔  信息技术和学科的整合不仅是学生学习方式的革命,也是教师教学方法的全方位变革,...
Spark Streaming... 一、文件流 在文件流的应用场景中,需要编写Spark Streaming程序ÿ...
通过form表单,ajax构造... 一,form表单构造form表单中重要参数:action:...
暑假心情随笔 暑假心情随笔(精选12篇)  在日常学习、工作生活中,大家一定没少看到别人写的随笔吧?随笔是一种散文...
相机中的你们 相机中的你们  相机,记录了我们的相处的点点滴滴,是一种美好的回忆。下面是小编为大家带来了相机中的你...
刚入学大班教育随笔 刚入学大班教育随笔(通用11篇)  在日常学习和工作生活中,大家一定没少看到别人写的随笔吧?随笔是散...
新学期随笔作文 新学期随笔作文三篇  新学期来了,大家都写了哪些随笔呢?下面是小编精心为你整理新学期随笔作文,希望你...
Bluetooth LE AT... 一,简介ESP-AT 当前仅支持 Bluetooth LE 4.2 协议规范ÿ...
2.2 离散型随机变量 引入:有些随机变量,它全部可能取到的值是有限个或可列无限多个,这种随机变量称为离散型随...
情感日志心情随笔 情感日志心情随笔  在学习、工作或生活中,大家对日志都再熟悉不过了吧,此时此刻我们需要写一篇日志了。...
小学数学教学反思随笔 小学数学教学反思随笔  在日复一日的学习、工作生活中,大家都知道随笔吧?随笔是一种散文体裁,可以抒情...
幼儿中班教师教育随笔   教师是人类文化科学知识的继承者和传播者。对学生来说,又是学生智力的开发者和个性的塑造者。下面是小...
荒凉里的眼泪随笔散文 荒凉里的眼泪随笔散文  荒凉里的太阳仍旧照着,微风吹起路上的土打着圈儿,冬麦的绿色在田里挣扎着,淡绿...
LeetCode1436. 旅... 题目 给你一份旅游线路图,该线路图中的旅行线路用数组 paths 表示,...
巧用提示语,说说话就能做个聊天... 你好,我是徐文浩。 这一讲,我们来看看Open AI提供的Complet...
【全民Python】Pytho... 目录 一.前言 二.xlrd xlwt xlutils库的安装 三.Excel的读写改 1.创建Ex...