解析muduo源码之 EPollPoller.h & EPollPoller.cc

目录

[一、 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_createepoll_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 复用 / 映射错误导致的事件分发错误 开发阶段快速定位问题,提升代码健壮性

总结

  1. 状态机核心:通过 kNew/kAdded/kDeleted 标记 Channel 状态,精准控制 epoll_ctl 操作,避免重复 ADD/DEL 错误;
  2. 高性能映射:epoll_event.data.ptr 直接绑定 Channel 指针,省去 fd 查找步骤,避免 fd 复用错误;
  3. 资源安全:RAII 管理 epollfd(析构关闭),事件列表复用 + 自动扩容,兼顾性能与内存;
  4. 严格校验:编译期断言验证事件常量,DEBUG 断言校验映射关系,线程校验保证操作在 Loop 线程执行;
  5. 错误分级:epoll_ctl (DEL) 失败仅打日志,ADD/MOD 失败致命终止,兼顾容错性与稳定性。
相关推荐
凯子坚持 c2 小时前
C++基于微服务脚手架的视频点播系统---客户端(3)
开发语言·c++·微服务
寻寻觅觅☆2 小时前
东华OJ-基础题-86-字符串统计(C++)
开发语言·c++·算法
楼田莉子2 小时前
Linux学习:进程信号
linux·运维·服务器·c++·学习
D.不吃西红柿2 小时前
CPM.cmake轻量级包管理器
c++·cmake·cpm.cmake
绿浪19842 小时前
C#与C++高效互操作指南
c++·c#
CSDN_RTKLIB2 小时前
std::string打印原始字节查看是否乱码
c++
shilei_c2 小时前
qt qDebug无输出问题解决
开发语言·c++·算法
一切尽在,你来2 小时前
C++ 零基础教程 - 第4讲-实现简单计算器
开发语言·c++
是店小二呀2 小时前
Visual Studio C++ 工程架构深度解析:从 .vcxproj 到 Qt MOC 的文件管理实录
c++·qt·visual studio