【Muduo】三大核心之Channel

Muduo网络库的Channel模块是一个关键组件,它封装了文件描述符(fd)及其相关的事件,使得在TCP网络编程中能够利用IO多路复用技术(如epoll/poll)来监听和处理这些文件描述符上的事件。

封装内容

  • 文件描述符(fd):每个Channel对象都对应一个特定的文件描述符,该文件描述符通常代表一个网络连接。
  • 感兴趣的事件(events):Channel对象封装了与文件描述符相关的一组感兴趣的事件,如可读、可写等。
  • 实际发生的事件(revents):当Poller监听到文件描述符上发生某个事件时,会将该事件以及对应的文件描述符通知给Channel对象,Channel对象会保存这些实际发生的事件。
  • 回调函数(callbacks):Channel对象还保存了与每种事件对应的处理函数(回调函数),当相应的事件发生时,这些函数会被调用以处理事件。

主要成员变量

  • loop_:指向所属的事件循环(EventLoop)对象的指针。每个Channel对象都隶属于一个EventLoop,即一个IO线程。
  • 其他成员变量还包括文件描述符(fd)、感兴趣的事件(events)、实际发生的事件(revents)等。

主要功能

  • 设置感兴趣的事件:通过Channel对象的接口,可以设置文件描述符感兴趣的事件类型。
  • 注册/移除事件监听:可以将文件描述符及其感兴趣的事件注册到事件监听器(如epoll)上,也可以从事件监听器上移除已注册的文件描述符。
  • 事件回调处理:当IO多路复用模块监听到文件描述符上发生某个事件时,会通知相应的Channel对象,Channel对象会根据实际发生的事件调用相应的回调函数进行处理。

与Poller的交互

  • Channel对象通过EventLoop作为中介,与Poller(如EPollPoller)的IO多路复用模块进行交互。Poller负责将Channel对象中包含的文件描述符注册到epoll上,并监听这些文件描述符上的事件。
  • 当epoll监听到某个文件描述符上发生事件时,会返回对应的文件描述符集合以及每个文件描述符上发生的事件集合。Poller会根据这些信息找到对应的Channel对象,并调用相应的回调函数进行处理。

线程安全

  • 每个Channel对象只属于一个EventLoop,即只属于一个IO线程。这保证了Channel对象的线程安全性,避免了多线程访问导致的竞态条件和数据不一致问题。

综上所述,Muduo网络库的Channel模块是一个用于封装文件描述符及其相关事件、实现IO多路复用监听的关键组件。它提供了丰富的接口和功能来设置感兴趣的事件、注册/移除事件监听、处理事件回调等,为TCP网络编程提供了高效、灵活的事件驱动编程模型。以下是全部代码:

Channel.h

#pragma once
#include "noncopyable.h"
#include "Timestamp.h"
#include "LogStream.h"
#include <functional>
#include <memory>

// 在cpp文件夹中实际include,可以更少暴露引用文件细节
class EventLoop;

/*
理清楚 EventLoop、Channel、Poller之间的关系  《= Reactor模型上对应 Demultiplex
Channel 理解为通道,封装了socketfd和其他感兴趣的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通知以后,处理事件的 
    void handleEvent(Timestamp recieveTime);
    
    // 设置回调函数对象
    void setReadCallback(ReadEventCallback cb) { readCallback_ = std::move(cb); } // 使用move将形参的值转移给成员变量
    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_; }
    void set_revents(int revt) { revents_ = revt; } // used by pollers

    // 设置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; }

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

    // one loop per thread, one loop has a poller, one poller can listen many channel
    EventLoop* ownerLoop() { return loop_; }
    void remove(); 
private:
    void update();
    void handleEventWithGuard(Timestamp receiveTime);
    static const int kNoneEvent; // 没有事件
    static const int kReadEvent;
    static const int kWriteEvent;

    EventLoop *loop_; // 事件循环
    const int fd_;    // Poller监听的对象
    int events_;      // 注册fd感兴趣的事件
    int revents_;     // it's the received event types of epoll or poll // poller返回的具体发生的事件
    int index_;       // used by Poller.

    std::weak_ptr<void> tie_; // 多线程中监听对象的生存情况
    bool tied_;

    ReadEventCallback readCallback_;
    EventCallback writeCallback_;
    EventCallback closeCallback_;
    EventCallback errorCallback_;
};

Channel.cc

#include "Channel.h"
#include "EventLoop.h"
#include "LogStream.h"
#include <poll.h>

const int Channel::kNoneEvent = 0;
const int Channel::kReadEvent = POLLIN | POLLPRI; // pool.h
const int Channel::kWriteEvent = POLLOUT;

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

Channel::~Channel()
{
}

// 在一个TcpConnection新连接建立的时候调用
// TcpConnection 含有一个 Channel,在Channel中保留对其上层对象的指针,
// 防止TcpConnection被用户析构后,本类回调其回调函数
void Channel::tie(const std::shared_ptr<void> &obj)
{
    tie_ = obj; //
    tied_ = true;
    LOG_DEBUG << "Channel::tie() 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);
}

void Channel::handleEvent(Timestamp receiveTime)
{
    LOG_INFO << "channel handleEvent revents:" << revents_ << "  " << receiveTime.now().toString();
    if (tied_) // 上层TcpConnection对象还在
    {
        std::shared_ptr<void> guard = tie_.lock(); // lock 将弱智能指针 提升为 强智能指针
        if (guard)                                 // 是否存活
        {
            handleEventWithGuard(receiveTime);
        }
    }
    else
    {
        handleEventWithGuard(receiveTime);
    }
}

// 根据poller通知的Channel发生的具体事件,由Channel负责调用具体的回调操作
void Channel::handleEventWithGuard(Timestamp receiveTime)
{
    LOG_INFO << "channel handleEventWithGuard revents:" << revents_ << "  " << receiveTime.now().toString();
    if ((revents_ & POLLHUP) && !(revents_ & POLLIN))
    {
        if (closeCallback_)
            closeCallback_();
    }

    if (revents_ & POLLERR)
    {
        if (errorCallback_)
            errorCallback_();
    }
    if (revents_ & (POLLIN | POLLPRI))
    {
        if (readCallback_)
            readCallback_(receiveTime);
    }
    if (revents_ & POLLOUT)
    {
        if (writeCallback_)
            writeCallback_();
    }
}

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值