EpollPoller是对 Linux epoll 的 C++ 封装,它负责:监听 fd → 等待事件 → 返回有事件的 Channel,它继承自 Poller(抽象类)

EpollPoller.h

1
2
3
4
5
6
7
8
9
#pragma once 

#include <vector>
#include <sys/epoll.h>

#include "Poller.h"
#include "Timestamp.h"

class Channel;
1
class EpollPoller : public Poller

继承Poller

成员变量

1
2
3
4
5
6
7
private:
static const int kInitEventListSize = 16;

using EventList = std::vector<epoll_event>;

int epollfd_;
EventList events_;
  • kInitEventListSizeepoll 事件数组(events_)的初始容量。
  • epollfd_epoll_create/epoll_create1创建的文件描述符,代表创建的 epoll 实例
  • events_是epoll 存放事件的数组

内部函数

1
2
3
4
5
private:
// 填写活跃的连接
void fillActiveChannels(int numEvents, ChannelList *activeChannels) const;
// 更新channel通道
void update(int operation, Channel *channel);//映射epoll_ctl()
  • fillActiveChannels把发生事件的 Channel 打包好,交给 EventLoop 处理。
  • update封装底层操作 epoll_ctl,增、删、改监听。

public函数

1
2
3
public:
EpollPoller(EventLoop* loop);//映射epoll_create1()
~EpollPoller() override;

父类是虚析构,子类重写虚构。

1
2
3
4
public:
Timestamp poll(int timeoutMs, ChannelList* activeChannels) override; //映射epoll_wait()
void updateChannel(Channel* channel) override;
void removeChannel(Channel* channel) override;

重写子类虚函数。

EpollPoller.cc

1
2
3
4
5
6
7
8
9
10
11
#include "EpollPoller.h"
#include "Logger.h"
#include "Channel.h"

#include <errno.h>
#include <unistd.h>
#include <strings.h>

const int kNew = -1; //表示一个Channel还没有添加到Poller中
const int kAdded = 1; //表示一个Channel已经添加到这个Poller中
const int kDeleted = 2; //表示一个Channel已经从Poller中删除掉了

Channel 里 index_ 变量,表示它 epoll 中状态的三个标识。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
EpollPoller::EpollPoller(EventLoop* loop)
: Poller(loop)
, epollfd_(::epoll_create1(EPOLL_CLOEXEC))
, events_(kInitEventListSize)
{
if(epollfd_ < 0)
{
LOG_FATAL("epoll_create1 failed, errno : {}", errno);
}
}

EpollPoller::~EpollPoller()
{
::close(epollfd_);
}
  • Poller(loop)调用父类 Poller 的构造函数,把 EventLoop 传给父类保存

  • EPOLL_CLOEXEC安全标志程序执行 exec自动关闭这个 fd防止文件描述符泄漏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void EpollPoller::updateChannel(Channel* channel)
{
int index = channel->index();
LOG_INFO("func={} => fd={} events={} index={}", __FUNCTION__, channel->fd(), channel->events(), index);

if(index == kNew || index == kDeleted)
{
if(index == kNew)
{
int fd = channel->fd();
channels_[fd] = channel;
}
channel->set_index(kAdded);
update(EPOLL_CTL_ADD, channel);
}
else
{
if(channel->isNoneEvent())
{
channel->set_index(kDeleted);
update(EPOLL_CTL_DEL, channel);
}
else
{
update(EPOLL_CTL_MOD, channel);
}
}
}
  • index == kNew 或 kDeleted= 还没在 epoll 里

​ → 执行 EPOLL_CTL_ADD

​ → 把 fd 加入 epoll 监听

​ → 状态改成 kAdded

  • index == kAdded= 已经在 epoll 里
    • 如果不需要监听任何事件→ EPOLL_CTL_DEL删除
    • 如果需要修改监听事件(读 / 写)→ EPOLL_CTL_MOD修改
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void EpollPoller::update(int operation, Channel *channel)
{
epoll_event ev;
bzero(&ev, sizeof ev);

int fd = channel->fd();

ev.events = channel->events();
ev.data.ptr = channel;

if(::epoll_ctl(epollfd_, operation, fd, &ev) < 0)
{
if(operation == EPOLL_CTL_DEL)
{
LOG_ERROR("epoll_ctl del error, errno : {}", errno);
}
else
{
LOG_FATAL("epoll_ctl add/mod error, errno : {}", errno);
}
}
}
  1. 准备一个 epoll_event 结构体

  2. 要监听的事件Channel 本身地址 塞进去

  3. 调用 epoll_ctl告诉内核:

    • 增加监听(ADD)
    • 修改监听(MOD)
    • 删除监听(DEL)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void EpollPoller::removeChannel(Channel* channel)
{
int fd = channel->fd();
int index = channel->index();
channels_.erase(fd);

LOG_INFO("func={} => fd={}", __FUNCTION__, fd);

if(index == kAdded)
{
update(EPOLL_CTL_DEL, channel);
}

channel->set_index(kNew);
}

从 epoll 里删掉一个 Channel,同时从管理 map 里删掉,恢复成初始状态。

  1. 从 channels_ map 里删掉→ Poller 不再记录它

  2. 如果正在监听 → 调用 epoll_ctl DEL→ 内核不再监听这个 fd

  3. 把 channel 状态设为 kNew→ 变回全新未使用状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Timestamp EpollPoller::poll(int timeoutMs, ChannelList *activeChannels)
{
LOG_INFO("func={} => fd total count:{} \n", __FUNCTION__, channels_.size());

int numEvents = epoll_wait(epollfd_, events_.data(), static_cast<int>(events_.size()), timeoutMs);
/*epoll_wait 失败时设置了 errno,我们必须立即保存它,否则在执行后续的 LOG_INFO 等操作后,
errno 的值就被覆盖了,我们就无法知道 epoll_wait 真正的失败原因。*/
int saveErrno = errno;
Timestamp now(Timestamp::now());

if(numEvents > 0)
{
LOG_INFO("{} events happened \n", numEvents);
fillActiveChannels(numEvents, activeChannels);
if(numEvents == events_.size())
{
events_.resize(numEvents * 2);
}
}
else if(numEvents == 0)
{
LOG_DEBUG("{} timeout! \n", __FUNCTION__);
}
else
{
if (saveErrno != EINTR)
{
errno = saveErrno;
LOG_ERROR("EPollPoller::poll() error! errno = {}", saveErrno);
}
}
return now;
}

调用 epoll_wait 阻塞等待事件 → 有事件就填充到 activeChannels → 返回当前时间

  1. epoll_wait 阻塞等待

  2. 有事件 → 填充 activeChannels

  3. 事件满了自动扩容

  4. 返回当前时间戳

  5. EventLoop 拿到 activeChannels 执行回调

为什么要保存 saveErrno = errno

因为 errno 是全局的,任何函数调用都可能覆盖它!

  • 必须在 epoll_wait 结束第一时间保存
  • 否则 LOG 打印都会把错误码改了
1
2
3
4
5
6
7
8
9
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->emplace_back(channel);
}
}

把 epoll 返回来的事件,转换成 Channel,塞进 activeChannels 列表给 EventLoop 处理

  • 注册时(ev.data.ptr = channel;)直接把 Channel 地址存进内核,事件触发时 → 内核直接把地址还给我们,不用查 map、不用找 fd,零查找,高效。
  • 因为这里存放的是简单的类型,也可以用push_back,如果是智能指针、自定义类型等复杂类型,建议直接使用emplace_back,现代C++也推荐直接用emplace_back直接在 vector 里原地构造,零拷贝

源码地址

EpollPoller.h:https://gitee.com/lpzdinghai/lpzmuduo/blob/master/EpollPoller.h

EpollPoller.cc:https://gitee.com/lpzdinghai/lpzmuduo/blob/master/EpollPoller.cc