手写C++Muduo库(Channel)

139 阅读6分钟

image.png

TcpServer相当于是muduo库提供给外部编写服务器程序的入口的一个类,相当于一个大箱子,把muduo库有关服务器的编程相关的东西,包括反应堆,事件分发器,事件回调都打包一块了。
我们看看它的成员变量。

image.png

事件分发器相当于epoll,在多核的CPU时代,不可能只是一个线程,所以肯定有一个事件分发的线程池EventLoopThreadPool;ConnectionMap维护了所有连接。
我们先从EventLoop往下看 image.png

eventloop事件循环相当于是一个epoll,epoll里面主要管理什么呢?查看它的成员变量

image.png

poller相当于epoll的抽象概念,用poller当做一个基类,在派生类里面实现了poll和epoll这些io复用的具体的事件分发操作。epoll里面还得需要监听socket,eventloop里另外一个重要的成员变量channel

image.png

事件分发器在这里面最重要的两个东西:poller和channel

我们看一下channel是什么

image.png

image.png

revents是具体的发生的事件
channel我们理解成通道,相当于就是fd和它所绑定的感兴趣的事件和实际上发生事件epoll_wait返回以后在fd上发生的事件(绑定)。

eventloop包含channel eventloop是事件循环,相当于事件分发器一样,包含了epoll,也就是poller,另外就是epoll感兴趣的fd,以及它对应的事件和最终发生的事件被muduo库封装成channel 这些事件最终要向poller注册,发生的事件由poller给channel通知,channel得到相应fd的事件通知调用预置的回调操作。 一个线程有一个eventloop,一个eventloop有一个poller,一个poller可以监听多个channel,每个channel属于1个eventloop,1个eventloop有很多channel,这就是多路。

重写Channel
Channel.h

#pragma once

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

#include<functional>
#include<memory>

class EventLoop;

/*
理清楚EventLoop、channel和Poller之间的关系,他们在reactor模型上对应Demultiplex多路事件分发器
channel理解为通道,封装了sockfd和其感兴趣的event,如EPOLLIN、EPOLLOUT事件
还绑定了poller返回的具体事件
*/
class Channel : noncopyable
{
public:
    using EventCallback = std::function<void()>;    //事件回调
    using ReadEventCallback = std::function<void(Timestamp)>;   //只读事件回调
    
    Channel(EventLoop *loop,int fd);
    ~Channel();

    //fd得到poller通知以后,处理事件的
    //调用相应的回调方法来处理事件 
    /*构造函数中的EventLoop只是前置声明就可以了,因为这里只用了类型定义的指针,不管是什么类型指针都是四个字节,不影响编译;
    但是TimeStamp这里是定义的变量,需要确定变量的大小,所以需要包含它的头文件
    */
   //fd得到poller通知以后,调用相应的回调方法处理事件
    void handleEvent(Timestamp receiveTime);

    //设置回调函数对象
    //因为cb和这个成员变量都属于左值,有内存和变量名字,因为function是一个对象,对象我们假设它是很大的,占用很多资源,在这里面调用的就是函数对象的赋值操作,
    //所以要把cb转成一个右值,把cb的资源转给成员变量,因为出了这个函数,cb形参的局部对象就不需要了,
    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 setErrorCallBack(EventCallback cb) { errorCallback_ = std::move(cb); }

    //防止当channel被手动remove掉,channel还在执行回调操作
    void tie(const std::shared_ptr<void>&);

    int fd() const { return fd_; }
    int events() const { return events_; } //fd所感兴趣的事件
    void set_events(int revt) { revents_ = revt; } //poller监听事件,设置了channel的fd相应事件 

    //设置fd相应的事件状态,要让fd对这个事件感兴趣 
    //update就是调用epoll_ctrl,通知poller把fd感兴趣的事件添加到fd上
    void enableReading() { events_ |= kReadEvent; update(); }
    void disableReading() { events_ &= ~ kReadEvent; update(); }
    void enableWriting() { events_ |= kWriteEvent; update(); }
    void disableWriting() { events_ &= ~kWriteEvent; update(); }
    void disableAll() {events_ = kNoneEvent; update(); }
    

    //返回fd当前的事件状态
    bool isNoneEvent() const { return events_ == kNoneEvent; }
    bool isWriting() const { return events_ & kWriteEvent; }
    bool isReading() const {return events_ & kReadEvent; }

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

    //one loop per thread
    EventLoop* ownerLoop() { return loop_; }//当前channel属于哪个eventloop 
    void remove();//删除channel

private:

     void update();//更新,内部对象调用 
     void handleEventWithGuard(Timestamp receiveTime);//受保护的处理事件

    //表示当前fd和其状态,是没有对任何事件感兴趣,还是对读或者写感兴趣 
    static const int kNoneEvent;    //都不感兴趣
    static const int kReadEvent;    //读事件
    static const int kWriteEvent;   //写事件

    EventLoop *loop_;   //事件循环
    const int fd_;  //fd,poller监听的对象
    int events_;    //注册fd感兴趣的事件
    int revents_;   //poller返回的具体发生的事件
    int index_;

    /*
    防止手动调用removeChannel,Channel被手动remove以后我们还在使用Channel,
    所以做了跨线程的对象的生存状态的监听,使用的时候可以把弱智能指针提升成强智能指针,
    提升成功访问,提升失败说明已经被释放。
    */
    std::weak_ptr<void> tie_;   //绑定自己
    bool tied_;

    //因为channel通道里面能够获知fd最终发生的具体的事件revents,所以它负责调用具体事件的回调操作
    //这些回调是用户设定的,通过接口传给channel来负责调用 ,channel才知道fd上是什么事件 
    ReadEventCallback readCallback_;
    EventCallback writeCallback_;
    EventCallback closeCallback_;
    EventCallback errorCallback_;
};

image.png

析构函数的if:如果这个channel是在当前的事件循环所在的线程里面

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; //epoll的表示 
const int Channel::kWriteEvent = EPOLLOUT;   //epoll的表示 

//EventLoop底层: ChannelList  Poller 每个channel属于1个loop 
Channel::Channel(EventLoop *loop,int fd)
    :loop_(loop), fd_(fd), events_(0), revents_(0), index_(-1), tied_(false)
{}

//析构函数
//保证没有正在处理的回调操作
//保证channel要么是从来没使用过,要么是被remove掉后才能析构
//判断当前的loop是否在它所在的线程去析构的channel
Channel::~Channel()
{}

//channel的tie方法什么时候调用过?一个TcpConnection新连接创建的时候 TcpConnection => Channel
void Channel::tie(const std::shared_ptr<void> &obj){
    tie_ = obj;
    tied_ = true;
}

/*
当改变channel所表示fd的events事件后,update负责在poller里面更改fd相应的事件epoll_ctl
 EventLoop => ChannelList   Poller
*/
void Channel::update(){
    //通过channel所属的EventLoop,调用poller的相应方法,注册fd的events事件
    loop_->updateChannel(this);
}

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

//fd得到poller通知以后,调用相应的回调方法处理事件
void Channel::handleEvent(Timestamp receiveTime){
    if(tied_){ //绑定过 
        std::shared_ptr<void> guard = tie_.lock(); //弱智能指针-》强智能指针 
        if(guard){ //是否存活 
            handleEventWithGuard(receiveTime);
        }
    }
    else{
        handleEventWithGuard(receiveTime);
    }
}

//根据poller通知的channel发生的具体事件, 由channel负责调用具体的回调操作
void Channel::handleEventWithGuard(Timestamp receiveTime){
    LOG_INFO("channel handleEvent revent:%d\n",revents_);

    if((revents_ & EPOLLHUP) && !(revents_ & EPOLLIN)){
        if(closeCallback_){
            closeCallback_();
        }
    }

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

    if(revents_ & (EPOLLIN|EPOLLPRI)){
        if(readCallback_){
            readCallback_(receiveTime);
        }
    }
    
    if(revents_ & EPOLLOUT){
        if(writeCallback_){
            writeCallback_();
        }
    }
}