I/O多路复用是系统级技术,通过select、poll、epoll实现单线程监控多socket;select跨平台但低效,poll无fd数量限制但仍O(n),epoll为Linux高效方案,支持ET/LT模式及近O(1)就绪通知;C++推荐用Boost.Asio等封装库。

I/O多路复用是C++(更准确说是系统级网络编程)中一种让单个线程/进程同时监控多个文件描述符(如socket)是否就绪(可读、可写、出错)的技术。它不等于C++标准库的iostream,而是底层基于操作系统提供的系统调用(select、poll、epoll),常用于实现高性能并发服务器(比如用C++写的HTTP服务、游戏网关等)。
select:最老但跨平台,适合少量连接
原理是把一组fd(文件描述符)拷贝到内核,内核轮询检查它们的状态,任一就绪就返回,并修改用户传入的fd_set标记哪些就绪了。
缺点明显:每次调用都要拷贝整个fd集合、内核需遍历所有fd(O(n))、最大fd数量受限(通常1024)、无法告知具体哪个fd就绪(得自己遍历查)。
使用要点:
- 每次调用前必须重新初始化
fd_set(用FD_ZERO和FD_SET) - 超时参数是输入输出参数,调用后会被修改,需每次重设
- 监听的fd必须小于
FOPEN_MAX或系统限制,且最大值要传给select()作为第一个参数
poll:比select稍好,无fd数量硬限制
用struct pollfd数组替代fd_set,内核直接遍历该数组。不再有1024限制,也不需要计算最大fd,但仍是O(n)遍历,每次仍要传全部fd数组,内核态/用户态间仍有数据拷贝。
立即学习“C++免费学习笔记(深入)”;
注意点:
-
pollfd.revents是输出字段,表示实际发生的事件;events是输入字段,指定关心哪些事件 - 数组长度就是监控的fd总数,不支持动态扩容,应用层需自行管理数组大小
- 超时值为毫秒,-1表示阻塞,0表示非阻塞轮询
epoll:Linux专属,高效可扩展,适合高并发
核心思想是“事件注册 + 就绪队列”。先用epoll_create创建一个内核事件表,再用epoll_ctl增删改监听项(每个fd只注册一次),最后用epoll_wait等待就绪事件——它只返回真正就绪的fd列表,时间复杂度接近O(1)(就绪数)。
关键优势:
- 注册一次,反复使用;不用每次传全部fd
- 内核维护红黑树+就绪链表,插入/删除/查询高效
- 支持边缘触发(ET)和水平触发(LT),ET模式配合非阻塞IO能减少重复通知
- 没有fd数量硬限制(只受系统内存和ulimit约束)
典型用法:创建epoll fd → 设置socket为非阻塞 → epoll_ctl(EPOLL_CTL_ADD)注册 → 循环epoll_wait → 对每个就绪fd处理读写(ET下必须循环recv/send直到EAGAIN)。
C++中怎么用?别直接裸写系统调用
实际项目中,不建议手撸select/poll/epoll封装。推荐:
- 用成熟网络库:如
libevent、libev、Boost.Asio(跨平台,内部自动选最优机制) - Asio示例:
io_context+async_accept/async_read,底层自动在Linux用epoll,macOS用kqueue,Windows用IOCP - 若必须手动控制(如嵌入式或极致性能场景),优先用epoll(Linux)+ 非阻塞socket + ET模式 + 边缘事件循环
基本上就这些。选哪个不是看名字多酷,而是看你的目标平台、连接规模、维护成本——小工具用select也够用,万级并发服务必须上epoll或Asio。











