3. EPollPoller
约 3131 字大约 10 分钟
2026-04-06
EPollPoller 简介
EPollPoller 类可以理解为 Poller 的 epoll 版本实现,它是 muduo 中真正和 Linux 的 epoll 打交道的类。
如果说 Poller 是“抽象接口”,那么 EPollPoller 就是“干活的具体工人”。它负责把 Channel 里感兴趣的事件注册到内核的 epoll 事件监听器上,并在 epoll_wait() 返回后把发生事件的 Channel 收集起来交给 EventLoop 处理。
可以把它理解为:
- Channel:我关心什么事件,以及事件发生后该做什么。
- EPollPoller:帮我把这些事件交给 epoll 监听,并等待事件发生。
- EventLoop:拿到活跃 Channel 后,依次调用它们的回调函数。
EPollPoller 类重要的成员变量
1. 重要成员
int epollfd_:epoll_create1()创建出来的 epoll 实例对应的文件描述符。
可以理解为 epoll 的“总控制器 ID”,后续所有epoll_ctl()和epoll_wait()都依赖它。EventList events_:用于存放epoll_wait()返回的事件集合。
每次调用poll()时,内核会把有事件发生的epoll_event填到这个数组里。static const int kInitEventListSize = 16:初始事件数组大小。
也就是说,一开始先给events_分配 16 个空间,如果一次epoll_wait()返回的事件数等于当前容量,就再扩容。
2. EPollPoller 中的状态标记
在 EPollPoller.cc 中定义了三个状态常量,它们是配合 Channel::index_ 使用的:
const int kNew = -1; // 某个channel还没添加至Poller // channel的成员index_初始化为-1
const int kAdded = 1; // 某个channel已经添加至Poller
const int kDeleted = 2; // 某个channel已经从Poller删除这三个状态分别代表什么?
- kNew:这个 Channel 还没有加入到 EPollPoller 中。
- kAdded:这个 Channel 已经被添加到了 epoll 监听器中。
- kDeleted:这个 Channel 曾经被添加过,但后来从 epoll 中删除了。
为什么需要这些状态?
因为 EPollPoller 需要知道:
- 这个 Channel 是第一次加入监听。
- 这个 Channel 是已经在监听中,只是修改监听事件。
- 这个 Channel 已经删掉了,现在又要重新加回来。
Channel::index_ 就是用来记录这个状态的。
EPollPoller 类重要的成员方法
1. 构造 / 析构
EPollPoller(EventLoop *loop);
~EPollPoller() override;EPollPoller::EPollPoller(EventLoop *loop)
: Poller(loop)
, epollfd_(::epoll_create1(EPOLL_CLOEXEC))
, events_(kInitEventListSize) // vector<epoll_event>(16)
{
if (epollfd_ < 0)
{
LOG_FATAL("epoll_create error:%d \n", errno);
}
}
EPollPoller::~EPollPoller()
{
::close(epollfd_);
}构造函数主要做了两件事:
- 调用 Poller(loop),把所属的 EventLoop 传给基类。
- 调用 epoll_create1(EPOLL_CLOEXEC) 创建 epoll 实例,并把返回值保存到
epollfd_。
- EPOLL_CLOEXEC 的作用:
它表示:当当前进程 exec 新程序时,这个 epoll 文件描述符会自动关闭,避免 fd 泄漏。
析构函数则负责关闭 epollfd_,释放 epoll 资源。
2. poll(int timeoutMs, ChannelList *activeChannels)
Timestamp poll(int timeoutMs, ChannelList *activeChannels) override;Timestamp EPollPoller::poll(int timeoutMs, ChannelList *activeChannels)
{
// 由于频繁调用poll 实际上应该用LOG_DEBUG输出日志更为合理 当遇到并发场景 关闭DEBUG日志提升效率
LOG_INFO("func=%s => fd total count:%lu\n", __FUNCTION__, channels_.size());
int numEvents = ::epoll_wait(epollfd_, &*events_.begin(), static_cast<int>(events_.size()), timeoutMs);
int saveErrno = errno;
Timestamp now(Timestamp::now());
if (numEvents > 0)
{
LOG_INFO("%d events happend\n", numEvents); // LOG_DEBUG最合理
fillActiveChannels(numEvents, activeChannels);
if (numEvents == events_.size()) // 扩容操作
{
events_.resize(events_.size() * 2);
}
}
else if (numEvents == 0)
{
LOG_DEBUG("%s timeout!\n", __FUNCTION__);
}
else
{
if (saveErrno != EINTR)
{
errno = saveErrno;
LOG_ERROR("EPollPoller::poll() error!");
}
}
return now;
}poll() 是 EPollPoller 最核心的函数。
它的作用是:
- 调用
epoll_wait()等待事件发生。 - 把发生事件的 Channel 收集到 activeChannels。
- 如果返回的事件太多,就扩容 events_。
- 返回当前时间戳。
这个函数的本质:poll() 做的是 “等待事件” + “收集活跃 Channel”,它不执行回调,回调是 Channel::handleEvent() 去执行的。
参数说明
- timeoutMs:epoll 等待的超时时间。
- activeChannels:输出参数,用来保存本次有事件发生的 Channel。
epoll_wait() 返回值含义
> 0:有事件发生。== 0:超时,没有事件。< 0:出错。
为什么要记录 now?
因为 EventLoop 和 Channel 回调通常需要知道“这个事件是什么时候发生的”,方便打日志、做延迟统计等。
3. updateChannel(Channel *channel)
void updateChannel(Channel *channel) override;// channel update remove => EventLoop updateChannel removeChannel => Poller updateChannel removeChannel
void EPollPoller::updateChannel(Channel *channel)
{
const int index = channel->index();
LOG_INFO("func=%s => fd=%d events=%d index=%d\n", __FUNCTION__, channel->fd(), channel->events(), index);
if (index == kNew || index == kDeleted)
{
if (index == kNew)
{
int fd = channel->fd();
channels_[fd] = channel;
}
else // index == kDeleted
{
}
channel->set_index(kAdded);
update(EPOLL_CTL_ADD, channel);
}
else // channel已经在Poller中注册过了
{
int fd = channel->fd();
if (channel->isNoneEvent())
{
update(EPOLL_CTL_DEL, channel);
channel->set_index(kDeleted);
}
else
{
update(EPOLL_CTL_MOD, channel);
}
}
}updateChannel() 的作用是:根据 Channel 当前的状态,把它同步到 epoll 中。
它会根据 channel->index() 的状态来决定是:
- EPOLL_CTL_ADD
- EPOLL_CTL_MOD
- EPOLL_CTL_DEL
分情况理解
1)index == kNew
说明这个 Channel 还是新的,之前没加入过 epoll。
这时候要做两件事:
channels_[fd] = channel:把它记录到 channels_ 里。update(EPOLL_CTL_ADD, channel):把它添加到 epoll。
然后把状态改成 kAdded。
2)index == kDeleted
说明这个 Channel 之前被删过,现在又要重新加入。
这时候通常也会走 EPOLL_CTL_ADD,并把状态改成 kAdded。
3)index == kAdded
说明这个 Channel 已经在 epoll 中了。
这时候要再看它当前的 events_:
如果 events_ == kNoneEvent,说明它不想监听任何事件了,就用 EPOLL_CTL_DEL 删除。 否则就是修改监听事件,用 EPOLL_CTL_MOD。
这段逻辑的本质
updateChannel() 其实就是把 Channel 的“兴趣”同步给内核:
- 新增监听
- 修改监听
- 删除监听
提示
为什么 updateChannel() 里 kDeleted 分支是空的?
kDeleted = “还在 Poller 管理中,只是暂时没注册 epoll”,该 Channel 已经在 channels_ 里,只需要通过 epoll_ctl(ADD) 重新加回 epoll,不需要再做任何额外逻辑。
4. removeChannel(Channel *channel)
void removeChannel(Channel *channel) override;// 从Poller中删除channel
void EPollPoller::removeChannel(Channel *channel)
{
int fd = channel->fd();
channels_.erase(fd);
LOG_INFO("func=%s => fd=%d\n", __FUNCTION__, fd);
int index = channel->index();
if (index == kAdded)
{
update(EPOLL_CTL_DEL, channel);
}
channel->set_index(kNew);
}removeChannel() 的作用是:把某个 Channel 从当前 Poller 中彻底移除。
逻辑说明
- 先从 channels_ 中删除这个 fd 对应的 Channel。
- 如果它当前状态是 kAdded,说明它还在 epoll 中,就调用 EPOLL_CTL_DEL。
- 最后把它的状态设回 kNew。
提示
为什么要先 erase?
因为这个 Channel 已经不再属于当前 Poller 的管理范围了,后续 epoll_wait() 返回相关事件时不应该再找到它。正因为这样,后面的 channel->set_index(kNew); 是设置的 kNew 而不是 kDeleted 。
5. fillActiveChannels(int numEvents, ChannelList *activeChannels)
void fillActiveChannels(int numEvents, ChannelList *activeChannels) const;// 填写活跃的连接
void EPollPoller::fillActiveChannels(int numEvents, ChannelList *activeChannels) const
{
for (int i = 0; i < numEvents; ++i)
{
Channel *channel = static_cast<Channel *>(events_[i].data.ptr);
channel->set_revents(events_[i].events);
activeChannels->push_back(channel); // EventLoop就拿到了它的Poller给它返回的所有发生事件的channel列表了
}
}这个函数的作用是:把 epoll_wait() 返回的事件,转换成 Channel 列表。
它做了什么
对每个 epoll_event:
- 从
data.ptr里取出对应的Channel *。 - 把事件类型写到
channel->revents_。 - 把这个 Channel 放进
activeChannels。
提示
为什么用 data.ptr?
因为在 update() 里已经把:
event.data.ptr = channel;
所以 epoll_wait() 返回后,可以直接拿回原来的 Channel *。
这是一种非常常见的技巧: 把业务对象指针放进 epoll_event 里,事件返回时直接取回。
6. update(int operation, Channel *channel)
void update(int operation, Channel *channel);// 更新channel通道 其实就是调用epoll_ctl add/mod/del
void EPollPoller::update(int operation, Channel *channel)
{
epoll_event event;
::memset(&event, 0, sizeof(event));
int fd = channel->fd();
event.events = channel->events();
//这里是联合体所以data.fd写了没有意义会被data.ptr覆盖
// event.data.fd = fd;
event.data.ptr = channel;
if (::epoll_ctl(epollfd_, operation, fd, &event) < 0)
{
if (operation == EPOLL_CTL_DEL)
{
LOG_ERROR("epoll_ctl del error:%d\n", errno);
}
else
{
LOG_FATAL("epoll_ctl add/mod error:%d\n", errno);
}
}
}这个函数就是 对 epoll_ctl() 的封装。
它的作用
根据传入的 operation:
- EPOLL_CTL_ADD
- EPOLL_CTL_MOD
- EPOLL_CTL_DEL
把 channel 注册、修改或删除到 epoll 中。
关键步骤
1)准备 epoll_event event;, ::memset(&event, 0, sizeof(event));
先把结构体清零,避免脏数据。
2)设置监听事件 event.events = channel->events();
这里把 Channel 当前感兴趣的事件写进去。
3)保存 Channel * event.data.ptr = channel;
这样 epoll_wait() 返回时,就能把原始 Channel 找回来。
4)调用 epoll_ctl(), ::epoll_ctl(epollfd_, operation, fd, &event)
把操作真正交给内核。
提示
epoll_event 为什么用 data.ptr?
因为 epoll_event 里的 data 是联合体,fd 和 ptr 只能放一个。
这里选择放 ptr,是为了直接拿回 Channel *,避免再通过 fd 去查找,提高效率。
EPollPoller 和 Poller、Channel 的关系
可以这样理解:
Channel 负责保存:
- fd
- 感兴趣事件
- 实际发生事件
- 回调函数
EPollPoller 负责:
- 用 epoll 监听这些 Channel
- 把 Channel 注册到内核
- 等待事件发生
- 收集活跃 Channel
EventLoop 负责:
- 驱动 EPollPoller::poll()
- 遍历活跃 Channel
- 调用 Channel::handleEvent()
EPollPoller 的整体工作流程
1. 注册阶段
当外部调用:
channel->enableReading();
会一路走到:
Channel::update()
-> EventLoop::updateChannel()
-> EPollPoller::updateChannel()
-> EPollPoller::update()
-> epoll_ctl(...)2. 等待阶段
在 EventLoop::loop() 中不断调用:
poller->poll(timeoutMs, &activeChannels);这里 EPollPoller 会调用 epoll_wait() 阻塞等待。
3. 收集阶段
当某些 fd 发生事件后:
epoll_wait() 返回
fillActiveChannels() 把事件对应的 Channel 找出来
填入 revents_
放入 activeChannels4. 处理阶段
EventLoop 遍历 activeChannels:
channel->handleEvent(receiveTime);然后由 Channel 根据 revents_ 执行对应回调。
你可以这样理解 EPollPoller
EPollPoller 就像一个“基于 epoll 的事件登记员”和“事件收件员”:
Channel 告诉它:“我关心这个 fd 的读/写事件” 它帮忙把这些关注交给内核 事件一发生,它就把对应的 Channel 收集出来 最后交给 EventLoop 和 Channel 去处理
总结
EPollPoller 的核心职责
- 封装 Linux epoll 的使用
- 管理所有已注册的 Channel
- 负责 epoll_create1、epoll_ctl、epoll_wait
- 把发生事件的 Channel 收集到 activeChannels
- 不直接执行业务回调,只负责“监听”和“分发”
一句话总结
Channel 是事件描述者,EPollPoller 是事件监听器,EventLoop 是事件调度者。
EPollPoller 源码
#pragma once
#include <vector>
#include <sys/epoll.h>
#include "Poller.h"
#include "Timestamp.h"
/**
* epoll的使用:
* 1. epoll_create
* 2. epoll_ctl (add, mod, del)
* 3. epoll_wait
**/
class Channel;
class EPollPoller : public Poller
{
public:
EPollPoller(EventLoop *loop);
~EPollPoller() override;
// 重写基类Poller的抽象方法
Timestamp poll(int timeoutMs, ChannelList *activeChannels) override;
void updateChannel(Channel *channel) override;
void removeChannel(Channel *channel) override;
private:
static const int kInitEventListSize = 16;
// 填写活跃的连接
void fillActiveChannels(int numEvents, ChannelList *activeChannels) const;
// 更新channel通道 其实就是调用epoll_ctl
void update(int operation, Channel *channel);
using EventList = std::vector<epoll_event>; // C++中可以省略struct 直接写epoll_event即可
int epollfd_; // epoll_create创建返回的fd保存在epollfd_中
EventList events_; // 用于存放epoll_wait返回的所有发生的事件的文件描述符事件集
};#include <errno.h>
#include <unistd.h>
#include <string.h>
#include "EPollPoller.h"
#include "Logger.h"
#include "Channel.h"
const int kNew = -1; // 某个channel还没添加至Poller // channel的成员index_初始化为-1
const int kAdded = 1; // 某个channel已经添加至Poller
const int kDeleted = 2; // 某个channel已经从Poller删除
EPollPoller::EPollPoller(EventLoop *loop)
: Poller(loop)
, epollfd_(::epoll_create1(EPOLL_CLOEXEC))
, events_(kInitEventListSize) // vector<epoll_event>(16)
{
if (epollfd_ < 0)
{
LOG_FATAL("epoll_create error:%d \n", errno);
}
}
EPollPoller::~EPollPoller()
{
::close(epollfd_);
}
Timestamp EPollPoller::poll(int timeoutMs, ChannelList *activeChannels)
{
// 由于频繁调用poll 实际上应该用LOG_DEBUG输出日志更为合理 当遇到并发场景 关闭DEBUG日志提升效率
LOG_INFO("func=%s => fd total count:%lu\n", __FUNCTION__, channels_.size());
int numEvents = ::epoll_wait(epollfd_, &*events_.begin(), static_cast<int>(events_.size()), timeoutMs);
int saveErrno = errno;
Timestamp now(Timestamp::now());
if (numEvents > 0)
{
LOG_INFO("%d events happend\n", numEvents); // LOG_DEBUG最合理
fillActiveChannels(numEvents, activeChannels);
if (numEvents == events_.size()) // 扩容操作
{
events_.resize(events_.size() * 2);
}
}
else if (numEvents == 0)
{
LOG_DEBUG("%s timeout!\n", __FUNCTION__);
}
else
{
if (saveErrno != EINTR)
{
errno = saveErrno;
LOG_ERROR("EPollPoller::poll() error!");
}
}
return now;
}
// channel update remove => EventLoop updateChannel removeChannel => Poller updateChannel removeChannel
void EPollPoller::updateChannel(Channel *channel)
{
const int index = channel->index();
LOG_INFO("func=%s => fd=%d events=%d index=%d\n", __FUNCTION__, channel->fd(), channel->events(), index);
if (index == kNew || index == kDeleted)
{
if (index == kNew)
{
int fd = channel->fd();
channels_[fd] = channel;
}
else // index == kDeleted
{
}
channel->set_index(kAdded);
update(EPOLL_CTL_ADD, channel);
}
else // channel已经在Poller中注册过了
{
int fd = channel->fd();
if (channel->isNoneEvent())
{
update(EPOLL_CTL_DEL, channel);
channel->set_index(kDeleted);
}
else
{
update(EPOLL_CTL_MOD, channel);
}
}
}
// 从Poller中删除channel
void EPollPoller::removeChannel(Channel *channel)
{
int fd = channel->fd();
channels_.erase(fd);
LOG_INFO("func=%s => fd=%d\n", __FUNCTION__, fd);
int index = channel->index();
if (index == kAdded)
{
update(EPOLL_CTL_DEL, channel);
}
channel->set_index(kNew);
}
// 填写活跃的连接
void EPollPoller::fillActiveChannels(int numEvents, ChannelList *activeChannels) const
{
for (int i = 0; i < numEvents; ++i)
{
Channel *channel = static_cast<Channel *>(events_[i].data.ptr);
channel->set_revents(events_[i].events);
activeChannels->push_back(channel); // EventLoop就拿到了它的Poller给它返回的所有发生事件的channel列表了
}
}
// 更新channel通道 其实就是调用epoll_ctl add/mod/del
void EPollPoller::update(int operation, Channel *channel)
{
epoll_event event;
::memset(&event, 0, sizeof(event));
int fd = channel->fd();
event.events = channel->events();
//这里是联合体所以data.fd写了没有意义会被data.ptr覆盖
// event.data.fd = fd;
event.data.ptr = channel;
if (::epoll_ctl(epollfd_, operation, fd, &event) < 0)
{
if (operation == EPOLL_CTL_DEL)
{
LOG_ERROR("epoll_ctl del error:%d\n", errno);
}
else
{
LOG_FATAL("epoll_ctl add/mod error:%d\n", errno);
}
}
}