剖析 epoll 的事件驱动模型与多路复用原理,并基于此构建可扩展的 Reactor 反应堆框架,打造低延迟、高吞吐量的服务端架构!
@[toc]  # 本篇摘要 - 本篇将在之前学过的多路转接,多路复用的select,poll的基础上,进行对`多路转接之epoll`进行学习,从基础使用,到底层原理,到多模式了解,**最后基于epoll实现简单版的Reactor反应堆框架。** # 一. 多路转接之epoll认识与使用 之前我们学习了poll,发现它有很多缺点如:需要轮询查找就绪文件描述符,拷贝带来的消耗等;**而epoll就是为处理⼤批量句柄⽽作了改进的poll。** 比如epoll它也会进行用户态到内核态的拷贝,但是会大大降低这种开销。(比如:**返回就绪fd的时候,poll是返回所有的fd**,等待我们自己根据标记进行判断继续,**而epoll是返回一个就绪fd队列**,这样就降低了因为多个连接不活跃导致的无效拷贝等工作,提高效率) **`epoll相比 select和 poll,epoll更高效,支持 海量 fd 监听,且 只返回就绪的 fd,减少无效遍历。`** ## 关于epoll的使用 `epoll` 是 Linux 下高性能的 I/O 多路复用机制,适用于高并发网络编程(如 Web 服务器)。相比 `select` 和 `poll`,`epoll` 更高效,支持 **海量 fd 监听**,且 **只返回就绪的 fd**,减少无效遍历。 ### **`epoll 核心步骤`** 使用 `epoll` 主要分为 **3 步**: 1. **创建 epoll 实例** (`epoll_create1`) 2. **添加/修改/删除要监听的 fd** (`epoll_ctl`) 3. **等待事件发生** (`epoll_wait`) ### 关键函数 --- #### **(1) `epoll_create1(int flags)`** **作用**:创建一个 `epoll` 实例,返回一个 `epoll_fd`(类似一个“事件管理器”)。 **参数**: - `flags`:通常传 `0` 或 `EPOLL_CLOEXEC`(进程 fork 后自动关闭 fd)。 **返回值**: - 成功:返回 `epoll_fd`(>0 的文件描述符)。 - 失败:返回 `-1`,并设置 `errno`。 - ⽤完之后,必须调⽤close()关闭(也是个文件描述符). **示例**: ```c int epfd = epoll_create1(0); // 创建 epoll 实例 if (epfd == -1) { perror("epoll_create1"); exit(EXIT_FAILURE); } ``` --- #### **(2) `epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)`** **作用**:向 `epoll` 实例 **添加、修改或删除** 要监听的 fd(如 socket)。 **参数**: - `epfd`:`epoll_create1` 返回的 epoll 文件描述符。 - `op`:操作类型: - `EPOLL_CTL_ADD`:**添加** fd 到 epoll 监听列表。 - `EPOLL_CTL_MOD`:**修改** 已监听 fd 的事件(如从 `EPOLLIN` 改为 `EPOLLOUT`)。 - `EPOLL_CTL_DEL`:**删除** fd(不再监听)。 - `fd`:要监听的文件描述符(如 socket)。 - `event`:指定要监听的事件(如 `EPOLLIN` 可读、`EPOLLOUT` 可写)。 **`struct epoll_event` 结构**: ```c struct epoll_event { uint32_t events; // 监听的事件类型(如 EPOLLIN, EPOLLOUT) epoll_data_t data; // 用户数据(通常放 fd 或指针) }; typedef union epoll_data { void *ptr; int fd; // 通常存 fd uint32_t u32; uint64_t u64; } epoll_data_t; ``` **常见 `events` 事件类型**: | 事件类型 | 描述 | |----------------|----------------------------------------------------------------------| | `EPOLLIN` | **有数据可读**(例如 socket 接收缓冲区中有新数据到达) | | `EPOLLOUT` | **可写入数据**(例如 socket 发送缓冲区有空闲空间,允许发送数据) | | `EPOLLERR` | **错误事件**(例如连接异常断开、协议错误等需立即处理的错误) | | `EPOLLHUP` | **挂起事件**(例如对端关闭连接,后续读写操作将失效) | | `EPOLLRDHUP` | **对端关闭连接**(流套接字对端关闭写端,需配合 `EPOLLIN` 使用) | | `EPOLLPRI` | **紧急数据可读**(例如 TCP 带外数据到达) | | `EPOLLET` | **边缘触发模式**(需显式设置,事件仅在状态变化时通知一次) | | `EPOLLONESHOT` | **一次性事件**(事件触发后需重新注册,避免多线程并发处理同一事件) | ### 补充 - **组合使用**:可通过位或操作组合多个事件(如 `EPOLLIN | EPOLLET`)。 - **默认行为**:未指定 `EPOLLET` 时为水平触发模式(事件满足条件会重复通知)。 - **系统差异**:部分事件(如 `EPOLLRDHUP`)需内核版本支持。 **如(添加 socket 监听可读事件)**: ```c int sockfd = ...; // 你的 socket fd struct epoll_event ev; ev.events = EPOLLIN; // 监听可读事件 ev.data.fd = sockfd; // 存储 fd(方便识别) if (epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev) == -1) { perror("epoll_ctl: ADD"); exit(EXIT_FAILURE); } ``` --- #### **(3) `epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)`** **作用**:**阻塞等待** 有事件发生的 fd,返回就绪的 fd 列表。 **参数**: - `epfd`:`epoll_create1` 返回的 epoll 文件描述符。 - `events`:**输出参数**,存储就绪的 fd 事件(数组)。 - `maxevents`:`events` 数组的大小(最多监听多少个 fd)。 - `timeout`:超时时间(毫秒): - `-1`:**无限阻塞**,直到有事件发生。 - `0`:**非阻塞**,立即返回(没有事件也返回)。 - `>0`:**等待指定毫秒**(如 `1000` = 等待 1 秒)。 **返回值**: - **>0**:返回 **就绪的 fd 数量**(`events` 数组里有多少个 fd 可用)。 - **0**:超时,没有事件发生。 - **-1**:出错(如 `epfd` 无效)。 **如(等待事件并处理)**: ```c #define MAX_EVENTS 10 struct epoll_event events[MAX_EVENTS]; // 存储就绪的 fd while (1) { int n = epoll_wait(epfd, events, MAX_EVENTS, -1); // 阻塞等待 if (n == -1) { perror("epoll_wait"); break; } for (int i = 0; i epoll工作原理 **下面先看张图然后解释下:**  ## **epoll 底层核心机制介绍** 每个 **`epoll` 对象** 都有一个独立的 **`eventpoll` 结构体**,它的作用是 **管理所有通过 `epoll_ctl` 添加的 fd 事件**。其底层工作流程如下: --- ### `eventpoll` 结构体(核心管理结构) - **每个 `epoll` 实例(`epfd`)对应一个 `eventpoll` 结构体**,它是 **epoll 的核心管理数据结构**,负责存储所有被监听的 fd 事件。 **结构体简化如下:** ```cpp struct eventpoll{ .... /*红⿊树的根节点,这颗树中存储着所有添加到epoll中的需要监控的事件*/ struct rb_root rbr; /*双链表中则存放着将要通过epoll_wait返回给⽤⼾的满⾜条件的事件*/ struct list_head rdlist; .... }; ``` --- ### 红黑树(存储所有监听的 fd) - **所有通过 `epoll_ctl(EPOLL_CTL_ADD)` 添加的 fd 事件,都会挂载在一棵 **红黑树** 中**(`rb_root`)。 - **红黑树的作用**: - **高效查找、插入、删除**(时间复杂度 **O(log n)**,`n` 是树的高度)。 - **防止重复添加同一个 fd**(比如多次 `epoll_ctl(EPOLL_CTL_ADD, fd, ...)`,红黑树能快速判断是否已存在)。 --- ### 回调机制(事件触发时自动加入就绪队列) - **所有添加到 `epoll` 的 fd 都会和底层设备(如网卡驱动)建立回调关系**。 - **当 fd 上发生你监听的事件(如 `EPOLLIN` 可读、`EPOLLOUT` 可写)时,内核会调用回调函数 `ep_poll_callback`**。 - **这个回调函数的作用**: - **把触发的事件对应的 `epitem`(fd 事件结构体)** **加入到一个 **双链表 `rdlist`(就绪链表)** 中**。 - **这样 `epoll_wait()` 就不用遍历所有 fd,而是直接检查 `rdlist` 里有哪些 fd 就绪了**(高效!)。 --- ### `rdlist`(就绪链表,双链表) - `rdlist` 是一个 **双向链表**,存储所有 **已经触发事件(就绪)的 fd**。 - **`epoll_wait()` 时,内核只需要检查 `rdlist` 里有哪些 fd 就绪了**,然后返回给用户态程序。 - **这样 `epoll_wait()` 的效率极高**(只返回真正有事件的 fd,不用遍历所有 fd)。 --- ### `epitem` 结构体(每个 fd 对应一个) - **对于每一个被 `epoll` 监听的 fd,内核都会创建一个 **`epitem` 结构体** 来管理它**。 - **`epitem` 存储了该 fd 的所有相关信息**,比如: - **fd 本身**(如 socket 文件描述符)。 - **监听的事件类型(`EPOLLIN` / `EPOLLOUT`)**。 - **它在红黑树和 `rdlist` 中的位置**。 - **`epitem` 是 `epoll` 管理每个 fd 的最小单位**。 --- ### 工作机制流程图  --- ### **🔧 总结** | 步骤 | 说明 | 关键数据结构 | |------|------|-------------| | **1. 创建 epoll** | `epoll_create1()` → 创建 `epfd`,并初始化一个 **`eventpoll` 结构体** | `eventpoll` | | **2. 添加 fd** | `epoll_ctl(EPOLL_CTL_ADD, fd, ...)` → 把 fd 事件加入 **红黑树**(防止重复) | **红黑树(rb_root)** | | **3. 建立回调** | 每个 fd 都和底层驱动(如网卡)建立回调关系,事件触发时调用 `ep_poll_callback` | **回调机制** | | **4. 事件触发** | 当 fd 有数据(如 socket 收到数据),内核调用 `ep_poll_callback`,把该 fd 加入 **`rdlist`(就绪链表)** | **`rdlist`(双链表)** | | **5. `epoll_wait()`** | 检查 `rdlist`,返回所有就绪的 fd(高效,不用遍历所有 fd) | **`epitem`(每个 fd 的管理结构)** | ### 简单理解 - **`epoll` 高效的核心原因**: 1. **红黑树** 存储所有监听的 fd,**查找/插入/删除快(O(log n))**,还能 **防止重复添加**。 2. **回调机制**:当 fd 有事件时,内核自动调用 `ep_poll_callback`,把就绪的 fd 放进 **`rdlist`(双链表)**。 3. **`epoll_wait()` 只检查 `rdlist`**,**只返回真正有事件的 fd**,**不用遍历所有 fd**,所以 **极快**! ### 基于epoll底层原理的总结 > **每个 `epoll` 对象(`epfd`)都有一个 `eventpoll` 结构体管理所有 fd 事件:** > - **红黑树** 存储所有监听的 fd(高效查找,防止重复)。 > - **回调机制**(`ep_poll_callback`)在事件发生时,把 fd 加入 **`rdlist`(就绪链表)**。 > - **`epoll_wait()` 只检查 `rdlist`**,**只返回真正有事件的 fd**,所以 **性能极高**! > > **这就是 `epoll` 比 `select`/`poll` 快得多的原因!** # 三.epoll优点简述 1. **接口简单高效** - 拆分成 `epoll_create1`、`epoll_ctl`、`epoll_wait` 三个函数,**使用更方便**。 - **无需每次循环重新设置 fd**,**输入/输出参数分离**,减少冗余操作。 2. **数据拷贝少** - 只在 `EPOLL_CTL_ADD` 时**将 fd 结构拷贝到内核一次**(不频繁),**不像 select/poll 每次循环都拷贝**。 3. **事件回调机制(高效)** - **不遍历所有 fd**,而是通过 **回调函数** 将就绪的 fd 加入 **就绪队列**。 - `epoll_wait` **直接访问就绪队列**,**时间复杂度 O(1)**,**即使 fd 很多也高效**。 4. **无数量限制** - **支持的 fd 数量无上限**(不像 select/poll 有 FD_SETSIZE 限制)。 # 四.认识epoll的边缘触发和水平触发模式 ## **1. 水平触发(LT,Level Triggered,默认模式)** - **epoll 默认工作模式**,类似 `select/poll`。 - **特点**: - **只要 socket 缓冲区有数据可读/可写,`epoll_wait` 就会重复返回该事件**(即使你只处理了一部分数据)。 - **如果只读取了部分数据(如 1K/2K),剩余数据仍在缓冲区,下次 `epoll_wait` 仍会通知该 fd 可读**。 - **必须持续处理,直到缓冲区数据全部读完/写完,`epoll_wait` 才不再频繁返回该 fd**。 - **支持阻塞和非阻塞 I/O**。 ## **2. 边缘触发(ET,Edge Triggered,需手动启用)** - **通过 `EPOLLET` 标志启用**(如 `event.events = EPOLLIN | EPOLLET`)。 - **特点**: - **仅当 socket 状态变化时(如从“无数据”变为“有数据”),`epoll_wait` 返回一次事件**。 - **如果只处理了部分数据(如读了 1K,剩余 1K 仍在缓冲区),下次 `epoll_wait` 不会再通知该 fd**,除非有新数据到达。 - **必须一次性处理完所有数据(通常配合非阻塞 I/O + 循环读写)**,否则可能丢失事件。 - **性能更高**(`epoll_wait` 返回次数更少),**但编程更复杂**。 - **仅支持非阻塞 I/O**(阻塞读写会导致未处理完的数据无法继续触发事件)。 - **Nginx 默认使用 ET 模式**。 ## **3. 对比总结** | **特性** | **水平触发 (LT)** | **边缘触发 (ET)** | |----------------|--------------------------------|--------------------------------| | **默认模式** | 是(epoll 默认) | 否(需加 `EPOLLET` 标志) | | **事件通知** | **只要数据未处理完,就持续通知** | **仅状态变化时通知一次** | | **数据处理** | 可分多次处理 | **必须一次性处理完所有数据** | | **I/O 类型** | 支持阻塞和非阻塞 | **仅支持非阻塞** | | **性能** | 较低(返回次数多) | **更高(返回次数少)** | | **典型应用** | 一般场景(编程简单) | 高性能服务(如 Nginx) | - **LT 模式**:适合大多数场景,**编程简单**,但可能 **频繁触发 `epoll_wait`**。 - **ET 模式**:**性能更高**(推荐高性能服务如 Nginx),但 **必须用非阻塞 I/O 并处理完所有数据**,否则易丢事件。 ## 4.为什么说ET模式一定要设置非阻塞呢? - 首先要知道ET模式是只通知一次(也就是如果没读完就不会通知了,除非下次缓冲区再次被写入)。 **`假设这样的场景:服务器接收到⼀个10k的请求,会向客⼾端返回⼀个应答数据.如果客⼾端收不到应答,不会发送第⼆个10k请求。`**  - 如果是ET模式的阻塞模式,也就是每次只能读一次就不再通知,直到对面再次发送数据,但是如果read没有读取完,只读了1k(可能是信号中断或者读取数组大小限制),剩下的9k,就不会被读取。  - 因此,这个规则就造成了客户端等着服务端应答才发数据,但是服务端还要等客户端再次发送数据使得缓冲区状态变化才能读取,卡住了。  ==**因此,ET模式一定要设置非阻塞,靠死循环把它读完(合理利用标志位)**== # 五. `基于epoll的代码编写(基于tcp实现echo回显):` **注:** **这里只测试epoll的LT模式,对于ET的非阻塞模式后面基于epoll实现Reactor再进行测试。** ## **epoll 服务器设计思路** 1. **初始化**(构造函数) - 创建监听 socket(绑定端口) - 创建 epoll 实例(`epoll_create`) - 把监听 socket 加入 epoll 监听列表(`epoll_ctl`),监听新连接 2. **启动服务**(`start()`) - 循环调用 `epoll_wait` 等待事件 - 如果有事件就绪: - 调用 `Dispatcher` 分发处理 3. **事件分发**(`Dispatcher`) - 如果是监听 socket 有数据(新连接)→ 调用 `Acceper` 处理 - 如果是客户端 socket 有数据 → 调用 `Recver` 处理 4. **处理新连接**(`Acceper`) - 接受新连接(`accept`) - 把新客户端 socket 加入 epoll 监听(`epoll_ctl`) 5. **处理客户端数据**(`Recver`) - 读取客户端数据(`recv`) - 如果客户端断开 → 从 epoll 移除并关闭 socket 6. **停止服务**(`stop()`) - 关闭 epoll 和监听 socket ## epoll服务器操作流程: ```mermaid graph TD %% 初始化 A[创建监听socket] --> B[创建epoll实例] B --> C[监听socket加入epoll] %% 主循环 D[epoll_wait等待事件] --> E{有事件?} E -->|是| F[事件分发] E -->|否| G[超时/错误处理] G --> D %% 事件处理 F --> H{监听socket事件?} H -->|是| I[接受新连接] H -->|否| J[读取客户端数据] %% 新连接处理 I --> K[新socket加入epoll] %% 客户端数据处理 J --> L{数据有效?} L -->|是| M[处理数据] L -->|否| N[关闭客户端连接] %% 结束 O[停止服务] --> P[清理epoll资源] P --> Q[关闭监听socket] %% 循环逻辑 N --> D M --> D K --> D ``` --- ## `测试效果:` - **首先运行epoll服务器进行监听:**  - **telnet 连接进行测试:**  - **可以正常对话,如果是多个客户端同时访问,基于epoll机制也不会有问题!** ## 源码 [**点我查看测试源码**](https://gitee.com/wang-yimingq/linux_learn/tree/master/IO) # 六.关于epoll的惊群效应的理解 ## **1. 什么是惊群效应?** - **场景**:多个线程/进程同时用 `epoll_wait` 监听 **同一个socket(如监听80端口的socket)**。 - **问题**:当 **新连接到来时**,内核 **会唤醒所有等待的线程/进程**,但 **只有1个能成功 `accept()` 处理连接**,其他线程/进程发现没活干(`errno=EAGAIN`),又继续睡觉。 - **结果**:**大量无效唤醒 → CPU资源浪费 + 性能下降**(就像一群人被吵醒,但只有一个人能干活)。 --- ## **2. 为什么会产生?** - **原因**:操作系统 **不知道该让哪个线程/进程处理新连接**,索性 **全唤醒**,最终 **只有一个能成功**,其他白忙活。 --- ## **3. 如何解决?** ### **(1) 多线程环境** - **方法**:**只让1个线程监听 `epoll_wait`**,它负责 `accept()` 新连接,然后把连接交给其他工作线程处理数据。 - **优点**:**避免多个线程抢同一个事件**,彻底解决惊群。 ### **(2) 多进程环境(如Nginx/Lighttpd)** - **方案1(Lighttpd)**: **本身就无视惊群**,每个子进程自己 `epoll_wait`,谁抢到算谁(抢不到的忽略 `EAGAIN` 错误)。 - **方案2(Nginx)**: **只让1个子进程监听**,通过 **全局锁(mutex)** 确保同一时间 **只有一个进程在 `epoll_wait`**。 ## **4. 总结** - **惊群效应** =:==多个线程/进程抢同一个连接事件,但只有1个能成功,其他白忙 → **浪费CPU**。== - **解决**:==**别让多个线程/进程抢同一个socket!**(单线程监听 + 分发,或加锁/独立监听)。== # 七. `epoll` ==适用场景及对比== ## 场景: **高并发 TCP 服务器**(如 Web 服务器、游戏服务器) **需要监听大量 fd 的情况**(比 `select`/`poll` 更高效) **需要低延迟、高性能 I/O 的场景** --- ## **对比 `select`/`poll`** | 特性 | `select` | `poll` | `epoll` | |------|---------|--------|---------| | **最大 fd 数量** | 1024(受限) | 无硬限制,但效率低 | 无硬限制,高效 | | **是否遍历所有 fd** | 是(每次都要检查) | 是 | **只返回就绪的 fd** | | **底层实现** | 轮询 | 轮询 | **回调机制(更高效)** | | **适用场景** | 低并发 | 低并发 | **高并发(推荐)** | ## **总结** - **`epoll` 是 Linux 高性能 I/O 多路复用的最佳选择**,适用于高并发服务器。 - **核心步骤**:`epoll_create1` → `epoll_ctl` → `epoll_wait`。 - **主要监听事件**:`EPOLLIN`(可读)、`EPOLLOUT`(可写)。 - **比 `select`/`poll` 更高效**,只返回就绪的 fd,减少无效遍历。 **适用于:Web 服务器、聊天服务器、游戏服务器等高并发场景!** # 九.基于epoll的ET非阻塞模式实现Reactor反应堆模式 **Reactor模式是⼀种事件驱动的编程模式,⽤于⾼效地处理并发I/O操作。它通过⼀个或多个事件循环(Event Loop)来监听和处理各种事件(如⽹络请求、定时器事件等),从⽽实现⾼效的并发处理,⽽⽆需为每个连接创建⼀个线程或进程**。 形象如图所示:  好的,我用大白话给你解释一下这两个概念,分条来看: ## 事件循环(Event Loop) 可以把它想象成**“值班小管家”**: - **核心作用**:专门盯着“有没有事情发生”(比如用户点按钮、网络请求返回、定时器到点了……这些统称“事件”)。一旦有事情发生,就赶紧去执行对应的“后续操作”(也就是回调函数)。 - **单线程 + 多路复用**:它一般是单线程跑的(不用多个线程来回切换,省事儿),但为了同时盯着很多 I/O 操作(比如同时等网络数据、等磁盘读取),会用 `epoll`/`select`/`poll` 这些“高科技”手段——相当于小管家能一次看住好多扇门,哪扇门有动静(I/O 完成)了,就立刻去处理,不用傻等。 ### 基于epoll实现单Reactor模式框架 #### ==设计思路== **采取继承的方式(因为listenfd和普通fd的操作大部分相同,因此封装成一个connection,采取子类继承),所有连接组成对应的队列让Reactor中的(connections+epoll进行维护),这里因为对应的连接内部都设计对整体管理,因此添加对应的对于Reactor这个总的管理者的回调指针,整体操作流程如图所示,细节见代码** **`基于简单Reactor框架的巧妙设计:`** - **对于不同链接(listenfd,fd)采取有相似也有不同逻辑故采取继承多态方式完成。** - **实现框架分层处理(底层拿到数据后进行解密的协议处理再业务处理最后加密返回底层进行发送,整体解耦性强大)。** - **默认所有普通fd都监听的是读与写,而且把所有的event(比如异常等),都化为IO处理,最终就让它IO出错对应的是出错的处理。** **下面博主准备了十分形象的纯手画过程图,绝对秒懂:**  #### 测试效果 - 把服务端绑定8080端口:  - 因为这里涉及到协议,客户端必须有解析协议功能,因此选择使用网络计算器版本的client进行访问:  - 效果也符合预期:  #### 源码汇总 [**点我查看测试源码**](https://gitee.com/wang-yimingq/linux_learn/tree/master/net_IO/reactor_module) ## `单线程模型(One Thread One Loop)` 简单说就是**一个线程配一个值班小管家**: - 每个线程自己跑一套“事件循环”,自己盯着自己的那堆事件。 - 为啥这么设计? - 避免多线程那些糟心事儿:多线程要锁、要同步,一不小心就死锁、数据乱套……单线程里跑事件循环,就没有这些同步的麻烦。 - 还能利用系统高效 I/O:前面说过,事件循环靠 `epoll` 这些技术高效处理 I/O,单线程里跑一套,既省了线程切换的开销,又能高效处理多 I/O 场景。 **如图:**  - **简单来说就是master进行监控fd,拿到fd后创建子进程,往匿名管道进行写入对应fd,然后slaver拿到后进行处理即可。** - **这里就不再事件了,简单改下即可。** 上面是用进程实现的otol,下面我们使用线程+eventfd(一种轻量级事件通知机制【[evntfd详解跳转链接](https://youxing.blog.csdn.net/article/details/149155787?spm=1001.2014.3001.5502)】)来讲下这个版本的otol,效果如图:  - **这里是基于eventfd实现的,还是master进行监控连接fd,然后拿到后放入fdpool里面,然后负载均衡选择对应的线程进行发送事件通知,让他去fdpool里面取fd进行对应操作!** - **这里直接改成对应的多线程就行了,不实现了。** ==**总结一下:**== **事件循环就是“单线程里的事件管家”,负责盯着事儿、办事儿;单线程模型就是“每个线程都雇一个这样的进行fd处理管家”,既躲开了多线程的坑,又能高效干活~** # 十.小结 **本篇介绍了epoll代替之前select poll的使用,表明了它的有待呢,与使用,然后基于它实现了简单的Reactor框架(如muduo库就是它的应用),本篇难点实现Reactor框架,因为bug多端,耗时也很久,总之,遇到bug不要慌,手拿把掐打开gdb,调就完了。**