I/O 多路复用,网络编程中的select、poll、epoll的发展历史、原理详解以及代码实现(二)

selectpollepoll 是 Linux 中实现 I/O 多路复用的三种主要方法,它们的设计思想和实现原理各有不同,用于满足不同的场景需求。上一节介绍了他们三者的发展历史,本节我将继续介绍他们三者的详细原理。(PS:本系列文章面向的读者群体需要有一定的基本网络编程知识,若文章中出现的一些名词不清楚含义,恕笔者无法讲得事无巨细,读者可自行利用搜索引擎解决,或者在评论区留言交流)

 

1. select 原理

背景与设计

select 是最早的多路复用 I/O 实现方式,首次出现在 1983 年的 BSD Unix 中。它通过监控一组文件描述符的状态,等待其中的一个或多个文件描述符变为就绪(可读、可写或异常)。

原理

  1. 文件描述符集合
    • select 使用一个固定大小的位图集合(fd_set)来表示文件描述符。
    • 每个文件描述符对应一个位,标记是否需要被监听。
  2. 监控方式
    • 用户进程将需要监听的文件描述符设置到 fd_set 中。
    • 调用 select 时,内核会遍历所有文件描述符,检查其状态(是否可读、可写或异常)。
    • 如果一个或多个文件描述符变为就绪,select 返回,用户进程可以对就绪的文件描述符进行处理。
  3. 阻塞与超时
    • 用户可以指定 select 的阻塞方式(阻塞、非阻塞或超时等待)。

实现流程

  1. fd_set 从用户态复制到内核态。
  2. 内核遍历文件描述符集合,检查每个描述符的状态。
  3. 如果至少一个文件描述符就绪,返回到用户态;否则阻塞或超时返回。
  4. 用户进程遍历整个 fd_set,找出哪些文件描述符是就绪的。

问题与局限性

  1. 性能瓶颈
    • 每次调用都需要将文件描述符集合从用户态复制到内核态(开销较大)。
    • 内核需要遍历整个文件描述符集合,时间复杂度为 O(n)
  2. 文件描述符限制
    • 文件描述符数量有限,通常为 1024(可以通过修改内核参数增加,但增加后性能可能受影响)。
  3. 效率低下
    • 大量文件描述符中只有少数就绪时,效率较低。

2. poll 原理

背景与设计

pollselect 的改进版本,引入于 1986 年的 System V Unix。它解决了 select 文件描述符数量固定的限制,但仍然存在性能问题。

原理

  1. 文件描述符列表
    • poll 使用一个动态数组(struct pollfd)来存储需要监听的文件描述符及其事件。
    • 每个 pollfd 结构体包含文件描述符、感兴趣的事件类型(如可读、可写)以及实际发生的事件。
  2. 监控方式
    • 用户进程将所有需要监听的文件描述符及其事件类型填入 pollfd 数组。
    • 调用 poll 后,内核检查每个文件描述符的状态,将发生的事件写回 pollfd 数组。
  3. 非阻塞与超时
    • poll 支持非阻塞和超时操作。

实现流程

  1. pollfd 数组从用户态复制到内核态。
  2. 内核遍历数组中的所有文件描述符,检查其状态。
  3. 如果至少一个文件描述符发生事件,poll 返回;否则阻塞或超时返回。
  4. 用户遍历 pollfd 数组,处理发生事件的文件描述符。

优点

  1. 支持任意数量的文件描述符:
    • poll 的文件描述符数量不再受固定大小限制(与 select 相比)。
  2. 接口更灵活:
    • poll 通过 pollfd 数组描述文件描述符和事件,扩展性更好。

问题与局限性

  1. 性能问题
    • select 一样,poll 每次调用都需要将整个 pollfd 数组从用户态复制到内核态。
    • 内核仍需要遍历所有文件描述符,时间复杂度为 O(n)
  2. 无事件通知
    • 即使只有少数文件描述符发生事件,poll 仍需遍历整个数组。

 

3. epoll 原理

背景与设计

epoll 是 Linux 特有的高性能 I/O 多路复用机制,于 2002 年在 Linux 2.5.44 中引入。它解决了 selectpoll 的性能瓶颈,特别适用于高并发场景。

原理

epoll 的核心思想是 事件驱动,通过注册机制只监听真正发生事件的文件描述符,避免遍历整个集合。

  1. 内核数据结构
    • epoll 通过内核维护一个 红黑树 用于管理所有需要监听的文件描述符。
    • 另有一个 就绪队列(双向链表),存储已发生事件的文件描述符。
  2. 事件注册
    • 文件描述符通过 epoll_ctl 一次性注册到内核的红黑树中。
    • 后续无需重复传递文件描述符。
  3. 事件通知
    • 当文件描述符发生事件时,内核会将其放入就绪队列。
    • 用户调用 epoll_wait 获取就绪的文件描述符。

触发模式

  • 水平触发(Level Triggered, LT)
    • 默认模式,当文件描述符状态仍未处理时会重复通知。
  • 边缘触发(Edge Triggered, ET)
    • 高效模式,仅在状态发生变化时通知。

实现流程

  1. 用户调用 epoll_create 创建一个 epoll 实例(在内核中创建红黑树和就绪队列)。
  2. 用户通过 epoll_ctl 将文件描述符及其事件添加到红黑树。
  3. 内核监听这些文件描述符的状态变化,发生事件时将其加入就绪队列。
  4. 用户调用 epoll_wait 获取就绪的文件描述符。

优点

  1. 高性能
    • 注册的文件描述符只需监听一次,避免重复传递。
    • 内核通过就绪队列只返回发生事件的文件描述符,时间复杂度为 O(1)
  2. 灵活触发模式
    • 支持水平触发(LT)和边缘触发(ET),适应不同的应用场景。
  3. 适合高并发
    • 在大量文件描述符场景下,epoll 的效率远高于 selectpoll

问题与局限性

  1. 复杂性
    • 相比 selectpollepoll 的使用和调试更加复杂。
  2. 内存消耗
    • 内核需要维护红黑树和就绪队列,占用更多内存。

4. 三者对比

特性selectpollepoll
文件描述符限制有限制(通常为 1024)无限制(动态数组)无限制
性能O(n)O(n)O(1) 或 O(就绪数)
内核交互每次复制整个集合每次复制整个数组注册一次,仅传递就绪事件
事件通知轮询全部轮询全部事件驱动,回调机制
触发模式水平触发水平触发水平触发 / 边缘触发
适用场景小型应用,低并发中小型应用高并发,高性能网络编程

 

5. 总结

  • select 是最早的多路复用机制,简单易用,但性能低下且有文件描述符数量限制。
  • poll 是对 select 的改进,移除了文件描述符数量限制,但仍然需要轮询整个集合,性能不够高。
  • epoll 是 Linux 高性能 I/O 的主流解决方案,采用事件驱动模型,适用于高并发和大规模文件描述符场景。

在实际应用中:

  • 对于小型应用或跨平台需求,可选择 selectpoll
  • 对于高并发场景,建议使用 epoll 或现代的 io_uring

 本节主要对selectpollepoll的原理进行介绍,原理介绍是从知识层面帮助学习者来理解,但网络编程终归是一项实践性质的工作,因此下一节将会带来selectpollepoll的代码示例。

https://github.com/0voice

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值