目录
引言
epoll是在Linux内核里重新添加的一套机制,它是在2.5.44内核中被引进的epoll(4),它可以批量的监控文件描述符的各种事件。
三个系统调用
在头文件#include <sys/epoll.h>中
cpp
int epoll_create(int size);
用于创建epoll模型,返回值是一个文件描述符,也可以理解为epoll模型的一个句柄(Linux里一切接文件)。这个epoll模型是什么后文详细介绍。在Linux2.6.8之后epoll_create的参数是被忽略的,我们只需传入一个非负整数即可。
cpp
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
第一个参数:传入epoll模型对应的文件描述符
第二个参数:传入对epoll模型的操作
|--------------------------------|
| EPOLL_CTL_ADD :注册新的fd到epfd中; |
| EPOLL_CTL_MOD :修改已经注册的fd的监听事件; |
| EPOLL_CTL_DEL :从epfd中删除一个fd; |
第三个参数:要监控的文件描述符
第四个参数:要监控的事件
struct epoll_event 结构如下
cpp
typedef union epoll_data
{
void *ptr; // 指向用户自定义数据的指针
int fd; // 文件描述符
uint32_t u32; // 32 位整数
uint64_t u64; // 64 位整数
} epoll_data_t;
struct epoll_event
{
uint32_t events; // 发生的事件
epoll_data_t data; // 用户数据
} __EPOLL_PACKED; // 确保本结构体不被填充
-
events (
uint32_t
):- 这个字段指定了关注的事件类型,比如
EPOLLIN
(可读事件)、EPOLLOUT
(可写事件)等。可以使用按位或运算组合多个事件。
- 这个字段指定了关注的事件类型,比如
-
data (
epoll_data_t
):- 这个字段是一个联合体,允许存储不同类型的数据。通常用来保存与事件关联的文件描述符或者指向用户结构的指针。你可以使用以下方式之一来存储数据:
ptr
: 通用指针,指向用户自定义的数据结构。fd
: 文件描述符。u32
和u64
: 32 位和 64 位整数,可以用于存储任意附加信息。
- 这个字段是一个联合体,允许存储不同类型的数据。通常用来保存与事件关联的文件描述符或者指向用户结构的指针。你可以使用以下方式之一来存储数据:
events可以是以下几个宏的集合:
EPOLLIN : 表示对应的文件描述符可以读 (包括对端SOCKET正常关闭);
EPOLLOUT : 表示对应的文件描述符可以写;
EPOLLPRI : 表示对应的文件描述符有紧急的数据可读 (这里应该表示有带外数据到来);
EPOLLERR : 表示对应的文件描述符发生错误;
EPOLLHUP : 表示对应的文件描述符被挂断;
EPOLLET : 将EPOLL设为边缘触发(Edge Triggered)模式, 这是相对于水平触发(Level Triggered)来说的.
EPOLLONESHOT :只监听一次事件, 当监听完这次事件之后, 如果还需要继续监听这个socket的话, 需要再次把这个socket加入到EPOLL队列里
cpp
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
参数events是分配好的epoll_event结构体数组.
epoll将会把发生的事件赋值到events数组中 (events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去帮助我们在用户态中分配内存).
maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size.
参数timeout是超时时间 (毫秒,0会立即返回,-1是永久阻塞).
如果函数调用成功,返回对应I/O上已准备好的文件描述符数目,如返回0表示已超时, 返回小于0表示函数失败.
epoll模型
当某一进程调用epoll_create方法时,Linux内核会创建一个eventpoll结构体。要监控的事件被连入到内核的红黑树结构中。
当就有文件描述符上的事件就绪了,epoll模型中还有一个双向链表结构,这个数据结构用来存放就绪事件,epoll_wait就是在这上面捞取就绪事件。
epoll工作方式
epoll有2种工作方式-水平触发(LT)和边缘触发(ET)。
水平触发Level Triggered 工作模式
epoll默认状态下就是 LT 工作模式 .
当epoll 检测到 socket 上事件就绪的时候 , 可以不立刻进行处理 . 或者只处理一部分 .
如上面的例子 , 由于只读了 1K 数据 , 缓冲区中还剩 1K 数据 , 在第二次调用 epoll_wait 时 , epoll_wait
仍然会立刻返回并通知 socket 读事件就绪 . 直到缓冲区上所有的数据都被处理完, epoll_wait 才不会立刻返回 .
边缘触发Edge Triggered 工作模式
如果我们在第1 步将 socket 添加到 epoll 描述符的时候使用了 EPOLLET 标志 , epoll 进入 ET 工作模式 .
当epoll 检测到 socket 上事件就绪时 , 必须立刻处理 .
如上面的例子, 虽然只读了 1K 的数据 , 缓冲区还剩 1K 的数据 , 在第二次调用 epoll_wait 的时候 ,
epoll_wait 不会再返回了 . 也就是说, ET 模式下 , 文件描述符上的事件就绪后 , 只有一次处理机会 .
ET的性能比 LT 性能更高 ( epoll_wait 返回的次数少了很多 ). Nginx 默认采用 ET 模式使用 epoll.
只支持非阻塞的读写