目录
[一、 EPollPoller.h](#一、 EPollPoller.h)
[1. EPollPoller 的整体定位与核心设计目标](#1. EPollPoller 的整体定位与核心设计目标)
[2. 核心成员变量解析](#2. 核心成员变量解析)
[1. 静态常量:事件列表初始大小(性能优化)](#1. 静态常量:事件列表初始大小(性能优化))
[2. epoll 核心资源](#2. epoll 核心资源)
[3. 核心接口逐模块拆解](#3. 核心接口逐模块拆解)
[1. 构造 / 析构:epoll 资源的创建与释放](#1. 构造 / 析构:epoll 资源的创建与释放)
[2. 重写 Poller 纯虚接口(核心)](#2. 重写 Poller 纯虚接口(核心))
[3. 私有辅助方法:分工明确的工具函数](#3. 私有辅助方法:分工明确的工具函数)
[1. fillActiveChannels:事件→Channel 映射(核心分发)](#1. fillActiveChannels:事件→Channel 映射(核心分发))
[2. update:统一处理 epoll_ctl 操作(简化逻辑)](#2. update:统一处理 epoll_ctl 操作(简化逻辑))
[3. operationToString:调试辅助(可读字符串)](#3. operationToString:调试辅助(可读字符串))
[4. 核心设计亮点总结](#4. 核心设计亮点总结)
[二、 EPollPoller.cc](#二、 EPollPoller.cc)
[1. 代码整体核心逻辑回顾](#1. 代码整体核心逻辑回顾)
[2. 逐模块拆解核心实现](#2. 逐模块拆解核心实现)
[1. 前置编译期断言 + 状态常量(核心约束)](#1. 前置编译期断言 + 状态常量(核心约束))
[2. 构造 / 析构:epoll 资源的 RAII 管理](#2. 构造 / 析构:epoll 资源的 RAII 管理)
[3. poll 方法:等待 IO 事件(核心)](#3. poll 方法:等待 IO 事件(核心))
[4. fillActiveChannels:事件→Channel 分发(核心)](#4. fillActiveChannels:事件→Channel 分发(核心))
[5. updateChannel:状态驱动的 epoll_ctl 操作(核心)](#5. updateChannel:状态驱动的 epoll_ctl 操作(核心))
[6. removeChannel:安全移除 Channel(核心)](#6. removeChannel:安全移除 Channel(核心))
[7. update:封装 epoll_ctl 调用(核心工具)](#7. update:封装 epoll_ctl 调用(核心工具))
[8. operationToString:调试辅助(可读字符串)](#8. operationToString:调试辅助(可读字符串))
[3. 核心设计亮点总结](#3. 核心设计亮点总结)
一、 EPollPoller.h
先贴出完整代码,再逐部分解释:
cpp
// Copyright 2010, Shuo Chen. All rights reserved.
// http://code.google.com/p/muduo/
//
// 本源代码的使用受 BSD 风格许可证约束
// 该许可证可在 License 文件中查阅。
// 作者:陈硕 (chenshuo at chenshuo dot com)
//
// 这是一个内部头文件,你不应该直接包含它。
#ifndef MUDUO_NET_POLLER_EPOLLPOLLER_H
#define MUDUO_NET_POLLER_EPOLLPOLLER_H
#include "muduo/net/Poller.h" // 继承 Poller 抽象基类
#include <vector>
struct epoll_event; // 前向声明:epoll 事件结构体(定义在 <sys/epoll.h>)
namespace muduo
{
namespace net
{
///
/// 基于 epoll(4) 系统调用实现的 IO 多路复用器
///
/// 核心特性:1. 继承 Poller 抽象基类,实现 epoll 版本的 IO 多路复用;
/// 2. 管理 epoll fd(epollfd_)和 epoll_event 事件数组(events_);
/// 3. 重写基类纯虚接口,封装 epoll_create/epoll_ctl/epoll_wait 系统调用;
/// 4. 所有接口遵守 Poller 基类的线程约束(仅在 EventLoop 线程调用)
class EPollPoller : public Poller
{
public:
/// 构造函数:创建 epoll fd,初始化事件数组
/// @param loop 所属的 EventLoop(传递给 Poller 基类)
EPollPoller(EventLoop* loop);
/// 析构函数:关闭 epoll fd,释放资源(override 确保重写基类虚析构)
~EPollPoller() override;
/// 轮询 IO 事件(重写 Poller 纯虚接口)
/// 底层调用 epoll_wait,获取触发的事件并填充到 activeChannels
/// @param timeoutMs 超时时间(毫秒,-1=无限等待,0=非阻塞)
/// @param activeChannels 输出参数,存储触发事件的活跃 Channel 列表
/// @return 实际轮询返回的时间戳
Timestamp poll(int timeoutMs, ChannelList* activeChannels) override;
/// 更新 Channel 关注的事件(重写 Poller 纯虚接口)
/// 底层调用 epoll_ctl(EPOLL_CTL_ADD/EPOLL_CTL_MOD)
/// @param channel 要更新的 Channel 对象
void updateChannel(Channel* channel) override;
/// 移除 Channel(重写 Poller 纯虚接口)
/// 底层调用 epoll_ctl(EPOLL_CTL_DEL),并从 channels_ 中删除映射
/// @param channel 要移除的 Channel 对象
void removeChannel(Channel* channel) override;
private:
/// epoll_event 数组的初始大小(避免频繁扩容,默认16个)
/// 若触发事件数超过该大小,会自动扩容(reserve)
static const int kInitEventListSize = 16;
/// 辅助函数:将 epoll_ctl 的操作类型(ADD/MOD/DEL)转为字符串(便于日志/调试)
/// @param op epoll_ctl 的操作码(EPOLL_CTL_ADD/EPOLL_CTL_MOD/EPOLL_CTL_DEL)
/// @return 对应的字符串(如 "ADD"/"MOD"/"DEL")
static const char* operationToString(int op);
/// 填充活跃 Channel 列表(核心辅助函数)
/// 将 epoll_wait 返回的触发事件,映射到对应的 Channel,并设置 Channel 的 revents_
/// @param numEvents epoll_wait 返回的触发事件数量
/// @param activeChannels 输出参数,存储填充后的活跃 Channel 列表
void fillActiveChannels(int numEvents,
ChannelList* activeChannels) const;
/// 封装 epoll_ctl 调用(核心辅助函数)
/// 统一处理 ADD/MOD/DEL 操作,避免重复代码
/// @param operation epoll_ctl 操作码(EPOLL_CTL_ADD/EPOLL_CTL_MOD/EPOLL_CTL_DEL)
/// @param channel 要操作的 Channel 对象
void update(int operation, Channel* channel);
/// epoll_event 数组类型:存储 epoll_wait 返回的触发事件
typedef std::vector<struct epoll_event> EventList;
int epollfd_; // epoll 实例的文件描述符(由 epoll_create 创建)
EventList events_; // epoll_wait 输出的事件数组(存储触发的 epoll_event)
};
} // namespace net
} // namespace muduo
#endif // MUDUO_NET_POLLER_EPOLLPOLLER_H
1. EPollPoller 的整体定位与核心设计目标
EPollPoller 是 Poller 抽象基类在 Linux 系统下的具体实现(Muduo 默认 Poller),核心设计目标:
- 封装 epoll 系统调用 :将
epoll_create/epoll_ctl/epoll_wait封装为面向对象的接口,适配 Poller 定义的统一契约; - 高性能设计:通过预分配事件列表(events_)避免频繁内存扩容,初始大小 16 兼顾内存占用与扩容频率;
- 事件映射:将 epoll 事件(EPOLLIN/EPOLLOUT 等)与 Channel 的关注事件(kReadEvent/kWriteEvent)对应,完成事件分发;
- 资源安全管理:析构时关闭 epollfd_,避免系统资源泄漏。
在 Muduo 架构中,EPollPoller 是 EventLoop 与 Linux epoll 之间的 "桥梁",核心映射关系:
cpp
Poller 纯虚接口 → EPollPoller 实现 → 底层 epoll 系统调用
poll() → epoll_wait()
updateChannel() → epoll_ctl(ADD/MOD)
removeChannel() → epoll_ctl(DEL)
2. 核心成员变量解析
1. 静态常量:事件列表初始大小(性能优化)
cpp
private:
static const int kInitEventListSize = 16; // epoll_wait 事件列表初始大小
核心设计点:
- 初始大小 16:是 "内存占用" 与 "扩容频率" 的平衡值 ------ 太小会导致频繁扩容(epoll_wait 返回事件数超过列表大小时,需要 resize),太大则浪费内存;
- 静态常量:所有 EPollPoller 实例共享该值,避免每个实例重复定义。
2. epoll 核心资源
cpp
private:
typedef std::vector<struct epoll_event> EventList;
int epollfd_; // epoll 实例的 fd(由 epoll_create1 创建)
EventList events_; // epoll_wait 输出的事件列表(复用减少内存分配)
关键细节:
epollfd_:是 epoll 实例的唯一标识,所有epoll_ctl/epoll_wait操作都依赖这个 fd;events_:复用的事件列表 ------ 每次调用epoll_wait时,将事件写入这个预分配的 vector,避免每次调用都创建新数组,减少内存分配开销;EventList类型别名:简化std::vector<struct epoll_event>的书写,提升代码可读性。
3. 核心接口逐模块拆解
1. 构造 / 析构:epoll 资源的创建与释放
cpp
EPollPoller(EventLoop* loop); // 构造:创建 epollfd_,初始化 events_
~EPollPoller() override; // 析构:关闭 epollfd_,override 确保重写基类析构
核心逻辑:
- 构造函数:调用
epoll_create1(EPOLL_CLOEXEC)创建 epollfd(带 CLOEXEC 标记,exec 时关闭),并初始化events_大小为kInitEventListSize; - 析构函数:调用
::close(epollfd_)关闭 epoll 实例,释放系统资源; override关键字(C++11):显式标记重写基类虚函数,避免因函数签名错误导致 "重写失败"(如返回值 / 参数不一致)。
2. 重写 Poller 纯虚接口(核心)
这是 EPollPoller 实现 Poller 契约的核心,也是 epoll 功能的直接落地:
cpp
/// 等待 epoll 事件,超时时间 timeoutMs(毫秒),填充活跃 Channel 列表
/// 返回值:epoll_wait 唤醒的时间戳(用于 EventLoop 计时)
Timestamp poll(int timeoutMs, ChannelList* activeChannels) override;
/// 更新 Channel 的关注事件(新增/修改)
/// 对应 epoll_ctl(ADD/MOD),如 Channel 启用读事件则调用 MOD 操作
void updateChannel(Channel* channel) override;
/// 移除 Channel(停止监听 fd 事件)
/// 对应 epoll_ctl(DEL),并从 channels_ 中删除 fd 映射
void removeChannel(Channel* channel) override;
核心映射关系:
| EPollPoller 方法 | 底层 epoll 系统调用 | 核心作用 |
|---|---|---|
| poll() | epoll_wait() | 阻塞等待 IO 事件,返回触发的事件 |
| updateChannel() | epoll_ctl(ADD/MOD) | 新增 / 修改 fd 的关注事件 |
| removeChannel() | epoll_ctl(DEL) | 移除 fd 的所有关注事件 |
3. 私有辅助方法:分工明确的工具函数
1. fillActiveChannels:事件→Channel 映射(核心分发)
cpp
void fillActiveChannels(int numEvents, ChannelList* activeChannels) const;
核心作用:
- 输入:
epoll_wait返回的触发事件数numEvents; - 逻辑:遍历
events_中前numEvents个事件,根据事件的 fd 从channels_找到对应的 Channel,设置 Channel 的revents_(已发生事件),并将 Channel 加入activeChannels; - 价值:将 epoll 底层事件转换为上层 Channel 可识别的格式,完成 "事件→Channel" 的分发。
2. update:统一处理 epoll_ctl 操作(简化逻辑)
cpp
void update(int operation, Channel* channel);
核心作用:
- 输入:epoll_ctl 的操作码(EPOLL_CTL_ADD/EPOLL_CTL_MOD/EPOLL_CTL_DEL)、待操作的 Channel;
- 逻辑:构建
epoll_event结构体(设置关注的事件、绑定 Channel 指针),调用epoll_ctl(epollfd_, operation, channel->fd(), &event); - 价值:将
epoll_ctl的调用逻辑封装为统一方法,避免updateChannel/removeChannel中重复代码。
3. operationToString:调试辅助(可读字符串)
cpp
static const char* operationToString(int op);
核心作用:
- 将 epoll_ctl 的操作码(ADD/MOD/DEL)转换为可读字符串(如 "ADD"/"MOD"/"DEL");
- 应用场景:日志输出(如
LOG_TRACE << "epoll_ctl op = " << operationToString(op)),快速定位 epoll_ctl 操作类型,方便调试。
4. 核心设计亮点总结
| 设计点 | 解决的问题 | 价值 |
|---|---|---|
| 预分配事件列表(kInitEventListSize=16) | epoll_wait 每次都创建新数组,导致频繁内存分配 | 复用 events_ 减少内存开销,初始大小平衡内存与扩容频率 |
| override 关键字 | 重写基类虚函数时签名错误,导致 "隐式重载" 而非 "重写" | 编译期检查重写正确性,避免运行时错误 |
| 统一 update 方法 | epoll_ctl 调用逻辑分散在多个方法,代码冗余 | 集中处理 epoll_ctl,简化 updateChannel/removeChannel 逻辑 |
| 事件列表复用(events_) | 每次 epoll_wait 都重新分配事件数组,性能损耗 | 复用 vector 减少内存分配 / 释放,提升性能 |
| epollfd_ 带 CLOEXEC 标记 | fork+exec 后子进程继承 epollfd,导致资源泄漏 | exec 时自动关闭 epollfd,避免无用 fd 泄漏 |
二、 EPollPoller.cc
先贴出完整代码,再逐部分解释:
cpp
// Copyright 2010, Shuo Chen. All rights reserved.
// http://code.google.com/p/muduo/
//
// 本源代码的使用受 BSD 风格许可证约束
// 该许可证可在 License 文件中查阅。
// 作者:陈硕 (chenshuo at chenshuo dot com)
#include "muduo/net/poller/EPollPoller.h"
#include "muduo/base/Logging.h" // 日志库(LOG_TRACE/LOG_SYSFATAL 等)
#include "muduo/net/Channel.h" // Channel 类(管理 fd 和事件)
#include <assert.h> // 断言(调试用)
#include <errno.h> // 错误码定义
#include <poll.h> // poll 事件标志(用于静态断言)
#include <sys/epoll.h> // epoll 系统调用(epoll_create1/epoll_ctl/epoll_wait)
#include <unistd.h> // close 系统调用
using namespace muduo;
using namespace muduo::net;
// 静态断言:验证 Linux 下 epoll 和 poll 的事件标志值完全一致
// 核心目的:Channel 中使用的 events(如 POLLIN/POLLOUT)可直接传给 epoll,无需转换
static_assert(EPOLLIN == POLLIN, "epoll uses same flag values as poll");
static_assert(EPOLLPRI == POLLPRI, "epoll uses same flag values as poll");
static_assert(EPOLLOUT == POLLOUT, "epoll uses same flag values as poll");
static_assert(EPOLLRDHUP == POLLRDHUP, "epoll uses same flag values as poll");
static_assert(EPOLLERR == POLLERR, "epoll uses same flag values as poll");
static_assert(EPOLLHUP == POLLHUP, "epoll uses same flag values as poll");
// 匿名命名空间:仅当前文件可见的 Channel 状态常量
// 用于标记 Channel 在 epoll 中的状态(存储在 Channel::index_ 中)
namespace
{
const int kNew = -1; // 新创建的 Channel:未加入 epoll,也未加入 channels_
const int kAdded = 1; // 已添加的 Channel:已加入 epoll 和 channels_
const int kDeleted = 2; // 已删除的 Channel:从 epoll 移除,但仍在 channels_ 中
}
/// 构造函数:创建 epoll 实例,初始化事件数组
/// @param loop 所属的 EventLoop(传递给 Poller 基类)
EPollPoller::EPollPoller(EventLoop* loop)
: Poller(loop), // 初始化基类,绑定 EventLoop
epollfd_(::epoll_create1(EPOLL_CLOEXEC)), // 创建 epoll fd,带 FD_CLOEXEC 标志
events_(kInitEventListSize) // 初始化 epoll_event 数组,大小为 16
{
// epoll_create1 失败则输出致命日志并终止程序
if (epollfd_ < 0)
{
LOG_SYSFATAL << "EPollPoller::EPollPoller";
}
}
/// 析构函数:关闭 epoll fd,释放资源
EPollPoller::~EPollPoller()
{
::close(epollfd_);
}
/// 轮询 IO 事件(核心方法,重写 Poller 纯虚接口)
/// 底层调用 epoll_wait,处理返回结果,填充活跃 Channel 列表
/// @param timeoutMs 超时时间(毫秒,-1=无限等待,0=非阻塞)
/// @param activeChannels 输出参数,存储触发事件的活跃 Channel
/// @return 轮询返回的时间戳(用于计算事件处理耗时)
Timestamp EPollPoller::poll(int timeoutMs, ChannelList* activeChannels)
{
LOG_TRACE << "fd total count " << channels_.size(); // 调试日志:当前管理的 fd 总数
// 调用 epoll_wait 等待事件触发
// 参数:epoll fd | 事件数组首地址 | 数组大小 | 超时时间
int numEvents = ::epoll_wait(epollfd_,
&*events_.begin(), // vector 转原生数组指针
static_cast<int>(events_.size()),
timeoutMs);
int savedErrno = errno; // 保存 errno(避免日志调用覆盖)
Timestamp now(Timestamp::now()); // 获取当前时间戳
if (numEvents > 0) // 有事件触发
{
LOG_TRACE << numEvents << " events happened"; // 调试日志:触发的事件数
fillActiveChannels(numEvents, activeChannels); // 填充活跃 Channel 列表
// 若触发事件数等于数组大小,说明数组已满,扩容为 2 倍(避免下次 epoll_wait 丢失事件)
if (implicit_cast<size_t>(numEvents) == events_.size())
{
events_.resize(events_.size()*2);
}
}
else if (numEvents == 0) // 超时,无事件触发
{
LOG_TRACE << "nothing happened";
}
else // epoll_wait 调用失败
{
// 仅处理非预期错误:EINTR 是被信号中断,属于预期错误,忽略
if (savedErrno != EINTR)
{
errno = savedErrno; // 恢复 errno
LOG_SYSERR << "EPollPoller::poll()"; // 输出系统错误日志
}
}
return now;
}
/// 填充活跃 Channel 列表(核心辅助方法)
/// 遍历 epoll_wait 返回的事件,映射到对应的 Channel,设置 revents 并加入活跃列表
/// @param numEvents epoll_wait 返回的触发事件数
/// @param activeChannels 输出参数,存储活跃 Channel
void EPollPoller::fillActiveChannels(int numEvents,
ChannelList* activeChannels) const
{
// 断言:触发事件数不超过事件数组大小(防止越界)
assert(implicit_cast<size_t>(numEvents) <= events_.size());
// 遍历所有触发的事件
for (int i = 0; i < numEvents; ++i)
{
// epoll_event.data.ptr 存储的是 Channel 指针(update 时设置)
Channel* channel = static_cast<Channel*>(events_[i].data.ptr);
#ifndef NDEBUG // 调试模式下的校验:确保 Channel 存在且匹配
int fd = channel->fd();
ChannelMap::const_iterator it = channels_.find(fd);
assert(it != channels_.end());
assert(it->second == channel);
#endif
// 设置 Channel 的 revents_(触发的事件),供上层处理
channel->set_revents(events_[i].events);
// 将 Channel 加入活跃列表
activeChannels->push_back(channel);
}
}
/// 更新 Channel 关注的事件(核心方法,重写 Poller 纯虚接口)
/// 根据 Channel 的 index 状态,决定调用 epoll_ctl 的 ADD/MOD/DEL 操作
/// @param channel 要更新的 Channel
void EPollPoller::updateChannel(Channel* channel)
{
Poller::assertInLoopThread(); // 断言:当前线程是 EventLoop 线程(保证线程安全)
const int index = channel->index(); // 获取 Channel 的状态(kNew/kAdded/kDeleted)
LOG_TRACE << "fd = " << channel->fd()
<< " events = " << channel->events() << " index = " << index;
if (index == kNew || index == kDeleted) // 新 Channel 或已标记为删除的 Channel
{
// 执行 EPOLL_CTL_ADD 操作
int fd = channel->fd();
if (index == kNew) // 新 Channel:加入 channels_ 映射
{
assert(channels_.find(fd) == channels_.end());
channels_[fd] = channel;
}
else // index == kDeleted:已在 channels_ 中,仅状态更新
{
assert(channels_.find(fd) != channels_.end());
assert(channels_[fd] == channel);
}
channel->set_index(kAdded); // 标记 Channel 为已添加
update(EPOLL_CTL_ADD, channel); // 调用 epoll_ctl ADD
}
else // Channel 已添加(index == kAdded):执行 MOD 或 DEL
{
int fd = channel->fd();
(void)fd; // 抑制未使用变量警告(调试模式下会用到)
// 校验:Channel 必须在 channels_ 中且状态为 kAdded
assert(channels_.find(fd) != channels_.end());
assert(channels_[fd] == channel);
assert(index == kAdded);
if (channel->isNoneEvent()) // Channel 无关注事件:移除 epoll 关注
{
update(EPOLL_CTL_DEL, channel); // 调用 epoll_ctl DEL
channel->set_index(kDeleted); // 标记 Channel 为已删除
}
else // Channel 有关注事件:更新 epoll 关注的事件
{
update(EPOLL_CTL_MOD, channel); // 调用 epoll_ctl MOD
}
}
}
/// 移除 Channel(核心方法,重写 Poller 纯虚接口)
/// 从 channels_ 中删除映射,若需要则调用 epoll_ctl DEL
/// @param channel 要移除的 Channel
void EPollPoller::removeChannel(Channel* channel)
{
Poller::assertInLoopThread(); // 断言:当前线程是 EventLoop 线程
int fd = channel->fd();
LOG_TRACE << "fd = " << fd;
// 校验:Channel 必须在 channels_ 中,且无关注事件(确保已调用 updateChannel 设置为 DEL)
assert(channels_.find(fd) != channels_.end());
assert(channels_[fd] == channel);
assert(channel->isNoneEvent());
int index = channel->index(); // 获取 Channel 状态
assert(index == kAdded || index == kDeleted); // 仅允许已添加/已删除状态
// 从 channels_ 中删除该 fd 的映射
size_t n = channels_.erase(fd);
(void)n; // 抑制未使用变量警告
assert(n == 1); // 断言:仅删除一个元素
// 若 Channel 仍在 epoll 中(index == kAdded),调用 epoll_ctl DEL
if (index == kAdded)
{
update(EPOLL_CTL_DEL, channel);
}
channel->set_index(kNew); // 重置 Channel 状态为新创建
}
/// 封装 epoll_ctl 调用(核心辅助方法)
/// 统一处理 ADD/MOD/DEL 操作,设置 epoll_event 并调用 epoll_ctl
/// @param operation epoll_ctl 操作码(EPOLL_CTL_ADD/MOD/DEL)
/// @param channel 要操作的 Channel
void EPollPoller::update(int operation, Channel* channel)
{
struct epoll_event event;
memZero(&event, sizeof event); // 初始化 epoll_event 结构体(清零)
event.events = channel->events();// 设置关注的事件(如 EPOLLIN/EPOLLOUT)
event.data.ptr = channel; // 存储 Channel 指针(用于事件触发时快速映射)
int fd = channel->fd();
LOG_TRACE << "epoll_ctl op = " << operationToString(operation)
<< " fd = " << fd << " event = { " << channel->eventsToString() << " }";
// 调用 epoll_ctl 执行操作
if (::epoll_ctl(epollfd_, operation, fd, &event) < 0)
{
// DEL 操作失败:输出系统错误日志(非致命,fd 可能已关闭)
if (operation == EPOLL_CTL_DEL)
{
LOG_SYSERR << "epoll_ctl op =" << operationToString(operation) << " fd =" << fd;
}
else // ADD/MOD 操作失败:输出致命日志并终止程序
{
LOG_SYSFATAL << "epoll_ctl op =" << operationToString(operation) << " fd =" << fd;
}
}
}
/// 将 epoll_ctl 操作码转为字符串(调试辅助方法)
/// @param op epoll_ctl 操作码(EPOLL_CTL_ADD/MOD/DEL)
/// @return 对应的字符串(便于日志输出)
const char* EPollPoller::operationToString(int op)
{
switch (op)
{
case EPOLL_CTL_ADD:
return "ADD";
case EPOLL_CTL_DEL:
return "DEL";
case EPOLL_CTL_MOD:
return "MOD";
default:
assert(false && "ERROR op"); // 断言:不支持的操作码
return "Unknown Operation";
}
}
1. 代码整体核心逻辑回顾
EPollPoller.cpp 是 Poller 抽象接口的 Linux epoll 具体实现,核心围绕 "状态机管理 Channel + 高性能 epoll 调用 + 严格的状态校验 + 安全的资源管理" 展开:
- 编译期断言:保证 epoll 与 poll 的事件常量一致,Channel 的 events 可直接映射到 epoll_event.events;
- 状态机(kNew/kAdded/kDeleted):标记 Channel 在 Poller 中的状态,避免重复调用 epoll_ctl (ADD/DEL) 导致的错误;
- 性能优化:epoll_wait 事件列表自动扩容(2 倍),复用 vector 减少内存分配;
- 错误分级处理:epoll_ctl (DEL) 失败仅打日志(非致命),ADD/MOD 失败则 FATAL 终止(致命);
- 严格断言:DEBUG 模式下校验 fd 与 Channel 的映射关系,避免 fd 复用导致的错误。
2. 逐模块拆解核心实现
1. 前置编译期断言 + 状态常量(核心约束)
cpp
// 编译期断言:验证epoll和poll的事件常量值一致,保证Channel的events可直接映射到epoll_event.events
static_assert(EPOLLIN == POLLIN, "epoll uses same flag values as poll");
static_assert(EPOLLPRI == POLLPRI, "epoll uses same flag values as poll");
static_assert(EPOLLOUT == POLLOUT, "epoll uses same flag values as poll");
static_assert(EPOLLRDHUP == POLLRDHUP, "epoll uses same flag values as poll");
static_assert(EPOLLERR == POLLERR, "epoll uses same flag values as poll");
static_assert(EPOLLHUP == POLLHUP, "epoll uses same flag values as poll");
namespace
{
// Channel在Poller中的状态标记(避免重复ADD/DEL)
const int kNew = -1; // 新Channel:未加入epoll,也未在channels_中
const int kAdded = 1; // 已加入epoll,且在channels_中
const int kDeleted = 2; // 已从epoll中删除,但仍在channels_中
}
核心价值:
- 编译期断言:避免因系统头文件定义不一致,导致 Channel 的 events(如 kReadEvent=POLLIN)无法正确映射到 epoll_event.events,提前暴露问题;
- 状态常量:解决 epoll_ctl 的 "重复操作" 问题(如对已 ADD 的 fd 再次调用 ADD,会返回 EEXIST 错误),通过状态标记精准控制 epoll_ctl 操作类型。
2. 构造 / 析构:epoll 资源的 RAII 管理
cpp
EPollPoller::EPollPoller(EventLoop* loop)
: Poller(loop), // 调用基类构造,绑定ownerLoop_
epollfd_(::epoll_create1(EPOLL_CLOEXEC)), // 创建epoll实例,带CLOEXEC标记
events_(kInitEventListSize) // 初始化事件列表大小为16
{
if (epollfd_ < 0)
{
LOG_SYSFATAL << "EPollPoller::EPollPoller"; // 创建失败,致命错误
}
}
EPollPoller::~EPollPoller()
{
::close(epollfd_); // 析构关闭epollfd,释放系统资源
}
关键细节:
epoll_create1(EPOLL_CLOEXEC):Linux 2.6.27+ 支持,创建的 epollfd 带FD_CLOEXEC标记,fork+exec 后子进程自动关闭该 fd,避免资源泄漏;- 对比
epoll_create:epoll_create需要手动调用fcntl设置 CLOEXEC,epoll_create1一步到位,减少系统调用; - 事件列表初始化:
events_预分配 16 个 epoll_event 空间,避免首次 epoll_wait 时的内存分配。
3. poll 方法:等待 IO 事件(核心)
cpp
Timestamp EPollPoller::poll(int timeoutMs, ChannelList* activeChannels)
{
LOG_TRACE << "fd total count " << channels_.size();
// 调用epoll_wait:等待事件,超时时间timeoutMs(毫秒),结果写入events_
int numEvents = ::epoll_wait(epollfd_,
&*events_.begin(), // vector首地址(转为epoll_event*)
static_cast<int>(events_.size()),
timeoutMs);
int savedErrno = errno; // 保存errno,避免后续调用覆盖
Timestamp now(Timestamp::now()); // 记录epoll_wait唤醒的时间戳
if (numEvents > 0) // 有事件触发
{
LOG_TRACE << numEvents << " events happened";
fillActiveChannels(numEvents, activeChannels); // 填充活跃Channel列表
// 事件列表扩容:若返回事件数等于当前列表大小,下次可能溢出,扩容为2倍
if (implicit_cast<size_t>(numEvents) == events_.size())
{
events_.resize(events_.size()*2);
}
}
else if (numEvents == 0) // 超时,无事件
{
LOG_TRACE << "nothing happened";
}
else // 错误
{
// 仅处理非EINTR错误(EINTR是信号中断,属于预期错误,忽略)
if (savedErrno != EINTR)
{
errno = savedErrno;
LOG_SYSERR << "EPollPoller::poll()";
}
}
return now; // 返回唤醒时间戳,供EventLoop计时
}
&*events_.begin():vector 的begin()返回迭代器,*解引用得到首元素,&取地址转为epoll_event*,是 vector 作为数组使用的经典写法;- 自动扩容:当 epoll_wait 返回的事件数等于当前列表大小,说明下次可能有更多事件,扩容为 2 倍(指数扩容,平衡扩容频率与内存占用);
- 错误过滤:忽略 EINTR(epoll_wait 被信号中断),这是网络编程中常见的预期错误,无需处理;其他错误(如 EBADF)打系统错误日志。
4. fillActiveChannels:事件→Channel 分发(核心)
cpp
void EPollPoller::fillActiveChannels(int numEvents, ChannelList* activeChannels) const
{
assert(implicit_cast<size_t>(numEvents) <= events_.size()); // 断言:事件数不超过列表大小
for (int i = 0; i < numEvents; ++i)
{
// 从epoll_event的data.ptr获取Channel指针(update时绑定)
Channel* channel = static_cast<Channel*>(events_[i].data.ptr);
#ifndef NDEBUG // DEBUG模式:校验fd与Channel的映射关系
int fd = channel->fd();
ChannelMap::const_iterator it = channels_.find(fd);
assert(it != channels_.end());
assert(it->second == channel);
#endif
// 设置Channel的revents(已发生的事件)
channel->set_revents(events_[i].events);
// 将Channel加入活跃列表,供EventLoop遍历处理
activeChannels->push_back(channel);
}
}
核心逻辑:
data.ptr绑定 Channel:epoll_event 的data.ptr存储 Channel 指针,是 "事件→Channel" 映射的核心(替代传统的 fd 映射,避免 fd 复用导致的错误);- DEBUG 断言:确保触发事件的 fd 存在于
channels_中,且映射的 Channel 指针一致,避免 fd 复用导致的 "事件分发到错误 Channel"; set_revents:将 epoll 返回的事件(如 EPOLLIN|EPOLLERR)设置到 Channel,上层 Channel::handleEvent 会根据 revents 处理具体事件。
5. updateChannel:状态驱动的 epoll_ctl 操作(核心)
cpp
void EPollPoller::updateChannel(Channel* channel)
{
Poller::assertInLoopThread(); // 线程安全校验
const int index = channel->index(); // 获取Channel当前状态(kNew/kAdded/kDeleted)
LOG_TRACE << "fd = " << channel->fd()
<< " events = " << channel->events() << " index = " << index;
if (index == kNew || index == kDeleted)
{
// 新Channel 或 已删除的Channel → 调用EPOLL_CTL_ADD
int fd = channel->fd();
if (index == kNew)
{
assert(channels_.find(fd) == channels_.end());
channels_[fd] = channel; // 加入channels_映射
}
else // index == kDeleted
{
assert(channels_.find(fd) != channels_.end());
assert(channels_[fd] == channel);
}
channel->set_index(kAdded); // 更新状态为已加入
update(EPOLL_CTL_ADD, channel); // 调用epoll_ctl(ADD)
}
else
{
// 已加入的Channel → 处理MOD/DEL
int fd = channel->fd();
(void)fd; // 抑制未使用警告
assert(channels_.find(fd) != channels_.end());
assert(channels_[fd] == channel);
assert(index == kAdded);
if (channel->isNoneEvent()) // Channel无关注事件 → DEL
{
update(EPOLL_CTL_DEL, channel);
channel->set_index(kDeleted); // 更新状态为已删除
}
else // Channel有关注事件 → MOD
{
update(EPOLL_CTL_MOD, channel);
}
}
}
状态机逻辑梳理:
| Channel 状态 | 条件 | epoll_ctl 操作 | 新状态 |
|---|---|---|---|
| kNew | 首次添加 | EPOLL_CTL_ADD | kAdded |
| kDeleted | 重新启用事件 | EPOLL_CTL_ADD | kAdded |
| kAdded | 无关注事件 | EPOLL_CTL_DEL | kDeleted |
| kAdded | 事件变更 | EPOLL_CTL_MOD | kAdded |
核心价值:
- 精准控制 epoll_ctl 操作:避免对已 ADD 的 fd 再次 ADD(返回 EEXIST)、对已 DEL 的 fd 再次 DEL(返回 ENOENT);
- 状态驱动:所有操作基于 Channel 的 index 状态,逻辑清晰,避免硬编码。
6. removeChannel:安全移除 Channel(核心)
cpp
void EPollPoller::removeChannel(Channel* channel)
{
Poller::assertInLoopThread(); // 线程安全校验
int fd = channel->fd();
LOG_TRACE << "fd = " << fd;
// 前置断言:确保fd存在、映射正确、Channel无关注事件、状态合法
assert(channels_.find(fd) != channels_.end());
assert(channels_[fd] == channel);
assert(channel->isNoneEvent()); // 移除前必须先取消所有事件关注
int index = channel->index();
assert(index == kAdded || index == kDeleted);
// 从channels_中删除fd映射
size_t n = channels_.erase(fd);
(void)n;
assert(n == 1); // 确保只删除一个条目
// 若状态为kAdded,调用epoll_ctl(DEL)
if (index == kAdded)
{
update(EPOLL_CTL_DEL, channel);
}
channel->set_index(kNew); // 重置状态为新,方便后续复用
}
关键约束:
assert(channel->isNoneEvent()):移除 Channel 前必须先取消所有事件关注(调用channel->disableAll()),避免 epoll 仍监听该 fd;- 状态重置为 kNew:Channel 被移除后,若后续重新启用,可按 "新 Channel" 逻辑处理。
7. update:封装 epoll_ctl 调用(核心工具)
cpp
void EPollPoller::update(int operation, Channel* channel)
{
struct epoll_event event;
memZero(&event, sizeof event); // 初始化事件结构体
event.events = channel->events(); // 映射Channel的events到epoll_event
event.data.ptr = channel; // 绑定Channel指针(核心!)
int fd = channel->fd();
LOG_TRACE << "epoll_ctl op = " << operationToString(operation)
<< " fd = " << fd << " event = { " << channel->eventsToString() << " }";
// 调用epoll_ctl
if (::epoll_ctl(epollfd_, operation, fd, &event) < 0)
{
if (operation == EPOLL_CTL_DEL)
{
// DEL失败仅打日志(非致命,如fd已被DEL)
LOG_SYSERR << "epoll_ctl op =" << operationToString(operation) << " fd =" << fd;
}
else
{
// ADD/MOD失败是致命错误(如fd无效),FATAL终止
LOG_SYSFATAL << "epoll_ctl op =" << operationToString(operation) << " fd =" << fd;
}
}
}
核心设计:
event.data.ptr = channel:这是 Muduo 高性能的关键 ------ 传统 epoll 使用data.fd存储 fd,再通过 fd 查找 Channel;Muduo 直接存储 Channel 指针,省去 fd 查找步骤,且避免 fd 复用导致的错误;- 错误分级处理:
- DEL 失败:通常是 fd 已被删除或无效,非致命,仅打日志;
- ADD/MOD 失败:属于程序 bug 或系统故障(如 fd 关闭),致命,FATAL 终止。
8. operationToString:调试辅助(可读字符串)
cpp
const char* EPollPoller::operationToString(int op)
{
switch (op)
{
case EPOLL_CTL_ADD: return "ADD";
case EPOLL_CTL_DEL: return "DEL";
case EPOLL_CTL_MOD: return "MOD";
default:
assert(false && "ERROR op");
return "Unknown Operation";
}
}
核心价值:将 epoll_ctl 操作码转为可读字符串(如 ADD/DEL/MOD),日志输出更直观,方便调试(如快速定位是 ADD 还是 MOD 失败)。
3. 核心设计亮点总结
| 设计点 | 解决的问题 | 价值 |
|---|---|---|
| Channel 状态机(kNew/kAdded/kDeleted) | epoll_ctl 重复操作导致的 EEXIST/ENOENT 错误 | 精准控制 epoll_ctl 操作类型,避免系统调用错误 |
| event.data.ptr 绑定 Channel | 传统 data.fd 查找 Channel 耗时 + fd 复用错误 | O (1) 映射事件到 Channel,避免 fd 复用问题 |
| 事件列表自动扩容 | epoll_wait 事件数超过列表大小导致的溢出 | 动态适配事件数量,兼顾性能与内存 |
| 编译期断言验证事件常量 | epoll/poll 事件常量不一致导致的映射错误 | 编译期暴露问题,避免运行时错误 |
| 错误分级处理(DEL 日志 / ADD/MOD FATAL) | 所有错误都终止 / 都忽略,导致稳定性问题 | 区分致命 / 非致命错误,兼顾稳定性与容错性 |
| DEBUG 模式断言校验 | fd 复用 / 映射错误导致的事件分发错误 | 开发阶段快速定位问题,提升代码健壮性 |
总结
- 状态机核心:通过 kNew/kAdded/kDeleted 标记 Channel 状态,精准控制 epoll_ctl 操作,避免重复 ADD/DEL 错误;
- 高性能映射:epoll_event.data.ptr 直接绑定 Channel 指针,省去 fd 查找步骤,避免 fd 复用错误;
- 资源安全:RAII 管理 epollfd(析构关闭),事件列表复用 + 自动扩容,兼顾性能与内存;
- 严格校验:编译期断言验证事件常量,DEBUG 断言校验映射关系,线程校验保证操作在 Loop 线程执行;
- 错误分级:epoll_ctl (DEL) 失败仅打日志,ADD/MOD 失败致命终止,兼顾容错性与稳定性。