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; void update(int operation, Channel *channel);
|
fillActiveChannels把发生事件的 Channel 打包好,交给 EventLoop 处理。
update封装底层操作 epoll_ctl,增、删、改监听。
public函数
1 2 3
| public: EpollPoller(EventLoop* loop); ~EpollPoller() override;
|
父类是虚析构,子类重写虚构。
1 2 3 4
| public: Timestamp poll(int timeoutMs, ChannelList* activeChannels) override; 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; const int kAdded = 1; const int kDeleted = 2;
|
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_); }
|
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); } } }
|
准备一个 epoll_event 结构体
把 要监听的事件 和 Channel 本身地址 塞进去
调用 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 里删掉,恢复成初始状态。
从 channels_ map 里删掉→ Poller 不再记录它
如果正在监听 → 调用 epoll_ctl DEL→ 内核不再监听这个 fd
把 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);
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 → 返回当前时间
epoll_wait 阻塞等待
有事件 → 填充 activeChannels
事件满了自动扩容
返回当前时间戳
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