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所属的channel
using 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::iterator
auto 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中的ChannelList
void 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的。
具体细节认真阅读代码文章来源:https://www.toymoban.com/news/detail-408180.html
// 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 kDeleted
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) // 加入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中删除,成为kNew
LOG_INFO("func=%s => fd=%d\n", __FUNCTION__, fd);
int index = channel->index();
if(index == kAdded)
{
update(EPOLL_CTL_DEL, channel);
}
// 注意这里设置的时kNew而不是kDeleted
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列表
}
}
// 供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的时候,该文件描述符就需要进行关闭。 作用即回收无用文件描述符文章来源地址https://www.toymoban.com/news/detail-408180.html
到了这里,关于Muduo库源码剖析(二)——Poller和EPollPoller的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!