IO多路复用-select,poll以及epoll

本文介绍了Reactor模型在操作系统内核中的应用,特别是如何用于高效处理网络编程中的IO操作。讨论了select、poll和epoll三种IO多路复用机制,强调了epoll在性能和可扩展性方面的优势。此外,还提到了libevent和libev这样的事件处理库,以及Node.js作为基于Reactor模型的服务器端编程技术。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这部分内容属于操作系统内核,reactor模型也广泛应用于很多场景,比如Redis,这种注册channel然后回调式的告知真的很有效,面试的时候也是一个高频问题,所以对其有一定的了解是很重要的。

先说Reactor,Reactor 模型是一种应用于网络编程的设计模式,它基于事件驱动(event-driven)的方式实现了高效的IO多路服务。

Reactor 模型的优势包括:

  1. 高效性:Reactor 模型采用了事件驱动的方式,可以同时监听多个套接字并在有数据到达时及时处理,从而避免了传统的阻塞/轮询模式下频繁切换线程的开销,提高了系统的整体性能。

  2. 可扩展性:Reactor 模型可以通过增加监听套接字、调整事件处理器等方式来满足更高的并发性需求,具有很好的可扩展性。

  3. 灵活性:Reactor 模型采用了回调函数的方式,可以根据业务需求自由定制事件处理逻辑,具有很好的灵活性。

目前,有许多技术实现了 Reactor 模型,其中最常见的包括:

  1. select/poll/epoll:这些都是内核提供的IO多路复用机制,可以实现基于 Reactor 模型的网络编程。其中,epoll 是最常用的技术,因为它具有高效和可扩展性等优点。

  2. libevent/libev:这是两个常用的事件处理库,它们封装了 select/poll/epoll 等底层技术,提供了更高级别的事件处理接口和功能,方便开发者使用。

  3. Node.js:这是一种基于 JavaScript 的服务器端编程技术,它采用了基于 Reactor 模型的事件驱动方式,具有高效、轻量级和易学易用等特点,已经成为前端开发人员进入服务器端开发领域的首选技术。

虽然Reactor渐进演化出了很多不同的模式,但我们重点要知道的是:其核心是围绕事件驱动模型

  • 一方面监听并处理IO事件。
  • 另一方面将这些处理好的事件分发业务线程处理。

 IO多路服务是一种高效的网络编程技术,可以实现同时监听多个套接字(socket),并在有数据到达时及时处理。常见的IO多路服务有select、poll和epoll。

这三种技术都可以实现IO多路服务,但它们的实现方式有所不同。下面分别介绍这三种技术的特点和区别:

1. select

select 是最早出现的IO多路服务技术之一,它可以同时监听多个套接字,并在其中任何一个套接字上发生事件时通知应用程序。它的函数原型如下:

int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

其中,nfds 表示监听的最大文件描述符数;readfdswritefdsexceptfds 分别表示需要监听的可读、可写和异常条件的文件描述符集合;timeout 表示阻塞超时时间。

select 的缺点在于,它每次调用时需要将所有待监听的套接字从用户空间复制到内核空间,而且当套接字数量较大时,效率会降低。

具体的问题:

1)底层是用数组实现,这3个bitmap有大小限制(FD_SETSIZE,通常为1024);

2)由于这3个集合在返回时会被内核修改,因此我们每次调用时都需要重新设置;

3)每次调用select,都需要把fd_set集合从用户态拷贝到内核态,如果fd_set集合很大时,开销也很大;

4)每次调用select都需要在内核遍历传递进来的所有fd_set,需要扫描这3个fd集合,然后查看哪些fd的事件实际发生,在读/写比较稀疏的情况下同样存在效率问题.

2. poll

poll 是对 select 的改进,它也可以监听多个套接字,但与 select 不同的是,它使用一个 pollfd 数组来保存每个待监听套接字的状态信息,而不需要每次调用时重新复制。它的函数原型如下:

int poll(struct pollfd fds[], nfds_t nfds, int timeout);
struct pollfd {int fd;short events;short revents;}

其中,fds 是一个 pollfd 数组,它包含了每个待监听套接字的状态信息;nfds 表示数组中的元素个数;timeout 表示阻塞超时时间。

poll 的缺点在于,当需要监听的套接字数量很大时,效率也会下降。

poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态, 但是它没有最大连接数的限制,原因是它是基于链表来存储的.

poll调用需要传递的是一个pollfd结构的数组,调用返回时结果信息也存放在这个数组里面,pollfd的结构中存放着fd我们对该fd感兴趣的事件(events)以及该fd实际发生的事件(revents),poll传递的不是固定大小的bitmap,因此select的问题1解决了;poll将感兴趣事件和实际发生事件分开了,因此select的问题2也解决了,但select的问题3和问题4仍然没有解决.

3. epoll

epoll 是 Linux 内核提供的一种高效的IO多路服务机制,它使用了内核事件驱动(event-driven)方式来实现,并支持两种触发方式:边缘触发(edge-triggered)和水平触发(level-triggered)。

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 maxevents, int timeout);

epoll 使用了三个系统调用函数:epoll_create、epoll_ctl 和 epoll_wait。

其中,epoll_create 用于创建一个 epoll 对象,相当于一个context;

epoll_ctl 用于向 epoll 对象注册、修改或删除一个待监听套接字;

epoll_wait 用于等待 epoll 对象上的事件发生,一旦有事件发生就会返回。

具体执行过程:

epoll创建一个context,然后把对感兴趣的读写文件描述符保存到context,并指定一个回调函数。当设备就绪,就会调用这个回调函数,而这个回调函数会把就绪的fd设备加入一个就绪链表。epoll_wait的工作实际上就是在这个就绪链表中查看有没有就绪的fd.

epoll 的两种触发方式分别是:

  • 边缘触发:只在状态变化时通知应用程序,即只有当文件描述符从无状态变为有状态,或者从有状态变为无状态时才通知应用程序。这种方式可以减少不必要的事件通知,提高效率。
  • 水平触发:只要文件描述符有数据可读或可写,就会一直通知应用程序,即使应用程序没有处理完所有的事件。这种方式可以确保不会遗漏任何事件,但需要更多的系统开销。

总体来说,epoll 是实现IO多路服务的最佳选择,它具有高效、灵活和可扩展等优点,是 Linux 内核中最受欢迎的网络编程技术之一。

小结:

selectpoll        
操作方式遍历遍历回调
底层实现数组链表红黑树
IO效率每次调用都进行线性遍历,时间复杂度为O(n)每次调用都进行线性遍历,时间复杂度为O(n)事件通知方式,每当fd就绪,系统注册的回调函数就会被调用,将就绪fd放到readyList里面,时间复杂度O(1)
最大连接数1024(x86)或2048(x64)无上限无上限
fd拷贝每次调用select,都需要把fd集合从用户态拷贝到内核态每次调用poll,都需要把fd集合从用户态拷贝到内核态调用epoll_ctl时拷贝进内核并保存,之后每次epoll_wait不拷贝

参考资料:

高性能网络编程之 Reactor 网络模型(彻底搞懂) - 掘金 (juejin.cn)

IO多路复用的三种机制Select,Poll,Epoll - 简书 (jianshu.com)

Redis的I/O多路复用徐明晓的博客-优快云博客redis 多路复用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值