1. 课程导入与学习地图

如果你写过基于 accept 阻塞等待的 TCP 服务器,一定遇到过这样的困境:一个客户端连上来不发数据,整个服务就卡死;想用多线程解决,连接数一上千,线程切换和内存开销就把机器压垮。这正是 C10K 问题——单机同时处理一万个连接——在二十多年前被提出时,传统“一连接一线程”模型撞上的墙。epoll 就是 Linux 为翻过这堵墙给出的答案,它也是今天 Nginx、Redis、Netty、Envoy 等高性能网络组件在 Linux 上的底层事件引擎。

要理解 epoll 的定位,先要看清它和前辈的差别。select 和 poll 每次调用都要把整个待监听的文件描述符集合从用户态拷贝到内核态,返回后再由应用线性扫描找出就绪的 fd,时间复杂度是 O(n),连接越多越慢。epoll 把“注册监听”和“等待事件”拆成两步:epoll_ctl 把关心的 fd 一次性注册进内核的红黑树,epoll_wait 只返回真正就绪的那几个 fd,复杂度接近 O(1)。更关键的是,内核用就绪链表维护活跃连接,应用拿到的就是“有事发生的连接”,而不是“所有连接里哪些有事”。

epoll 在现代网络服务中的位置:从内核事件到业务处理 Linux 内核 网卡 / 协议栈 数据到达 → 就绪 红黑树(注册表) epoll_ctl 增删改 fd 就绪链表 只放活跃连接 epoll_wait 返回就绪事件 用户态服务进程 事件循环 epoll_wait 阻塞等待 事件分发 按 fd 找回调/处理器 非阻塞读写 recv/send 循环处理 业务逻辑 协议解析 处理完继续下一轮 epoll_wait(单线程即可支撑海量连接)

这张图给出了本课程要反复打磨的主线:内核负责“谁就绪”,用户态负责“怎么处理”。epoll 本身不处理业务,它只把就绪事件高效地交到你的事件循环里;真正的性能,取决于你如何组织事件循环、如何做非阻塞读写、如何管理连接状态。这也是为什么本课程不会停留在“背 API”,而是围绕一条完整的服务实现路径展开。

课程目标:从会调用到能交付

学完这门课,你应当能够独立完成三件事。第一,准确说清 epoll 的三种触发模式(水平触发 LT、边沿触发 ET、以及 ET 配合 EPOLLONESHOT)在什么场景下用、为什么用,并能解释 ET 模式下“必须一次读到 EAGAIN”的原因。第二,用 C 语言写出一个可运行的多路复用 TCP 服务,包含监听、接入、读、写、关闭的完整生命周期,并正确处理短读、短写、半包与粘包。第三,具备把单线程事件循环扩展为多 Reactor 的能力,理解主从 Reactor 的职责划分与惊群问题。

这些目标对应到知识框架上,可以分成四层。最底层是内核机制:红黑树、就绪链表、回调唤醒、epoll_event 结构。往上一层是 API 语义:epoll_create1、epoll_ctl、epoll_wait 的参数与返回值含义,以及 LT/ET 的行为差异。再往上是编程模型:Reactor 模式、事件分发、连接对象管理、缓冲区设计。最上层是工程实践:性能观测、常见错误排查、压力测试与调优。

实战项目概览:一个可压测的 echo 服务

课程会以一个渐进式项目贯穿始终——从最简单的阻塞 echo 服务器出发,逐步改造成 epoll LT 版本、epoll ET 版本,最后扩展为多线程 Reactor 版本。每一版都能编译运行,并用同一套压测方法对比。示例中我们约定:服务监听 0.0.0.0:9000,客户端发送一行文本,服务端原样回显并换行;连接上限先设为 1024,后续章节再讨论如何突破。

为了让你对“结果如何判断”有直观预期,下面给出一个可参考的压测命令与观察指标。注意,以下数值是示例性目标,不是实测结论,实际结果取决于机器配置与网络环境。

# 终端1:启动服务
./echo_epoll_et

# 终端2:用 wrk 或自写客户端发起并发连接
# 观察服务端 CPU 占用、内存增长、以及是否出现连接泄漏
# 判断依据:
#   1) 1 万连接下 CPU 占用是否平稳,不随连接数线性上升
#   2) 断开连接后 fd 数量是否回落(lsof -p <pid> | wc -l)
#   3) 是否出现 EAGAIN 处理遗漏导致的连接卡死
学习建议:每学完一个 API,就回到项目里替换对应实现,并保留上一版代码。对比不同版本在同一压测下的表现,比单纯记忆结论更能建立直觉。

学习地图与节奏

整个课程按“机制—API—模型—工程”推进。前半程重点建立对内核行为的准确理解,避免把 LT 和 ET 混用;中段进入编码,把事件循环、缓冲区、连接管理逐块搭起来;后段处理真实服务绕不开的问题:惊群、负载均衡、超时管理、优雅关闭。每一章都会给出可运行的代码片段或可执行的判断方法,而不是停留在概念描述。