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

33 篇文章 2 订阅
15 篇文章 7 订阅

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的时候,该文件描述符就需要进行关闭。 作用即回收无用文件描述符

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值