Linux I/O 多路复用
在进行网络编程或高并发处理时,你常常需要同时监听多个文件描述符(如多个 socket 连接)。这时,I/O 多路复用 就成了一种非常高效的技术。本文将带你理解什么是 I/O 多路复用、它的几种常见实现方式(select
、poll
、epoll
)以及各自的优缺点。
什么是 I/O 多路复用?
I/O 多路复用的本质是:使用一个线程同时等待多个文件描述符变为“就绪”状态(可读、可写或有异常),一旦某个文件描述符准备好了,你就可以对它进行 I/O 操作。
通俗来说,你不用为每个连接创建一个线程,而是可以用一个线程“盯着”多个 socket,当某个 socket 有数据时才处理它。这样做的好处是资源消耗更少,效率更高,尤其适合高并发场景。
常见的多路复用方式
Linux 下提供了三种常用的多路复用 API:
1. select
这是最早期的多路复用接口,几乎所有系统都支持 。
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
- 你需要通过
FD_SET()
等宏手动设置监听的文件描述符集合; nfds
是所有监听的 fd 中最大值加一;timeout
控制阻塞时间,传 NULL 表示一直等待;- 每次调用后,fd 集合会被修改,需重新设置。
缺点:
- 监听数量有限制(通常是 1024);
- 每次调用都需要重建 fd 集合;
- 难以扩展,高并发场景性能差。
2. poll
poll
的功能和 select
类似,但使用数组结构代替了 fd_set
,消除了 fd 数量的限制。
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
fds
是一个pollfd
数组;- 每个
pollfd
包含文件描述符和感兴趣的事件; - 返回时可以知道哪些 fd 准备好了。
改进点:
- 不再限制最大 fd;
- 结构更灵活;
- 但每次仍需遍历整个数组,效率不够高。