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 完成。
相关推荐
SHENGWEI胜为5 小时前
二层网络广播风暴如何排查?从交换机接口计数器到 STP 拓扑变化分析
网络
艾芯微科技6 小时前
HR911130C|汉仁 HANRUN 集成变压器千兆 RJ45 网口参数、电路、PCB 布线、替代与采购避坑
网络·单片机·嵌入式硬件·集成测试·51单片机
日常筹谋记6 小时前
工业厂房 TPO 防水卷材:技术需求如何传导到配方层面?
运维·网络·html
玄芯散人9 小时前
【元婴·106】I2C总线:地址冲突、Clock Stretching、总线死锁怎么破
网络
YH_Analytics9 小时前
2026 年增亮膜(DBEF)行业市场格局深度解析
大数据·网络
dk_tjy10 小时前
迪康网络运维故障自愈的自动化闭环技术路径分析
运维·网络·自动化
万联WANFLOW11 小时前
让分散的设备像在同一张局域网里:企业组网技术解析
运维·网络·网络安全·智能路由器
QY_Research_12 小时前
全球智能交通系统物联网市场规模及CAGR14.2%深度分析
大数据·网络·人工智能·物联网·制造
梦帮科技12 小时前
量子纠缠网络与量子隐形传态攻防:量子中继器信道串扰、纠缠态保真度降解与量子路由劫持
开发语言·网络·人工智能·算法·自动化·php
山东孙燕姿13 小时前
维修工单的转派与协同:两个人一起上
大数据·运维·服务器·网络·人工智能