Epoll:性能之王(红黑树+回调)
epoll 是为了解决大批量句柄处理而设计的
一、 Epoll 的核心工作原理
要理解 Epoll 的接口,首先要理解它在内核里建立的两个"秘密基地":
- 红黑树 (Red-Black Tree, rbr**)**:
-
- 用来存储所有你想要监听的文件描述符(FD)。
select每次都要把整个集合传给内核,而 Epoll 把这些 FD 存在内核的红黑树里,不需要重复拷贝 。增删查改的效率是 O(logN)。
- 就绪双向链表 (Ready List, rdlist**)**:
-
- 用来存储已经就绪(有数据来了)的事件。
- 当网卡接收到数据,会触发回调函数 (
ep_poll_callback),这个回调函数会自动把活跃的 FD 放入这个链表 5。 epoll_wait不需要遍历所有连接,只需要检查这个链表是否为空即可,复杂度是 O(1)
二、 Epoll 的三个核心接口详解
Epoll 把 select 那个臃肿的一个函数拆分成了三个步骤,分别对应"建群"、"加人"、"等消息" 。
1. epoll_create ------ 创建管家(建群)
int epoll_create(int size);
- 功能 :创建一个 Epoll 的句柄(在内核中创建一个
eventpoll对象)。 - 参数 size:
-
- 这个参数其实是被忽略的,只要填一个大于 0 的数即可 。
- 返回值 :成功返回一个 epoll 的文件描述符(
epfd),失败返回 -1。 - 注意 :Epoll 句柄本身也是一个文件描述符,占用一个 FD 资源,用完后必须调用
close()关闭 。
2. epoll_ctl ------ 管理名单(加人/踢人)
这是 Epoll 与 Select 最大的不同点。Select 是在等待时才给名单,Epoll 是提前注册。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
- 参数详解:
-
epfd:epoll_create返回的那个句柄 。op:要进行的操作动作,用宏表示 :
-
-
EPOLL_CTL_ADD:注册新的 FD 。EPOLL_CTL_MOD:修改已有的 FD 监听事件 。EPOLL_CTL_DEL:从红黑树中删除一个 FD 。
-
-
fd:需要监听的 socket 文件描述符 。event:告诉内核你要监听什么事(读/写/异常)以及你留的"备注数据" 。
关键结构体 epoll_event:
struct epoll_event {
uint32_t events; // 监听的事件类型
epoll_data_t data; // 用户数据(给用户自己用的)
};
-
- events****取值:
-
-
EPOLLIN:可读(包括对端关闭)。EPOLLOUT:可写 。EPOLLET:开启边缘触发模式 (Edge Triggered)。
-
-
- data****联合体 : 这是一个联合体,你可以存
int fd,也可以存void *ptr指针。当事件就绪时,内核会把这个data原样还给你。
- data****联合体 : 这是一个联合体,你可以存
3. epoll_wait ------ 等待消息(收割)
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
- 功能 :收集在 Epoll 监控中已经发生的事件 。它直接查看内核的
rdlist就绪链表,如果有数据,就拷贝给用户。 - 参数详解:
-
epfd:Epoll 句柄。events:输出参数。这是一个数组,内核会把就绪的事件复制到这里 。maxevents:告诉内核这个数组有多大(不能大于创建时的 size,但现在 size 被忽略,主要是防止数组越界)。timeout:超时时间(毫秒)。0表示立即返回(非阻塞),-1表示永久阻塞,>0表示等待指定时间 。
- 返回值:
-
> 0:就绪的文件描述符个数。0:超时。-1:出错 。
三、 Epoll 的两种工作模式:LT vs ET
1. LT 模式 (Level Triggered) ------ 水平触发
- 默认模式。
- 机制 :只要 socket 接收缓冲区里还有数据,
epoll_wait就会一直通知你 。 - 例子 :缓冲区有 2KB 数据,你只读了 1KB。下次调用
epoll_wait,它会再次返回并告诉你"有数据读" 。 - 特点:支持阻塞和非阻塞读写,编程不容易出错 。Select 和 Poll 本质上也是 LT 模式 。
2. ET 模式 (Edge Triggered) ------ 边缘触发
- 高性能模式(Nginx 默认采用)。
- 机制:只有当状态发生变化时(从无数据变成有数据),内核才会通知一次 。
- 例子 :缓冲区有 2KB 数据,你只读了 1KB。下次调用
epoll_wait,它不会再通知你了 ,剩下的 1KB 数据会一直待在缓冲区里,直到下一次有新数据到来 。 - 要求:
-
- 必须配合 非阻塞 I/O (Non-blocking IO) 使用 。
- 必须使用循环 (
while) 读取,直到read返回EAGAIN错误,确保把缓冲区读空 。
- 优点 :减少了
epoll_wait返回的次数,系统调用开销更小 。