IO 多路复用:select、poll、epoll 核心函数解析

摘要 :IO 多路复用是高并发网络编程核心,本文聚焦select、poll、epoll三组核心系统调用,梳理函数接口、底层行为、优缺点与关键注意点,适合快速复习与面试。

前言

IO 分为两个阶段:等待事件就绪 、数据拷贝。IO 多路复用的作用:把多个文件描述符交给内核监控,fd 就绪后通知用户进程,单线程管理大量连接。

注意:IO 多路复用属于同步 IO ,数据拷贝依旧需要用户进程调用read/recv完成。

1 select

函数原型

cs 复制代码
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
           fd_set *exceptfds, struct timeval *timeout);

参数

  1. nfds:最大监听 fd + 1,内核扫描范围 0 ~ nfds‑1。
  2. readfds/writefds/exceptfds:可读、可写、异常 fd 集合,位图fd_set。

内核会修改这三个集合:就绪位置 1,未就绪位清零。每次调用 select 前必须重新设置 fd_set。

  1. timeout:超时时间,结构体本身会被 select 修改,每次调用需要重新赋值。
    • NULL:永久阻塞;
    • {0,0}:不阻塞,轮询一次直接返回。

返回值

  • 0:就绪 fd 数量;

  • =0:超时;
  • <0:出错。

fd_set 操作宏

cs 复制代码
FD_ZERO(set);    //清空集合
FD_SET(fd, set); //添加fd
FD_CLR(fd, set); //删除fd
FD_ISSET(fd, set);//判断fd是否就绪

核心缺点

  1. 默认上限1024个 fd,受FD_SETSIZE限制;
  2. 每次调用,全部 fd 集合需要用户态↔内核态拷贝;
  3. O (n) 轮询;返回后需要遍历全部 fd 判断就绪。

优点:跨平台兼容性好。

2 poll

函数原型

cs 复制代码
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

struct pollfd {
    int fd;
    short events;  //【入参】用户设置要监听的事件 POLLIN / POLLOUT
    short revents; //【出参】内核填充,标记已经发生的事件
};

参数

  1. fds:pollfd结构体数组;events用户填写,内核只读;就绪事件写进revents。
  2. nfds:数组有效元素个数。
  3. timeout:超时,单位 ms;‑1永久阻塞,0立即返回。

返回值

  • 0:就绪 fd 数量;

  • =0:超时;
  • <0:出错。

改进点

  1. 取消 1024 的 fd 上限,内核使用链表 + 动态数组存储;
  2. events与revents分离,不需要每次重置监听事件。

遗留问题

  1. O (n) 轮询全部 fd;
  2. 每次调用全量拷贝数组到内核;
  3. 返回后仍然要遍历整个数组查找就绪 fd。

select、poll 本质:内核挨个扫描所有 fd,不知道谁就绪。

3 epoll(Linux 专属,3 个系统调用)

epoll 由epoll_create、epoll_ctl、epoll_wait共同工作;内核维护红黑树(保存监听 fd) 、就绪双向链表(保存就绪事件)。

① epoll_create 创建 epoll 实例

cs 复制代码
int epoll_create(int size);
  • size:历史遗留参数,现代内核忽略,传大于 0 即可。
  • 返回 epoll 实例 fd;失败返回‑1。

作用:在内核创建红黑树、就绪链表。

② epoll_ctl 增删改监听 fd

cs 复制代码
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

struct epoll_event {
    uint32_t events;  // EPOLLIN可读,EPOLLOUT可写,EPOLLET边缘触发
    epoll_data_t data;//联合体,保存fd或者自定义指针
};

op操作:

  • EPOLL_CTL_ADD:添加 fd 到红黑树,注册回调;
  • EPOLL_CTL_MOD:修改已有 fd 的监听事件;
  • EPOLL_CTL_DEL:从红黑树删除 fd。

关键:fd 就绪触发内核回调,直接把事件节点放入就绪链表,不轮询全部 fd。

③ epoll_wait 获取就绪事件

cs 复制代码
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
  • events:用户数组,内核将就绪事件拷贝到此;
  • maxevents:数组最大容量;
  • timeout:超时,单位 ms。

返回就绪事件的数量,用户直接遍历events数组拿到就绪 fd。

LT 水平触发 / ET 边缘触发

  1. LT(默认,水平触发):缓冲区有未读完数据,每次 epoll_wait 都会上报事件,编程简单。
  2. ET(EPOLLET,边缘触发) :仅状态变化瞬间上报一次。 ⚠️ET 硬性要求:fd 设置O_NONBLOCK非阻塞;循环读直到EAGAIN,否则残留数据不会再次通知。

epoll 优缺点

✅ 无 fd 上限;海量空闲连接性能高;仅拷贝就绪事件。

❌ 仅 Linux 支持;少量活跃连接场景,内核额外开销大。

函数简单对比

表格

接口 拷贝行为 时间复杂度 fd 上限 跨平台
select 每次全量拷贝 fd 集合 O(n) 默认 1024 是
poll 每次全量拷贝 pollfd 数组 O(n) 无 大部分平台
epoll 监听 fd 只拷贝一次,仅拷贝就绪事件 O (就绪数) 无 仅 Linux

高频简短考点

  1. epoll 红黑树存待监听 fd;就绪链表存放已经就绪事件,epoll_wait只读取就绪链表。
  2. ET 边缘触发必须非阻塞 + 循环读,否则会漏数据。
  3. epoll 不是一定更快,大量活跃短连接场景 select/poll 可能更优。
  4. IO 多路复用是同步 IO,数据拷贝仍然由用户进程调用 read 完成。
相关推荐
我就是不信37 分钟前
TCP/IP 网络编程:从入门到实战
网络·网络协议·tcp/ip
沫璃染墨1 小时前
《Qt从零入门系列(十二):Qt文件操作详解——从QFile读写到QFileInfo与记事本实战》
开发语言·网络·c++·qt·交互·信号处理·文件
梦帮科技3 小时前
推测解码(Speculative Decoding)与 Draft-Target 双核协同:接受率判据、树状验证与两倍吞吐无损加速
网络·人工智能·深度学习·神经网络·线性代数·矩阵·架构
骑着蜗牛撵大象3273 小时前
Agent 打字机是怎么来的:SSE 与 WebSocket 打通实时响应与中间状态
网络·websocket·网络协议·agent·sse·实时通信·流式输出
模拟器连接器曾工3 小时前
一进多出M12分线盒4口8口12口可选 PNP和NPN通用传感器集线器
网络·人工智能·m12分线盒·npn通用传感器集线器·pnp通用传感器集线器
RobinDevNotes3 小时前
大模型3D并行训练怎么配才不浪费算力
网络·人工智能·深度学习·深度优先
Fcy6484 小时前
Linux下 传输层协议TCP详解
linux·网络·tcp/ip
web打印社区4 小时前
C-Lodop 提示未准备好或 WebSocket 没准备好:先让本机服务起来
javascript·网络·websocket·网络协议·pdf·html
2401_868534784 小时前
无源光网络
网络