muduo中封装fd和事件的Channel类

muduo库中的Channel类封装了需要监听的fd以及fd感兴趣的事件events和返回的事件revents
在这里插入图片描述

TcpServer相当于是muduo库提供给外部编写服务器程序的一个类,相当于一个工具箱,把muduo库有关服务器的编程相关的东西,包括反应堆,事件分发器,事件回调都打包一块了

我们看看它的成员变量
在这里插入图片描述
EventLoop就是开启epoll_wait事件循环,可以理解成epoll,我们看看EventLoop具体怎么实现的
在这里插入图片描述
EventLoop的成员变量
在这里插入图片描述
Poller相当于是epoll抽象的概念,用Poller当做基类,在派生类中实现了poll和epoll,poll和epoll负责事件分发

此外,epoll还需要监听socket。我们注意到,在EventLoop类内除了编译器内置类型,自定义类型只有两个,Poller和Channel

我们看看Channel的一些成员变量
在这里插入图片描述
Channel包含了监听的sockfd,感兴趣的事件和发生的事件
在这里插入图片描述

Eventloop是事件循环(epoll_wait),相当于事件分发器,包含了Poller(epoll)和Channel(epoll监听的sockfd,以及它对应的事件和最终发生的事件),对应Reactor模型上的多路事件分发器Demultiplex

一个channel属于一个EventLoop,一个EventLoop对应多个channel,一个channel封装了一个fd和这个fd对应的感兴趣的事件以及发生的事件

Channel要向Poller注册感兴趣事件,Poller通知Channel发生的事件,Channel得到关于fd的事件通知会调用预置的回调操作

一个线程对应一个eventloop,一个eventloop有一个poller,一个poller可以监听多个channel,每个channel属于一个eventloop,一个eventloop有很多channel,这就是多路;一个线程监听多路fd

Channel.h

#pragma once

#include "noncopyable.h"
#include "Timestamp.h"

#include <functional>
#include <memory>

class EventLoop;

/**
 *  Channel理解为通道,封装了sockfd和其感兴趣的事件,如EPOLLIN、EPOLLOUT事件,还包括Poller返回的具体事件
 */

class Channel : noncopyable{
    public:
        using EventCallBack =  std::function<void()>;
        using ReadEventCallBack = std::function<void(Timestamp)>;

        Channel(EventLoop* loop, int fd);         // loop是指针,编译阶段能确定大小,不需要知道EventLoop的定义,无需包含头文件
        ~Channel();
        
        // 得到poller通知以后,处理事件
        void handleEvent(Timestamp receiveTime);  // 由于receiveTime是对象,不是指针,编译阶段需要确定对象的大小,所以只有Timestamp的声明不够,需要有类定义,即需要包含头文件

        // 设置回调操作 不需要形参的局部对象,将左值转换成右值
        void setReadCallBack(ReadEventCallBack cb){ readCallback_ = std::move(cb); }
        void setWriteCallBack(EventCallBack cb){ writeCallBack_ = std::move(cb); }
        void setCloseCallBack(EventCallBack cb){ closeCallBack_ = std::move(cb); }
        void errorCloseCallBack(EventCallBack cb){ errorCallBack_ = std::move(cb); }

        // 防止channel被手动remove,channel还在执行回调操作,处理fd上的数据
        void tie(const std::shared_ptr<void>&);
        int fd() const { return fd_; }

        int events() const{ return events_; }
        // 设置revents_,poller(poll/epoll)在监听fd,发生事件后调用set_revents告诉channel发生了什么事件
        void set_revents(int revt){ revents_ = revt; }
        
        void enableReading(){
            events_ |= kReadEvent;  // 相当于是添加感兴趣的事件,把events_中和read相关的位 置1
            update();               // epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &events_)
        }
        void disableReading(){
            events_ &= ~kReadEvent; // 删除读事件,取反后&,会把events_中和read相关的位 置0
            update();
        }
        void enableWriting(){ events_ |= kWriteEvent; update(); }
        void disableWriting(){ events_ &= ~kWriteEvent; update(); }
        void disableAll(){ events_ = kNoneEvent; update(); }

        bool isNoneEvent() const{ return events_ == kNoneEvent; }
        bool isWritingEvent() const{ return events_ & kWriteEvent; }
        bool isReadingEvent() const{ return events_ & kReadEvent; }

        int index(){ return index_; }
        void set_index(int idx){ index_ = idx; }

        // one thread per thread
        // 一个channel对应一个EventLoop,一个EventLoop对应多个channel,一个channel包含一个fd以及这个fd对应的感兴趣的事件以及发生的事件
        EventLoop* ownerLoop(){ return loop_; }

        // 用于删除channel
        void remove();

    private:
        void update();
        void handleEventWithGaurd(Timestamp receiceTime);

        // 表示当前fd的状态,对什么事件感兴趣
        static const int kNoneEvent;
        static const int kReadEvent;
        static const int kWriteEvent;

        EventLoop* loop_;  // 归属于哪个事件循环 epoll_wait
        const int fd_;     // Poller监听的对象
        int events_;       // 注册fd感兴趣的事件
        int revents_;      // Poller返回的具体发生的事件
        int index_;

        std::weak_ptr<void> tie_;  // 绑定自己,防止channel被手动remove后,我们还在使用channel
        bool tied_;

        // Poller通知Channel发生的事件会放在revents_,所以Channel负责调用具体事件的回调操作
        ReadEventCallBack readCallback_;
        EventCallBack writeCallBack_;
        EventCallBack closeCallBack_;
        EventCallBack errorCallBack_;
};

Channel类析构函数源码如下:

Channel::~Channel()
{
  assert(!eventHandling_);  // handleEventWithGuard调用会到函数处理事件的时候会使用,保证没有正在被处理的回调操作
  assert(!addedToLoop_);    // addedToLoop_在update的时候会被置为true,remove的时候被置为false,assert保证channel要么没使用过,要么被remove

  // 由于当前channel属于某个eventloop,判断当前channel是否在当前事件循环loop_的线程析构
  if (loop_->isInLoopThread())
  {
    assert(!loop_->hasChannel(this)); // 判断当前channel是否属于当前的事件循环loop_
  }
}

在这里插入图片描述
在这里插入图片描述
由于muduo源码中的Channel析构函数没有任何释放资源的操作,我们也就不写了

Channel::~Channel(){}

得到Poller通知后,Channel需要调用,处理事件

handleEvent源码如下:

void Channel::handleEvent(Timestamp receiveTime)
{
  std::shared_ptr<void> guard;
  if (tied_)              // weak_ptr tie_用于监听当前channel
  {
    guard = tie_.lock();  // 把weak_ptr tie_通过lock提升成shared_ptr
    if (guard)            // 查看tie_观察的对象是否还存活(资源是否被释放)
    {
      handleEventWithGuard(receiveTime);  // 观察的对象还存活,就调用事件处理函数
    }
  }
  else
  {
    // 如果没有执行tie()方法,tied_就是false,则直接调用事件处理函数
    handleEventWithGuard(receiveTime);
  }
}

那tie方法什么时候执行?有什么作用呢?

tie方法会在建立新连接时在TcpConnection::connectEstablished中调用,传TcpConnection对象的shared_ptr给参数obj,Channel有一个weak_ptr对象指向Channel所属的TcpConnection对象

这样可以解决TcpConnection对象已经不存在了,但是Channel还注册在Poller,还能感知到Poller的通知并调用Channel的回调方法的问题

tie_是一个weak_ptr,Channel调用回调方法前都会调用handleEvent,这个方法会检查weak_ptr指向的对象是否失效(lock方法),如果失效了就不会调用相应方法了

void Channel::tie(const std::shared_ptr<void>& obj){
    //  weak_ptr tie_ 观察 shared_ptr obj
    tie_ = obj;
    tied_ = true;
}

// 得到poller通知以后,处理事件
void Channel::handleEvent(Timestamp receiveTime){
    if(tied_){
        std::shared_ptr<void> guard = tie_.lock();
        if(guard){
            handleEventWithGaurd(receiveTime);
        }
    }else{
        handleEventWithGaurd(receiveTime);
    }
}

Channel.cc

#include "Channel.h"
#include "EventLoop.h"
#include "Logger.h"

#include <sys/epoll.h>

const int Channel::kNoneEvent = 0;
const int Channel::kReadEvent = EPOLLIN | EPOLLPRI;
const int Channel::kWriteEvent = EPOLLOUT;

Channel::Channel(EventLoop* loop, int fd)
    : loop_(loop)
    , fd_(fd)
    , events_(0)
    , revents_(0)
    , index_(-1)
    ,tied_(false)
{}

Channel::~Channel(){}

void Channel::tie(const std::shared_ptr<void>& obj){
    //  weak_ptr tie_ 观察 shared_ptr obj
    tie_ = obj;
    tied_ = true;
}

/*
  向Poller(poll/epoll)更新fd感兴趣的事件,就是epoll_ctl
  EventLoop包含Channel、Poller,在Channel中的update方法需要更新epoll内核事件表中fd感兴趣的事件,
  然而epoll内核事件表需要由Poller操作,所以Channel告诉EventLoop,需要Poller更新epoll内核事件表
*/
void Channel::update(){
    // 通过Channel所属的EventLoop,调用Poller的方法,注册fd的events事件
    loop_->updateChannel(this);
}

void Channel::remove(){
    // 在当前channel所属的eventloop中删除当前channel
    loop_->removeChannel(this);
}

// 得到poller通知以后,处理事件
void Channel::handleEvent(Timestamp receiveTime){
    if(tied_){
        std::shared_ptr<void> guard = tie_.lock();
        if(guard){
            handleEventWithGaurd(receiveTime);
        }
    }else{
        handleEventWithGaurd(receiveTime);
    }
}

// 根据接收到的事件,执行对象的回调函数处理
void Channel::handleEventWithGaurd(Timestamp receiveTime){
    // LOG_INFO("in function : %s\n", __FUNCTION__);
    // LOG_INFO("line : %s\n", __LINE__);

    LOG_INFO("channel handleEvent revents: %d\n", revents_);

    if((revents_ & EPOLLHUP) && !(revents_ & EPOLLIN)){
        // 读写都关闭 && 没有读事件,表示出问题了
        if(closeCallBack_){
            // 回调函数对象不空,执行回调
            closeCallBack_(); 
        }
    }

    if(revents_ & EPOLLERR){
        if(errorCallBack_){
            errorCallBack_(); 
        }
    }

    if(revents_ & (EPOLLIN | EPOLLPRI | EPOLLRDHUP)){
        if(readCallBack_){
            readCallBack_(receiveTime); 
        }
    }

    if(revents_ & EPOLLOUT){
        if(writeCallBack_){
            writeCallBack_(); 
        }
    }
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bugcoder-9905

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值