muduo网络库源码分析(1) —— Reactor的关键结构

主要参考了陈硕的书《Linux多线程服务端编程》,结合自己的理解摘了一些重要的的句子和函数、数据成员进行分析。

使用的代码自己用C++11抄了一个muduo库,简单版本,文章的代码删除了所有的Log和assert。

TCP网络编程最本质是的处理三个半事件

Reactor的关键结构

Reactor最核心的是事件分发机制,即将IO multiplexing拿到的IO事件分发给各个文件描述符(fd)的事件处理函数。

Reactor 模型 有四个组件

The reactor design pattern is an event handling pattern for handling service requests delivered concurrently to a service handler by one or more inputs. The service handler then demultiplexes the incoming requests and dispatches them synchronously to the associated request handlers.

反应器设计模式是一种事件处理模式,用于处理由一个或多个输入并发传递给服务处理程序的服务请求。 然后,服务处理程序对传入的请求进行多路分解,并将它们同步分派到相关联的请求处理程序。

重要组件:Event事件、Reactor反应堆、Demultiplex事件分发器、Eventhandler事件处理器

image-20211210220823265

Channel —— one fd per Channel (Event & Eventhandler)

Channel的构造函数,绑定一个loop,一个fd

Channel::Channel(EventLoop *loop, int fd)
    : loop_(loop), fd_(fd), events_(0), revents_(0), index_(-1), tied_(false)
{
}
  • 每个Channel对象自始至终只属于一个EventLoop,因此每个Channel对象都只属于某一个IO线程。每个Channel对象自始至终**只负责一个文件描述符(fd)**的IO事件分发,但它并不拥有这个fd,
    也不会在析构的时候关闭这个fd。
  • Channel class的数据成员。其中events_是它关心的IO事件,由用户设置;revents_是目前活动的事件,由EventLoop/Poller设置;这两个字段都是bit pattern,它们的名字来自poll(2)的struct pollfd。

Channel 理解为通道,封装了sockfd和其感兴趣的event,如EPOLLIN、EPOLLOUT事件

  • 负责设置回调函数(Read、Write、Close、Error)

  • 文件描述符的事件状态

    • 设置状态的(enableReading \ disableReading \ enableWriting \ disableWriting \ disableAll)
    • 返回当前的 isWriting 、isReading、isNone

Channel的重要数据成员

fd、events_、revents_、callback

    EventLoop *loop_; // 事件循环
    const int fd_;    // fd, Poller监听的对象
    int events_; // 注册fd感兴趣的事件
    int revents_; // poller返回的具体发生的事件,执行的事件
    int index_; // 自己在pollfds_数组中的下标,-1表示无所属

    std::weak_ptr<void> tie_;
    bool tied_;

    // 因为channel通道里面能够获知fd最终发生的具体的事件revents,所以它负责调用具体事件的回调操作
    ReadEventCallback readCallback_;
    EventCallback writeCallback_;
    EventCallback closeCallback_;
    EventCallback errorCallback_;
与Channel的交互

EventLoop => Channel

  • 根据poller通知的channel发生的具体事件, 由channel负责调用具体的回调操作

    • void Channel::handleEventWithGuard(Timestamp receiveTime)

    • // 根据poller通知的channel发生的具体事件, 由channel负责调用具体的回调操作
      void Channel::handleEventWithGuard(Timestamp receiveTime)
      {
          if ((revents_ & EPOLLHUP) && !(revents_ & EPOLLIN)) 调用 closeCallback_
      
          if (revents_ & EPOLLERR) ......
      
          if (revents_ & (EPOLLIN | EPOLLPRI)) ......
      
          if (revents_ & EPOLLOUT) ......
      }
      

Channel => Poller

  • Channel::update()会调用EventLoop::updateChannel(Channel *),后者会转而调用Poller::updateChannel(Channel *)更新channel。

Poller 和 EPollPoller - Demultiplex

  • Poller class是IO multiplexing的封装。在muduo中是个抽象基类,因为muduo同时支持poll(2)和epoll(4)两种IOmultiplexing机制。Poller是EventLoop的间接成员,只供其owner——EventLoop在IO线程调用,因此无须加锁。其生命期与EventLoop相等。
    Poller并不拥有Channel,Channel在析构之前必须自己unregister(EventLoop::removeChannel()),避免空悬指针。

  • ChannelMap

Poller 的重要数据成员

protected:
    // map的key: sockfd     value: sockfd所属的channel通道类型,one fd per Channel
    using ChannelMap = std::unordered_map<int, Channel*>;
    ChannelMap channels_;

private:
    EventLoop *ownerLoop_; // 定义Poller所属的事件循环EventLoop
// EventLoop可以通过该接口获取默认的IO复用的具体实现
    static Poller* newDefaultPoller(EventLoop *loop);

EPollPoller

数据成员

/**
 * 负责epoll的使用
 * epoll_create
 * epoll_ctl     add/mod/del
 * epoll_wait
 */
class EPollPoller : public Poller
{
public:
    EPollPoller(EventLoop *loop);
    ~EPollPoller() override;

    // 重写基类Poller的抽象方法
    Timestamp poll(int timeoutMs, ChannelList *actibeChannels) override;
    void updateChannel(Channel *channel) override;
    void removeChannel(Channel *channel) override;

private:
    static const int kInitEventListSize = 16;

    // 填写活跃的连接
    void fillActiveChannels(int numEvents, ChannelList *activeChannels) const;
    // 更新channel通道
    void update(int operation, Channel *channel);

    using EventList = std::vector<epoll_event>;

    int epollfd_;
    EventList events_; // events_ 对应的 Channel,Channel的数组
};

EPollPoller的构造函数

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);
    }
}
  • **fillActiveChannels()遍历pollfds_,找出有活动事件的fd,把它对应的Channel填入activeChannels。**这个函数的复杂度是O(N),其中N是pollfds_的长度,即文件描述符数目。为了提前结束循环,每找到一个活动fd就递减numEvents,这样当numEvents减为0时表示活动fd都找完了,不必做无用功。当前活动事件revents会保存在Channel中,供Channel::handleEvent()使用。
// 填写活跃的连接
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列表了
    }
}
  • update()的主要功能是负责维护和更新pollfds_数组。 —— epoll_ctl

    红黑树(自己用的是hashmap)
    添加新Channel的复杂度是O(logN),
    更新已有的Channel的复杂度是O(1) (因为Channel记住了自己在pollfds_数组中的下标,因此可以快速定位)。
    removeChannel()的复杂度也将会是O(logN)。

void EPollPoller::updateChannel(Channel *channel)
{
    const int index = channel->index();
    if (index == kNew || index == kDeleted) // 注册  EPOLL_CTL_ADD

    else  // channel已经在poller上注册过了
    {
        // 查看 channel 的event 为0删除    EPOLL_CTL_DEL
        // 否则 更改 EPOLL上 fd对应的event EPOLL_CTL_MOD
    }
}

// 更新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; // 放弃了 fd 成员使用channel,channel里存了fd
    
    if (::epoll_ctl(epollfd_, operation, fd, &event) < 0) ...
}

EventLoop —— Reactor

one loop per thread顾名思义每个线程只能有一个EventLoop对象,因此EventLoop的构造函数会检查当前线程是否已经创建了其他EventLoop对象,遇到错误就终止程序(LOG_FATAL)。

  • EventLoop的构造函数会记住本对象所属的线程(threadId_)。创建了EventLoop对象的线程是IO线程,其主要功能是运行事件循环EventLoop:: loop()。EventLoop对象的生命期通常和其所属的线程一样长,它不必是heap对象。

  • EventLoop通过scoped_ptr来间接持有Poller,因此不必包含,只需前向声明Poller class。

  • 一个 loop 只有一个 wakeup_fd ,EventLoop通过往 wakeup_fd写东西,唤醒相应的 loop
    每一个 wakeup_fd 也封装成 Channel注册在 Poller上 ——std::unique_ptr<Channel> wakeupChannel_

EventLoop的构造函数
EventLoop::EventLoop()
    : looping_(false) // 标识未开启loop循环
    , quit_(false)	// 标识未退出
    , callingPendingFunctors_(false)	// 
    , threadId_(CurrentThread::tid())	// 标识执行当前loop的线程id
    , poller_(Poller::newDefaultPoller(this))	// 构造一个poller
    , wakeupFd_(createEventfd())	// 通过往 wakeup_fd写东西,唤醒相应的 loop
    , wakeupChannel_(new Channel(this, wakeupFd_)) 	// wakeup_fd注册进Channel
{
    LOG_DEBUG("EventLoop created %p in thread %d \n", this, threadId_);
    if (t_loopInThisThread) { /*报警,两个loop出现在一个thread*/   }
    else
    {
        t_loopInThisThread = this;
    }

    // 设置wakeupfd的事件类型以及发生事件后的回调操作
    wakeupChannel_->setReadCallback(std::bind(&EventLoop::handleRead, this));
    // 每一个eventloop都将监听wakeupchannel的EPOLLIN读事件了
    wakeupChannel_->enableReading();
}

EventLoop的重要数据成员

   using ChannelList = std::vector<Channel*>;

    std::atomic_bool looping_;  // 原子操作,通过CAS实现的,是否执行looping_
    std::atomic_bool quit_; // 标识退出loop循环
    
    const pid_t threadId_; // 记录当前loop所在线程的id

    Timestamp pollReturnTime_; // poller返回发生事件的channels的时间点
    std::unique_ptr<Poller> poller_;

    int wakeupFd_; // 主要作用,当mainLoop获取一个新用户的channel,通过轮询算法选择一个subloop,通过该成员唤醒subloop处理channel
    std::unique_ptr<Channel> wakeupChannel_;

    ChannelList activeChannels_;

    std::atomic_bool callingPendingFunctors_; // 标识当前loop是否有需要执行的回调操作
    std::vector<Functor> pendingFunctors_; // 存储loop需要执行的所有的回调操作
    std::mutex mutex_; // 互斥锁,用来保护上面vector容器的线程安全操作
EventLoop::runInLoop(const Functor& cb) —— 区分回调函数的执行线程

EventLoop有一个非常有用的功能:在它的IO线程内执行某个用户任务回调,即EventLoop::runInLoop(const Functor& cb),其中Functor是boost::function<void()>。

  • 如果用户在当前IO线程调用这个函数,回调会同步进行;
  • 如果用户在其他线程调用runInLoop(),cb会被加入队列,IO线程会被唤醒来调用这个Functor;

loop函数 -> Reactor组件调用 时序图

时序图

最终是由Channel 负责回调函数的执行

image-20211216200454854

loop

void EventLoop::loop()
{
    looping_ = true; // 正在looping
    quit_ = false; // 未退出

    while(!quit_)
    {
        activeChannels_.clear();
        // 监听两类fd   一种是client的fd,一种wakeupfd
        pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_); // 调用poll()
        
        
        for (Channel *channel : activeChannels_)
        {
            channel->handleEvent(pollReturnTime_); // channel处理相应的事件
        }
        
        // 执行当前EventLoop事件循环需要处理的回调操作
        doPendingFunctors();
    }

    
    looping_ = false;
}

pool

Timestamp EPollPoller::poll(int timeoutMs, ChannelList *activeChannels)
{
    int numEvents = ::epoll_wait(epollfd_, &*events_.begin(), static_cast<int>(events_.size()), timeoutMs);
    int saveErrno = errno;
    Timestamp now(Timestamp::now());

    if (numEvents > 0)
    {
        fillActiveChannels(numEvents, activeChannels); // 返回所有活跃的channel
        if (numEvents == events_.size()) {}//扩容
    }
    else if (numEvents == 0) {} // log_bug
    else
    {
        if (saveErrno != EINTR) // EINTR(Interrupted system call) 可忽略
        {
            errno = saveErrno;
            LOG_ERROR("EPollPoller::poll() err!");
        }
    }
    return now;
}

handleEvent

// fd得到poller通知以后,处理事件,负责执行具体的回调
void Channel::handleEvent(Timestamp receiveTime)
{
    if (tied_)
    {
        std::shared_ptr<void> guard = tie_.lock();
        if (guard)
        {
            handleEventWithGuard(receiveTime);
        }
    }
    else
    {
        handleEventWithGuard(receiveTime);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值