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所属的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的。

具体细节认真阅读代码

// 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模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • muduo网络库剖析——套接字Socket类

    作为一个宏大的、功能健全的muduo库,考虑的肯定是众多情况是否可以高效满足;而作为学习者,我们需要抽取其中的精华进行简要实现,这要求我们足够了解muduo库。 做项目 = 模仿 + 修改,不要担心自己学了也不会写怎么办,重要的是积累,学到了这些方法,如果下次在遇

    2024年01月19日
    浏览(31)
  • 长文梳理Muduo库核心代码及优秀编程细节剖析

    代码地址: https://github.com/yyg192/Cpp11-Muduo-MultiReactor  Muduo库是陈硕个人开发的Tcp网络编程库,支持Reactor模型。本人前段时间出于个人学习目的用c++11重构了Muduo库中核心的Multi-Reactor架构。这篇博文对Muduo库中的Multi-reactor架构代码进行逻辑梳理,同时认真剖析了作者每一处精妙

    2024年02月12日
    浏览(41)
  • C++类和对象-多态->多态的基本语法、多态的原理剖析、纯虚函数和抽象类、虚析构和纯虚析构

    #includeiostream using namespace std; //多态 //动物类 class Animal { public:     //Speak函数就是虚函数     //函数前面加上virtual,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。     virtual void speak()     {         cout \\\"动物在说话\\\" endl;     } }; //猫类 class Cat

    2024年02月20日
    浏览(38)
  • Spring源码中的抽象工厂模式

    Spring 框架中广泛运用了抽象工厂模式来实现其核心组件的创建与管理。以下是源码分析: 源码分析: 1. BeanFactory 与其实现 org.springframework.beans.factory.BeanFactory 是 Spring 中最基础的工厂接口,它代表了抽象工厂模式中的“抽象工厂”,定义了一系列用于获取、创建和管理 Bean(

    2024年04月25日
    浏览(32)
  • go network poller 一

    假如需要开发者去实现一套新的网络协议(例如 redis 的resp), 是基于TCP的, 那tcp这层的协议,是否需要开发者自己去实现? 这层如果自己实现, 其实很复杂, 会涉及很多算法相关. 因此, 出现了 socket 对传输层进行了抽象, 开发者不需要关注传输层具体的实现, 使用socket提供的接口,

    2024年02月05日
    浏览(43)
  • go 网络 network poller

    假如需要开发者去实现一套新的网络协议(例如 redis 的resp), 是基于TCP的, 那tcp这层的协议,是否需要开发者自己去实现? 这层如果自己实现, 其实很复杂, 会涉及很多算法相关. 因此, 出现了 socket 对传输层进行了抽象, 开发者不需要关注传输层具体的实现, 使用socket提供的接口,

    2024年02月05日
    浏览(38)
  • 手写Java设计模式之抽象工厂模式,附源码解读

    接上篇,抽象工厂模式将汽车的一些属性可以抽象出来,可以理解为给不同汽车品牌生成时加上不同的特性,如颜色等,具体代码如下: 引入颜色接口: 将颜色与汽车生成品牌抽象出来,增加抽象类: 继承抽象类,分别对不同属性的特征进行操作,如涂上颜色等,首先实现

    2024年04月25日
    浏览(55)
  • MCAL配置之ADC模块及IO抽象层源码分析

    ADC模块是将外部模拟信号转化为芯片内可识别的数字信号的中间模块,与MCU的关联为使用了MCU提供的GTM模块中的TOM/ATOM部分的定时效果。 在ADC界面中,主要配置界面为General, AdcGlobalInputClass和AdcHwUnit三个选项卡。在General选项卡中,通常会使能AdcErrorDetect、AdcReadGroup、AdcEnableSt

    2024年02月07日
    浏览(36)
  • 万字长文解析AQS抽象同步器核心原理(深入阅读AQS源码)

    在争用激烈的场景下使用基于CAS自旋实现的轻量级锁有两个大的问题: CAS恶性空自旋会浪费大量的CPU资源。 在SMP架构的CPU上会导致“总线风暴”。 解决CAS恶性空自旋的有效方式之一是以空间换时间,较为常见的方案有两种:分散操作热点、使用队列削峰。 JUC并发包使用的是

    2024年02月11日
    浏览(45)
  • Java Collections源码剖析

    Collections以静态方法的方式提供了很多通用算法和功能,这些功能大概可以分为以下两类。 一类是对容器接口对象进行操作,包括查找、排序、添加和修改。 另一类返回一个容器接口对象,适配器:将其他类型的数据转换为容器接口对象+装饰器:修饰一个给定容器接口对象

    2024年02月19日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包