> 文章列表 > Muduo库源码剖析(二)——Poller和EPollPoller

Muduo库源码剖析(二)——Poller和EPollPoller

Muduo库源码剖析(二)——Poller和EPollPoller

Poller抽象基类

重点代码详解

// Poller.h
#include "noncopyable.h"
#include "Timestamp.h"#include <vector>
#include <unordered_map>class Channel;
class EventLoop;// muduo库中多路事件分发器的核心IO复用模块
class Poller : noncopyable
{
public:using ChannelList = std::vector<Channel*>;Poller(EventLoop *loop);virtual ~Poller() = default;// 给各种类型的IO复用保留统一的接口virtual Timestamp poll(int timeoutMs, ChannelList *activeChannels) = 0;virtual void updateChannel(Channel *channel) = 0;virtual void removeChannel(Channel *channel) = 0;// 判断参数channel是否在当前Poller中bool hasChannel(Channel *channel) const;// EventLoop可通过该接口获取默认的IO复用具体实现// 但注意实现不在Poller.cc中,依赖倒置static Poller* newDefaultPoller(EventLoop *loop);protected:// key:sockfd  value: sockfd所属的channelusing ChannelMap = std::unordered_map<int, Channel*>;ChannelMap channels_;
private:EventLoop *ownerLoop_; // Poller所属的事件循环EventLoop
};// Poller.cc
#include "Poller.h"
#include "Channel.h"Poller::Poller(EventLoop *loop): ownerLoop_(loop){}bool Poller::hasChannel(Channel *channel) const
{// auto -> Poller::ChannelMap::iteratorauto it = channels_.find(channel->fd());return it != channels_.end() && it->second == channel;
}

newDefaultPoller()这个成员函数实现并不在 Poller.cc中,因为考虑到,这个函数功能是获取一个具体的Poller,如果在Poller.cc中实现就需要include EpollPoller或PollPoller, 这样在抽象基类里包含实现类头文件不好!

基类不应该依赖派生类

根据依赖倒置原则:高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。

所以muduo库中将其单独放到一个文件中进行定义

// DefaultPoller.cc
#include "Poller.h"
// #include "EPollPoller.h"#include <stdlib.h>Poller* Poller::newDefaultPoller(EventLoop *loop)
{if(::getenv("MUDUO_USE_POLL")){// 生成POLL的实例return nullptr;}else {// [TODO]生成EPOLL实例return nullptr;}
}

EPollPoller派生类

要点

该类核心就是封装了epoll的操作:

  • epoll_create
  • epoll_ctl add/mod/del
  • epoll_wait

头文件类声明代码如下

// EPollPoller.h
#pragma once#include "Poller.h"
#include "Timestamp.h"#include <vector>
#include <sys/epoll.h>class Channel;class EPollPoller : public Poller
{
public:EPollPoller(EventLoop *loop);~EPollPoller() override;// 重写Timestamp poll(int timeoutMs, ChannelList *activeChannels) override;void updateChannel(Channel *channel) override;void removeChannel(Channel *channel) override;private:static const int KInitEventListSize = 16; // 事件链表初始大小// 填写活跃的连接到EventLoop中的ChannelListvoid fillActiveChannels(int numEvents, ChannelList *activeChannels) const;// 更新channel对应socket的感兴趣的事件void update(int operation, Channel *channel);// 用vector方便扩容using EventList = std::vector<epoll_event>;int epollfd_;EventList events_;
};

EPollPoller.cc 中设置了三个channel状态标志

// channel未添加到poller的ChannelMap中
const int kNew = -1; // channel成员初始index_ = -1
// channel已添加到poller的事件监视中。即,上epoll树监视了。
const int kAdded = 1;
// channel 从poller的监视列表删除但仍在poller的 ChannelMap 中,但还在eventLoop的ChannelList中
const int kDeleted = 2;

EPollPoller::poll

EPollPoller::poll 主要工作就是进行事件循环即调用 epoll_wait,若有事件发生:

  • 将活跃(有事件发生的)Channel添加到所属eventloop中的ChannelList
  • 若发生事件数 == 事件数组(events_)容量, 则2倍扩容

EPollPoller::updateChannel

EPollPoller::updateChannel 的主要工作是更新Channel 在 Poller上的状态:

  • 若 index == kNew || index == kDeleted
    • 若 index == kNew,则加入Poller的 ChannelMap<int, Channel *>
    • 设置 index 为 kAdded,将channel对应的fd和感兴趣的事件添加到EPOLL的监视列表
  • 若 index == kAdded ,即已添加到EPOLL上监视
    • 若 Channel 没有感兴趣的事件,则将channel对应的fd从EPOLL监视列表移除,但没有从Poller的ChannelMap中删除。

EPollPoller::fillActiveChannels

EPollPoller::fillActiveChannels 主要功能就是将Channel添加到 所属Eventloop中的ChannelList中。

EventLoop和Poller相关数据结构关系

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FJcWN2dd-1680870738725)(Muduo库源码剖析(二)]——Poller和EPollPoller.assets/image-20230330105759134.png)

EventLoop中的ChannelList始终是 大于等于 Poller中的ChannelMap的。

具体细节认真阅读代码

// EPollPoller.cc
#include "EPollPoller.h"
#include "Logger.h"
#include "Channel.h"#include <unistd.h>
#include <errno.h>
#include <strings.h>// channel未添加到poller中
const int kNew = -1; // channel成员初始index_ = -1
// channel已添加到poller中
const int kAdded = 1;
// channel 从poller中删除,但还在eventLoop的ChannelList中
const int kDeleted = 2;// EPOLL_CLOEXEC使得exec时关闭无效的文件描述符
EPollPoller::EPollPoller(EventLoop *loop): Poller(loop), epollfd_(::epoll_create1(EPOLL_CLOEXEC)), events_(KInitEventListSize)
{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);// 将活跃(有事件发生的)Channel添加到activeChannels中,在EventLoop中管理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 now;
}// channel update remove => EventLoop updateChannel removeChannel
void EPollPoller::updateChannel(Channel *channel)
{// index() 得出channel状态,即kNew or kAdded or kDeletedconst 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) // 加入Poller的ChannelMap{int fd = channel->fd();channels_[fd] = channel; //}channel->set_index(kAdded);update(EPOLL_CTL_ADD, channel);}else // channel已经在poller上注册过,即加入了 ChannelMap{int fd = channel->fd();if(channel->isNoneEvent()) // 没有感兴趣的事件,从内核事件表删除{update(EPOLL_CTL_DEL, channel);// kDeleted表示Poller不监视该channel,但未从ChannelMap中删除channel->set_index(kDeleted);}else {update(EPOLL_CTL_MOD, channel);}}
}
// ???从poller中拆除通道
void EPollPoller::removeChannel(Channel *channel)
{int fd = channel->fd();channels_.erase(fd); // 从ChannelMap中删除,成为kNewLOG_INFO("func=%s => fd=%d\\n", __FUNCTION__, fd);int index = channel->index();if(index == kAdded) {update(EPOLL_CTL_DEL, channel);}// 注意这里设置的时kNew而不是kDeletedchannel->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列表}
}// 供updateChannel调用
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 // 视mod和add出错为致命的错误{LOG_FATAL("epoll_ctl add/mod error:%d\\n", errno);}}
}

杂项

epoll_create1(EPOLL_CLOEXEC)

当我们用execve执行其他程序的时候,全新的程序会替换子进程中的地址空间,数据段,堆栈,此时保存与父进程文件描述符也就不存在了,也无法进行关闭,这时候就需要FD_CLOEXEC, 表示子进程在执行exec的时候,该文件描述符就需要进行关闭。 作用即回收无用文件描述符