【Linux】【网络】IO多路复用 select、poll、epoll
IO 多路复用
进程或线程同时监控多个文件描述符,查看描述符上是否有事件发生,从而提高资源利用率和系统吞吐量。
1. select
int select(int maxfd, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct ti
meval *timeout);
-
返回值代表多少个文件描述符上有事件
-
int maxfd 被监听的文件描述符的总数
-
fd_set *readfds 读事件
-
fd_set *writefds 写事件
-
fd_set *exceptfds 异常事件
-
struct timeval *timeout 超时时间
-
基本原理
- 使用固定大小的 fd_set(集合类型),每个位代表一个文件描述符。 最多能够监听1024个文件描述符
- 每次使用时,应用程序都需要将需要监控的文件描述符添加到fd_set集合中,然后调用系统调用 select()。
- 内核在超时时间内轮询 fd_set,当检测到某个文件描述符发生指定的 I/O 事件(select只有可读、可写、异常)时,返回就绪的文件描述符集合。
-
工作流程
- 应用程序将需要监控的 fd 添加到 fd_set 通过**FD_SET()**设置对应的位中,并设置超时时间。
- 调用 select() 后,内核遍历整个 fd_set,检查每个文件描述符的状态。
- 如果有文件描述符处于就绪状态,select() 返回 fd_set中有事件的位为1 通过**FD_ISSET()**轮询检测;否则在超时后返回
2.poll
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
- pollfd *fds pollfd结构体数组指针
- nfds_t nfds 结构体数组指针指向数组的元素个数
- int timeout 超时时间
struct pollfd
{
int fd; // 文件描述符
short events; // 注册的关注事件类型 每一位可以代表一个类型 因此最多检测16个事件
short revents; // 实际发生的事件类型,由内核填充
};
- fd 成员指定文件描述符
- events 成员告诉 poll 监听 fd 上的哪些事件类型。它是一系列事件的按位&
- revents 成员则由内核修改,通知应用程序 fd 上实际发生了哪些事件
- 基本原理
- poll 使用一个 pollfd 数组来描述需要监控的文件描述符及其关注的事件, 结构体数组可以开辟的很大,不受1024的限制
- 事件类型可以更多
- 事件和描述符封装在一起
- 工作流程
- 应用程序填充一个 pollfd 数组,每个元素记录文件描述符和关注的事件(如 POLLIN、POLLOUT)。
- 调用 poll() 后,内核遍历数组,检查每个 fd 的状态。
- poll() 返回就绪的文件描述符个数,并通过 pollfd 数组的 revents 字段告知应用程序哪些事件发生了。
3.epoll
-
epoll 使用一组函数来完成任务,epoll 把用户关心的文件描述符上的事件放在内核里的一个事件表中。从而无需像 select 和 poll 那样每次调用都要重复传入文件描述符或事件集。
-
但 epoll 需要使用一个额外的文件描述符,来唯一标识内核中的这个内核事件表
-
epoll_create()用于创建内核事件表
-
epoll_ctl()用于操作内核事件表
-
epoll_wait()用于在一段超时时间内等待一组文件描述符上的事件 //可能阻塞
-
基本原理
- epoll 是 Linux 特有的机制,其设计目标是高效处理大规模文件描述符。
- epoll 将所有注册的文件描述符存储在内核内部的数据结构中(通常采用红黑树管理所有注册项),并维护一个就绪队列用于保存触发事件的 fd。
- 应用程序通过 epoll_ctl() 添加、修改或删除文件描述符,通过 epoll_wait() 获取就绪事件。
-
数据结构
- 红黑树(rbtree):存储所有已注册的文件描述符(epitem),便于快速查找、添加和删除操作,时间复杂度 O(log N)。
- 就绪链表(ready list):存储已经触发事件的 epitem,当调用 epoll_wait() 时,内核直接返回这个就绪链表中记录的文件描述符,时间复杂度与就绪 fd 数量相关,通常远小于所有注册 fd 数量。
-
支持两种触发模式:
- 水平触发(Level Triggered, LT):类似 poll,每次调用 epoll_wait() 都返回当前就绪的 fd。
- 边缘触发(Edge Triggered, ET):仅在状态变化时通知,必须在一次性读取完数据,否则不会重复通知。
4.性能比较
特性 | select | poll | epoll |
---|---|---|---|
文件描述符数量限制 | 受 FD_SETSIZE 限制(通常 1024 个) | 无固定限制,但效率随数组长度线性下降 | 支持大量文件描述符,效率依赖于就绪事件数量 |
每次检查是否需要重新将数据拷贝给内核 | 是 | 是 | 否 |
扫描方式 | 线性遍历整个 fd_set | 线性遍历 pollfd 数组 | 内核通过红黑树查找 + 就绪链表遍历 |
内核检测就绪描述符时间复杂度 | O(N) | O(N) | O(1) |
查找就绪描述符时间复杂度 | O(N) | O(N) | O(1) |
触发模式 | 仅支持水平触发 | 仅支持水平触发 | 支持水平触发和边缘触发 |
平台支持 | POSIX 标准,跨平台支持 | POSIX 标准,跨平台支持 | 仅 Linux 支持 |
后续会详细写一下epoll 每个函数 以及内核实现方式 因为这个比较重要 以及libevent底层也是epoll