当前位置: 首页 > news >正文

【C++】IO多路复用(select、poll、epoll)

目录

  • 1.select
    • 1.1 介绍
    • 1.2 API 接口
    • 1.3 文件描述符集合(fd_set)的操作
    • 1.4 select 的工作原理
    • 1.5 select 的优缺点
  • 2.poll
    • 2.1 介绍
    • 2.2 API
    • 2.3 struct pollfd 结构体
    • 2.4 事件类型(events 和 revents 的取值):
    • 2.5 poll 的工作原理
    • 2.6 poll 与 select 的对比
    • 2.7 适用场景
  • 3. epoll
    • 3.1 介绍
    • 3.2 epoll 的核心优势
    • 3.3 struct epoll_event 结构体
    • 3.4 epoll 的工作原理
    • 3.5 触发模式

在这里插入图片描述

1.select

1.1 介绍

select 是早期 Unix 系统中经典的 I/O 多路复用技术,通过维护三个文件描述符集合(读、写、异常),调用时阻塞等待集合中任一文件描述符就绪。它的核心缺陷在于集合大小受系统宏定义限制(通常为 1024),且每次调用需将整个集合从用户态拷贝到内核态,同时就绪后需遍历整个集合才能找到活跃的文件描述符,在高并发场景下效率会显著下降,仅适用于连接数较少的简单场景。

1.2 API 接口

#include <sys/select.h>
#include <sys/time.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
  • 参数说明:
    • nfds:需要监控的文件描述符的范围,值为所有待监控文件描述符中的最大值 + 1(用于优化内核遍历范围)。
    • readfds读文件描述符集合,用于监控哪些文件描述符可读。函数返回时,会清除未就绪的描述符,仅保留就绪的。
    • writefds写文件描述符集合,用于监控哪些文件描述符可写。函数返回时,会清除未就绪的描述符,仅保留就绪的。
    • exceptfds异常文件描述符集合,用于监控文件描述符的异常事件。函数返回时,会清除未就绪的描述符,仅保留就绪的。
    • timeout:超时时间(struct timeval 类型),控制 select 的阻塞行为:
      • NULL:select 一直阻塞,直到有文件描述符就绪。
      • tv_sec=0 且 tv_usec=0:不阻塞,立即返回(轮询模式)。
      • 其他值:阻塞指定的秒数(tv_sec)和微秒数(tv_usec),超时后返回 0。
  • 返回值:
    • 成功:返回就绪的文件描述符总数(读、写、异常集合中就绪的总和)。
    • 失败:返回 -1,并设置 errno(如被信号中断则 errno=EINTR)。
    • 超时:返回 0(没有文件描述符就绪)。

1.3 文件描述符集合(fd_set)的操作

fd_set 是一个位图结构(本质是整数数组),每个位代表一个文件描述符是否被监控。系统提供了以下宏来操作 fd_set

作用
FD_ZERO(fd_set *)清空集合(所有位设为 0)
FD_SET(int fd, fd_set *)将文件描述符 fd 添加到集合(置位)
FD_CLR(int fd, fd_set *)从集合中移除 fd(清位)
FD_ISSET(int fd, fd_set *)检查 fd 是否在集合中(是否就绪)

1.4 select 的工作原理

  1. 初始化集合:调用 FD_ZERO 清空读、写、异常集合,再用 FD_SET 将需要监控的文件描述符添加到对应集合中。
  2. 调用 select:内核会阻塞等待,直到以下事件发生:
    - 某个监控的文件描述符就绪(读 / 写 / 异常)。
    - 超时时间到达。
    - 被信号中断。
  3. 处理就绪描述符:select 返回后,通过 FD_ISSET 检查每个文件描述符是否在就绪集合中,进而处理对应的 I/O 操作。

注意:select 会修改输入的 fd_set 集合(仅保留就绪的描述符),因此每次调用前需要重新初始化集合。

1.5 select 的优缺点

  • 优点:
    • 跨平台性好(支持 Unix/Linux、Windows 等)。
    • 简单易用,适合监控少量文件描述符的场景。
  • 缺点:
    • 文件描述符数量限制:受限于 FD_SETSIZE(通常为 1024),默认最多监控 1024 个文件描述符(可通过修改内核参数调整,但不推荐)。
    • 效率随描述符数量下降:每次调用 select 时,内核需要遍历所有监控的描述符检查就绪状态,当描述符数量庞大时,效率显著降低。
    • 集合需重复初始化:select 会修改输入的 fd_set,因此每次调用前必须重新设置集合,增加了代码复杂度。
    • 内核 / 用户空间拷贝开销:每次调用 select 时,fd_set 需从用户空间拷贝到内核空间,就绪后再拷贝回用户空间,存在性能开销。

2.poll

2.1 介绍

poll 是对 select 的改进,采用动态数组(pollfd 结构体数组)替代固定大小的文件描述符集合,从根本上突破了连接数限制,且无需区分读、写、异常三类集合,只需通过结构体中的 events 和 revents 字段标记关注事件与就绪事件。但 poll 未解决 select 的核心性能瓶颈 —— 每次调用仍需将整个数组拷贝到内核态,且就绪后仍需遍历所有元素查找活跃连接,因此在大量连接仅少数活跃的高并发场景中,效率依然较低。

2.2 API

#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
  • 参数说明:
    • fds:指向 struct pollfd 结构体数组的指针,每个结构体描述一个待监控的文件描述符及其关注的事件。
    • nfds:数组 fds 中元素的数量(即需要监控的文件描述符总数)。
    • timeout:超时时间(毫秒),控制 poll 的阻塞行为:
      • > 0:阻塞 timeout 毫秒后返回(若期间无事件就绪)。
      • = 0:不阻塞,立即返回(轮询模式)。
      • = -1:一直阻塞,直到有文件描述符就绪或被信号中断。
  • 返回值:
    • 成功:返回就绪的文件描述符总数(所有事件类型中就绪的总和)。
    • 失败:返回 -1,并设置 errno(如被信号中断则 errno=EINTR)。
    • 超时:返回 0(无就绪文件描述符)。

2.3 struct pollfd 结构体

poll 通过 struct pollfd 结构体定义每个文件描述符的监控信息,结构如下:

struct pollfd {int   fd;         // 待监控的文件描述符(若为-1,poll会忽略该结构体)short events;     // 关注的事件(输入参数,由用户设置)short revents;    // 实际发生的事件(输出参数,由内核填充)
};

2.4 事件类型(events 和 revents 的取值):

事件宏含义
POLLIN可读事件(如数据到达、管道可读、套接字关闭)
POLLOUT可写事件(如缓冲区未满,可写入数据)
POLLERR错误事件(无需在 events 中设置,内核自动检测)
POLLHUP挂断事件(如管道另一端关闭、套接字连接关闭)
POLLNVAL文件描述符无效(如未打开)

说明:

  • events 由用户设置,指定需要监控的事件(如 POLLIN | POLLOUT 表示同时监控可读和可写)。
  • revents 由内核在 poll 返回时填充,指示该文件描述符实际发生的事件(可能包含 events 中未设置的事件,如 POLLERR)。

2.5 poll 的工作原理

  • 初始化 pollfd 数组:为每个需要监控的文件描述符创建 struct pollfd 结构体,设置 fd 和关注的 events(如 POLLIN)。
  • 调用 poll:内核阻塞等待,直到以下情况发生:
    • 某个文件描述符的 events 事件就绪。
    • 超时时间到达。
    • 被信号中断。
  • 处理就绪事件:poll 返回后,遍历 pollfd 数组,通过检查 revents 确定哪些文件描述符就绪,并处理对应的 I/O 操作(如读 / 写数据)。
  • 关键特点:poll 不会修改 fds 数组中的 fd 和 events,仅更新 revents,因此无需像 select 那样每次调用前重新初始化集合。

2.6 poll 与 select 的对比

poll 的优点:

  1. 无文件描述符数量限制:select 受限于 FD_SETSIZE(默认 1024),而 poll 仅受系统内存和内核参数限制,可监控更多文件描述符。
  2. 无需重复初始化监控集合:select 会修改输入的 fd_set,每次调用前需重新设置;poll 仅修改 revents,fd 和 events 保持不变,可重复使用。
  3. 事件分离更清晰:select 通过三个独立集合区分读、写、异常事件,而 poll 用一个结构体同时包含所有事件类型,逻辑更简洁。
    poll 的缺点:
  4. 效率仍随描述符数量下降:与 select 类似,poll 返回后需遍历所有监控的描述符(通过 revents 检查就绪状态),当数量庞大时(如上万),遍历开销显著。
  5. 内核 / 用户空间拷贝开销:每次调用 poll 时,pollfd 数组需从用户空间拷贝到内核空间,就绪后无需拷贝回(仅修改用户空间的 revents),但仍有一定开销。
  6. 跨平台支持不如 select:Windows 系统不原生支持 poll(需通过模拟实现),而 select 是跨平台的。

2.7 适用场景

poll 适用于需要监控的文件描述符数量超过 1024(突破 select 限制),但并发量又不极端(如几千级别)的场景。例如中小型服务器、多设备监控等。
若需处理更高并发(如几万到几十万连接),Linux 系统推荐使用 epoll,BSD 系(如 macOS)推荐使用 kqueue,它们采用事件驱动模式,无需遍历所有描述符,效率更高。

3. epoll

3.1 介绍

epoll 是 Linux 特有的高性能 I/O 多路复用技术,通过 “事件驱动” 模式彻底优化性能:它先通过 epoll_ctl 注册文件描述符及关注事件,内核维护一棵红黑树存储这些文件描述符,避免每次调用的拷贝与遍历;当文件描述符就绪时,内核会将其加入就绪链表epoll_wait 只需直接读取该链表即可获取活跃连接,无需遍历全部注册项。此外,epoll 支持水平触发(LT)和边缘触发(ET)两种模式,ET 模式可进一步减少内核与用户态的交互次数,使其在高并发、高连接数的网络编程(如 Nginx、Redis)中成为首选方案。

3.2 epoll 的核心优势

与 select/poll 相比,epoll 的核心改进在于:

  • 事件驱动机制:无需轮询所有文件描述符,仅返回就绪的描述符,效率随并发量增长影响小。
  • 无文件描述符数量限制:仅受系统内存和内核参数(如 /proc/sys/fs/file-max)限制,可支持十万级以上连接。
  • 减少内核 / 用户空间拷贝:通过内存映射(mmap)共享事件数据,避免频繁数据拷贝。
  • 支持边缘触发(ET)和水平触发(LT):灵活适应不同 I/O 模式需求。
    epoll 的核心函数
    epoll 操作通过三个核心函数完成:epoll_createepoll_ctlepoll_wait
  1. 创建 epoll 实例
#include <sys/epoll.h>int epoll_create(int size);
  • 作用:创建一个 epoll 实例(内核数据结构),用于管理监控的文件描述符。
  • 参数:size 是早期内核用于提示所需监控的文件描述符数量,现代内核已忽略此值(但需传入大于 0 的数)。
  • 返回值:成功返回 epoll 实例的文件描述符(epfd),失败返回 -1 并设置 errno。
  1. 管理监控的文件描述符
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
作用:向 epoll 实例添加、修改或删除需要监控的文件描述符及其事件。
参数:- epfd:epoll_create 返回的实例描述符。- op:操作类型:- EPOLL_CTL_ADD:添加文件描述符 fd 到 epfd。- EPOLL_CTL_MOD:修改 fd 的监控事件。- EPOLL_CTL_DEL:从 epfd 中删除 fd(此时 event 可设为 NULL)。- fd:需要监控的文件描述符。- event:struct epoll_event 结构体,描述监控的事件和附加数据(见下文)。
返回值:成功返回 0,失败返回 -1 并设置 errno。
  1. 等待事件就绪
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

作用:阻塞等待 epoll 实例中监控的文件描述符就绪,返回就绪的事件。
参数:
- epfd:epoll 实例描述符。
- events:用户空间数组,用于存储内核返回的就绪事件(输出参数)。
- maxevents:events 数组的最大容量(必须大于 0)。
- timeout:超时时间(毫秒):
- > 0:阻塞 timeout 毫秒后返回。
- = 0:不阻塞,立即返回。
- = -1:一直阻塞,直到有事件就绪或被信号中断。
返回值:
- 成功:返回就绪的事件数量(>0)。
- 超时:返回 0。
- 失败:返回 -1 并设置 errno(如被信号中断则 errno=EINTR)。

3.3 struct epoll_event 结构体

struct epoll_event {uint32_t     events;  // 监控的事件(输入)或就绪的事件(输出)epoll_data_t data;    // 附加数据(用户自定义,如文件描述符、指针等)
};// 附加数据的联合体
typedef union epoll_data {void    *ptr;  // 指向用户数据的指针int      fd;   // 文件描述符(最常用)uint32_t u32;  // 32位整数uint64_t u64;  // 64位整数
} epoll_data_t;
事件宏含义
EPOLLIN 可读事件(如数据到达、套接字关闭)
EPOLLOUT 可写事件(如缓冲区未满,可写入数据)
EPOLLERR 错误事件(无需手动设置,内核自动监控)
EPOLLHUP 挂断事件(如连接关闭)
EPOLLET 边缘触发模式(Edge Triggered)
EPOLLONESHOT 一次性监控(事件触发后自动移除监控)

3.4 epoll 的工作原理

  • 创建 epoll 实例:epoll_create 在内核中创建一个 eventpoll 结构体(包含红黑树和就绪链表):
    • 红黑树:存储所有被监控的文件描述符及其事件(高效增删改查)。
    • 就绪链表:存储就绪的文件描述符(避免轮询,直接返回就绪事件)。
  • 添加 / 修改 / 删除监控对象:通过 epoll_ctl 操作红黑树,将文件描述符 fd 及其事件注册到内核。内核会为 fd 注册回调函数,当 fd 就绪时,回调函数将其加入就绪链表。
  • 等待就绪事件:epoll_wait 检查就绪链表,若有就绪事件,直接将事件复制到用户空间的 events 数组并返回;若无,则阻塞等待(直到超时或被唤醒)。
    epoll 的优缺点
    优点:
  • 高性能:基于事件驱动,无需轮询所有描述符,效率随并发量增长稳定。
  • 支持大量连接:无 select 的 1024 限制,可轻松支持十万级以上连接。
  • 灵活的触发模式:LT 模式简单易用,ET 模式适合高性能场景。
  • 低开销:通过红黑树管理描述符,增删改查效率高;通过 mmap 共享数据,减少拷贝。
    缺点:
  • 平台依赖性:仅 Linux 系统支持,不跨平台(BSD 用 kqueue,Windows 用 IOCP)。
  • ET 模式编程复杂:需配合非阻塞 I/O 并确保一次性处理完数据,否则可能遗漏事件。

3.5 触发模式

  • 水平触发(Level Triggered,默认模式)
    当 fd 上可读 / 可写时,epoll 会持续通知应用程序,直到数据被完全处理(读 / 写完成)。

  • 边缘触发(Edge Triggered,需设置 EPOLLET)
    仅在 fd 的状态发生变化瞬间触发事件(例如,从不可读变为可读、从不可写变为可写),之后即使数据未处理完,也不会再次通知,直到下次状态变化。

http://www.dtcms.com/a/589300.html

相关文章:

  • 高低温环境下DC-DC芯片启动行为对比研究
  • IntelliJIdea 工具新手操作技巧
  • 第3节 STM32 串口通信
  • 网站页面优化内容包括哪些科技信息网站建设的背景
  • 网站做的关键词被屏蔽百度云盘做网站空间
  • 打砖块——反弹算法与碰撞检测
  • 大连网站设计报价建设网站的策划书
  • 何超谈“AI元宇宙将引领场景革命 “十五五”勾勒科技新蓝图”
  • watch监视-ref基本类型数据
  • 基于单片机的超声波人体感应PWM自动调光灯设计与实现
  • 保定微网站 建设郑州网站建设361
  • [Java EE] 计算机基础
  • 【Playwright自动化】安装和使用
  • logstatsh push 安装
  • C# OpenCVSharp实现Hand Pose Estimation Mediapipe
  • Java和.NET的核心差异
  • 基于灰关联分析与数据场理论的雷达信号分选优化方法
  • Linux Socket 编程全解析:UDP 与 TCP 实现及应用
  • 【NTN卫星通信】什么是LEO卫星技术
  • 郑州市建网站个人对网络营销的看法
  • 罗湖网站建设公司上海seo推广公司
  • 厦门市小学生计算机 C++语言竞赛(初赛)题目精讲与训练(整数的数据类型)
  • VC:11月9日加更,结构行情
  • 杨和网站设计河北邯郸永利ktv视频
  • 里氏替换原则Liskov Substitution Principle,LSP
  • 享元设计模式
  • VitaBench:智能体在真实交互任务中的挑战与前沿探索
  • 深度学习:python动物识别系统 YOLOv5 数据分析 可视化 Django框架 pytorch 深度学习 ✅
  • 【数据库 | 基础】DDL语句以及数据类型
  • 视觉元素网站浙江建设职业技术学院迎新网站