文章目录
终于要开始我们的重点:事件分发起和事件循环了,在这里我们将揭开事件驱动的IO多路复用模型的神秘面纱!
EpollPoller事件分发器类
成员变量和成员函数解释
这些都是在头文件中声明的,我们可以先对类中封装的各个方法进行合理的研究和猜测。
私有的成员函数和成员变量
在这里我们简单介绍一下私有成员函数和成员变量
私有成员函数如下:
void fillActiveChannels(int numEvents, ChannelList *activeChannels) const; void update(int operation, Channel *channel);
fillActiveChannels()
这里主要就是将epoll_wait
返回的活跃事件填充到activeChannels
中。update()
这里的根据操作类型(添加、修改、删除),调用epoll_ctl来更新epoll实例中的Channel对象。
在Channel类中,我们也写了一个update,它的具体实现是loop_->updateChannel(this);
,调用了EventLoop中的updateChannel,所以我们有理由怀疑,其中的updateChannel()
就是在调用这里的update方法
私有成员变量
static const int kInitEventListSize = 16; using EventList = std::vector<epoll_event>; int epollfd_; EventList events_;
kInitEventListSize
:初始事件列表大小。EventList
:用于存储epoll事件的向量类型。int epollfd_
:epoll实例的文件描述符。EventList events_
:存储从epoll_wait返回的事件列表。
成员函数
EPollPoller(EventLoop *Loop); ~EPollPoller() override; //重写基类Poller的抽象方法 Timestamp poll(int timeoutMs, ChannelList *activeChannels) override; void updateChannel(Channel *channel) override; void removeChannel(Channel *channel) override;
Timestamp poll(int timeoutMs, ChannelList *activeChannels)
:- 调用epoll_wait等待事件发生,将活跃的事件填充到activeChannels中。
void updateChannel(Channel *channel)
:- 更新或添加一个Channel对象到epoll实例中,调用epoll_ctl。
void removeChannel(Channel *channel)
:- 从epoll实例中移除一个Channel对象,调用epoll_ctl。
具体实现
#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 = -1; EPollPoller::EPollPoller(EventLoop *loop) : Poller(loop) , epollfd_(::epoll_create1(EPOLL_CLOEXEC)) , events_(kInitEventListSize) { //创建了vector<epoll_events> if (epollfd_ < 0) LOG_FATAL("epoll_create error:%d\n", errno); } EPollPoller::~EPollPoller() { ::close(epollfd_); } Timestamp EPollPoller::poll(int timeoutMs, ChannelList *activeChannels) { 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 happened \n", numEvents); 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() err!"); } } return Timestamp(); } 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; } 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; bzero(&event, sizeof event); int fd = channel->fd(); event.events = channel->events(); 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); } } }
常量的作用
// channel未添加到poller中 const int kNew = -1; // channel的成员index_ = -1 // channel已添加到poller中 const int kAdded = 1; // channel从poller中删除 const int kDeleted = 2;
他们主要用于表示 channel的状态,在后续的方法具体实现中会体现到。
构造函数和析构函数
EPollPoller::EPollPoller(EventLoop *loop) : Poller(loop) , epollfd_(::epoll_create1(EPOLL_CLOEXEC)) , events_(kInitEventListSize) // vector<epoll_event> { if (epollfd_ < 0) { LOG_FATAL("epoll_create error:%d \n", errno); } } EPollPoller::~EPollPoller() { ::close(epollfd_); }
- 构造函数:创建一个
epoll
实例epollfd_
,随后我们需要初始化我们所关注的事件列表大小events_
, - 析构函数:我们知道,我们将所有监控的事件都委托给了内核的
epoll
实例来进行管理,该实例底层是一颗红黑树。我们最后析构的时候,可以直接关闭close
,就可以关闭所有网络IO的文件描述符了。
⭐️poll函数
Timestamp EPollPoller::poll(int timeoutMs, ChannelList *activeChannels) { 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 happened \n", numEvents); 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() err!"); } } return Timestamp(); }
他就是实现我们多路分发的函数:
poll
函数使用epoll_wait
等待事件发生,并将活跃的事件填充到activeChannels
中。- 如果发生事件,将这些事件填充到 activeChannels,并在必要时扩展事件列表。
- 返回当前的时间戳,主要是为了后续方便打日志和进行管理。
updateChannel
函数
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) { int fd = channel->fd(); if (index == kNew) { channels_[fd] = channel; } channel->set_index(kAdded); update(EPOLL_CTL_ADD, channel); } else { 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 实例中的事件,该函数肯定会被EventLoop封装,然后再由Channel自己来进行调用。- 如果是新添加的 Channel,则在 epoll 中注册该文件描述符。
- 如果 Channel 没有感兴趣的事件,则将其从 epoll 中删除。
removeChannel
函数
void EPollPoller::removeChannel(Channel *channel) { int fd = channel->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 从 epoll 实例中删除,并更新其状态。这一看就是我们的EventLoop需要调用的函数。
removeChannel
和updateChannel
从这两个函数理我们可以看出,他们其实是为EventLoop提供操作Channel的方法。从代码的具体实现细节来看,我们可以领略到 channel 为什么要设置一个 index_
标志,主要就是为了实现channel的复用,我们总不能每次有新连接都新建一个channel,连接断开就删除channel吧!
⭐️fillActiveChannels
函数
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); } }
fillActiveChannels 函数将 epoll_wait 返回的所有活跃事件填充到 activeChannels 列表中。
然手我们介绍一下
event.data
,我们将已经被激活的event直接拿到手,这里就需要用到我们的event.data.ptr
:data
字段是一个联合体,具体结构包含了我们常用的int fd
和void *ptr
;
ptr 是一个通用指针,可以用来指向任何类型的数据。它通常用于关联用户自定义的数据结构(这里是我们的Channel*),以便在事件触发时可以快速访问这些数据。例如,你可以将 ptr 设置为你的应用程序中某个特定对象的指针,当对应的文件描述符触发事件时,你的应用程序可以通过 ptr 直接访问到这个对象。然后调用channel的
set_revents
方法,可以将已经被激活的事件直接初始化到我们的channel中。随后把 channel 推到我们的
activeChannels
⭐️update 函数
void EPollPoller::update(int operation, Channel *channel) { epoll_event event; bzero(&event, sizeof event); int fd = channel->fd(); event.events = channel->events(); 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); } } }
update 函数根据操作类型(添加、修改或删除)调用 epoll_ctl 来更新 epoll 实例中的 Channel。
其实说白了update就是用来封装epoll_ctl的。
该函数被 EPollPoller::removeChannel
、EPollPoller::updateChannel
调用,用来更新Channel的封装的fd以及其需要监控的相关事件。
总结
EPollPoller 类实现了基于 epoll 的 I/O 多路复用,通过监控多个文件描述符上的事件,并在事件发生时通知相应的 Channel 对象来处理事件。通过实现这些函数,EPollPoller 能够高效地管理和分发事件。
下一节,我们将讲解EventLoop类的具体实现!