muduo源码剖析之channel通道类

本文涉及的产品
对象存储 OSS,20GB 3个月
对象存储 OSS,恶意文件检测 1000次 1年
对象存储 OSS,内容安全 1000次 1年
简介: channel是muduo中的事件分发器,它只属于一个EventLoop,Channel类中保存着IO事件的类型以及对应的回调函数,每个channel只负责一个文件描述符,但它并不拥有这个文件描述符。channel是在epoll和TcpConnection之间起沟通作用,故也叫做通道,其它类通过调用channel的setCallbcak来和建立channel沟通关系。

简介

channel是muduo中的事件分发器,它只属于一个EventLoop,Channel类中保存着IO事件的类型以及对应的回调函数,每个channel只负责一个文件描述符,但它并不拥有这个文件描述符。channel是在epoll和TcpConnection之间起沟通作用,故也叫做通道,其它类通过调用channel的setCallbcak来和建立channel沟通关系。

Channel类主要作用:

  1. 将文件描述符(可能是socket类型,eventfd类型,timefd类型,signalfd类型)封装,通过该类设置各种事件的回调函数(例如读回调,写回调,关闭回调等)。
  2. 可以设置自己的监听事件类型,然后根据该类型更新poller对象(epoll或者poll)对该类的操作(例如添加,修改,删除操作)。
  3. 根据自己监听到的事件类型触发回调函数。

源码剖析

channel.h

///
///一个能被选择的 I/O channel
//这个类不拥有自己的文件描述符
/// 这个文件描述符可能是socket,eventfd, timerfd,或者 signalfd.

class Channel : noncopyable
{
   
 public:
  typedef std::function<void()> EventCallback;    //事件回调
  typedef std::function<void(Timestamp)> ReadEventCallback;    //读事件回调

  Channel(EventLoop* loop, int fd);    //一个channel属于一个loop,一个loop可以对应多个channel
  ~Channel();

  void handleEvent(Timestamp receiveTime);    //处理事件
  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与shared_ptr管理的所有者对象绑定,
  ///阻止shared_ptr管理的所有者对象在handleEvent被销毁
  void tie(const std::shared_ptr<void>&);

  int fd() const {
    return fd_; }
  int events() const {
    return events_; }    //返回注册的事件
  void set_revents(int revt) {
    revents_ = revt; } // 设置监听到的事件类型
  // int revents() const { return revents_; }
  bool isNoneEvent() const {
    return events_ == kNoneEvent; }

  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(); }    //不监听任何事件
  bool isWriting() const {
    return events_ & kWriteEvent; }    //是否监听写事件
  bool isReading() const {
    return events_ & kReadEvent; }    //是否监听读事件

  // for Poller
  int index() {
    return index_; }    //poller事件数组中的下标
  void set_index(int idx) {
    index_ = idx; }    

  // for debug
  string reventsToString() const;
  string eventsToString() const;

  void doNotLogHup() {
    logHup_ = false; }

  EventLoop* ownerLoop() {
    return loop_; }    //返回所属的loop
  void remove();    //将channel从loop中移除

 private:
  static string eventsToString(int fd, int ev);

  void update();    //根据index_的状态,控制监听数组
  void handleEventWithGuard(Timestamp receiveTime);    //对监听到事件的一个处理

  static const int kNoneEvent;  //无事件
  static const int kReadEvent;  //可读事件
  static const int kWriteEvent; //可写事件

  EventLoop* loop_;
  const int  fd_;
  int        events_;    //监听的事件类型
  int        revents_;     //监听到的事件类型 
  int        index_;     // 表示poller事件数组中的序号
  bool       logHup_;    

  std::weak_ptr<void> tie_;
  bool tied_;    
  bool eventHandling_;    //是否处于处理事件中
  bool addedToLoop_;    //是否添加到loop上
  ReadEventCallback readCallback_;    //读回调
  EventCallback writeCallback_;    //写回调
  EventCallback closeCallback_;    //关闭回调
  EventCallback errorCallback_;    //错误回调
};

channel.cc

// Copyright 2010, Shuo Chen.  All rights reserved.
// http://code.google.com/p/muduo/
//
// Use of this source code is governed by a BSD-style license
// that can be found in the License file.

// Author: Shuo Chen (chenshuo at chenshuo dot com)

#include "muduo/base/Logging.h"
#include "muduo/net/Channel.h"
#include "muduo/net/EventLoop.h"

#include <sstream>

#include <poll.h>

using namespace muduo;
using namespace muduo::net;

const int Channel::kNoneEvent = 0;
const int Channel::kReadEvent = POLLIN | POLLPRI;
const int Channel::kWriteEvent = POLLOUT;

//Channel(事件分发器),只属于一个EventLoop,Channel类中保存着IO事件的类型以及对应的回调函数,每个channel只负责一个文件描述符
Channel::Channel(EventLoop* loop, int fd__)
  : loop_(loop),        //channel所属的loop,一个channel只属于一个loop
    fd_(fd__),          //channel负责的文件描述符
    events_(0),         //注册的事件
    revents_(0),        //poller设置的就绪的事件
    index_(-1),         //被poller使用的下标
    logHup_(true),      //是否生成某些日志
    tied_(false),   用于tie()方法
    eventHandling_(false),          //处理handevent的标志
    addedToLoop_(false)        //是否正在循环监听中
{
   
}

Channel::~Channel()
{
   
  assert(!eventHandling_);
  assert(!addedToLoop_);
  if (loop_->isInLoopThread())//one loop per thread,判断event_loop所在的线程和正在销毁该对象的线程是不是同一个线程
  {
   
    assert(!loop_->hasChannel(this));//该对象此时不属于loop_并且poller中也没有该记录才能销毁成功
  }
}

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

void Channel::update()   //根据index_的状态,控制该channel的监听状态
{
   
  addedToLoop_ = true;
  loop_->updateChannel(this);//该函数最底层实际是由Poller->updateChannel(this)实现的
}

void Channel::remove()  //同上
{
   
  assert(isNoneEvent());
  addedToLoop_ = false;
  loop_->removeChannel(this);//将该对象从Poller监听的数组对象中移除出去
}

//处理所有发生的事件,如果活着,底层调用handleEventWithGuard
void Channel::handleEvent(Timestamp receiveTime) //事件到来调用handleEvent处理
{
   
  std::shared_ptr<void> guard;       //守护
  if (tied_)
  {
   
    guard = tie_.lock();
    if (guard)
    {
   
      handleEventWithGuard(receiveTime);//实际上处理的函数
    }
  }
  else
  {
   
    handleEventWithGuard(receiveTime);
  }
}

//处理所有发生的事件
//EPOLLIN :表示对应的文件描述符可以读; 
//EPOLLOUT:表示对应的文件描述符可以写; 
//EPOLLPRI:表示对应的文件描述符有紧急的数据可读 
//EPOLLERR:表示对应的文件描述符发生错误; 
//EPOLLHUP:表示对应的文件描述符被挂断; 
//EPOLLET:表示对应的文件描述符有事件发生; 
void Channel::handleEventWithGuard(Timestamp receiveTime)
{
                       
  eventHandling_ = true;            
  LOG_TRACE << reventsToString();
  if ((revents_ & POLLHUP) && !(revents_ & POLLIN))  //判断返回事件类型
  {
   
    if (logHup_)
    {
   
      LOG_WARN << "fd = " << fd_ << " Channel::handle_event() POLLHUP";
    }
    if (closeCallback_) closeCallback_();
  }

  if (revents_ & POLLNVAL)                 //不合法文件描述符
  {
   
    LOG_WARN << "fd = " << fd_ << " Channel::handle_event() POLLNVAL";
  }

  if (revents_ & (POLLERR | POLLNVAL))
  {
   
    if (errorCallback_) errorCallback_();
  }
  if (revents_ & (POLLIN | POLLPRI | POLLRDHUP))   //POLLRDHUP是对端关闭连接事件,如shutdown等
  {
   
    if (readCallback_) readCallback_(receiveTime);
  }
  if (revents_ & POLLOUT)
  {
   
    if (writeCallback_) writeCallback_();
  }
  //事件处理完成,将事件处理标志职位false
  eventHandling_ = false;
}

//将revents转为string,方便输出调试
string Channel::reventsToString() const
{
   
  return eventsToString(fd_, revents_);
}

//将events转为string,方便输出调试
string Channel::eventsToString() const
{
   
  return eventsToString(fd_, events_);
}

//实际上events和revents转为string类型的实现
string Channel::eventsToString(int fd, int ev)
{
   
  std::ostringstream oss;
  oss << fd << ": ";
  if (ev & POLLIN)
    oss << "IN ";
  if (ev & POLLPRI)
    oss << "PRI ";
  if (ev & POLLOUT)
    oss << "OUT ";
  if (ev & POLLHUP)
    oss << "HUP ";
  if (ev & POLLRDHUP)
    oss << "RDHUP ";
  if (ev & POLLERR)
    oss << "ERR ";
  if (ev & POLLNVAL)
    oss << "NVAL ";

  return oss.str();
}
相关实践学习
借助OSS搭建在线教育视频课程分享网站
本教程介绍如何基于云服务器ECS和对象存储OSS,搭建一个在线教育视频课程分享网站。
目录
相关文章
|
7月前
muduo源码剖析之Acceptor监听类
Acceptor类用于创建套接字,设置套接字选项,调用socket()->bind()->listen()->accept()函数,接受连接,然后调用TcpServer设置的connect事件的回调。listen()//在TcpServer::start中调用封装了一个listen fd相关的操作,用于mainLoop接受器封装,实质上就是对Channel的多一层封装监听连接 当新连接进入时,调用Socket::accept创建套接字,触发TcpServer的回调TcpServer通过该接口设置回调,
54 0
|
7月前
muduo源码剖析之Socket类
封装了一个sockfd相关的设置。比较简单,已经编写注释。
58 0
|
6月前
|
调度 PHP
Swoole 源码分析之 Channel 通道模块
通道,用于协程间通讯,支持多生产者协程和多消费者协程。底层自动实现了协程的切换和调度。通道与 PHP 的 Array 类似,仅占用内存,没有其他额外的资源申请,所有操作均为内存操作,无 IO 消耗。
114 5
Swoole 源码分析之 Channel 通道模块
|
7月前
|
网络协议
Muduo类详解之Channel
Muduo类详解之Channel
|
7月前
Muduo类详解之EventLoop
Muduo类详解之EventLoop
|
7月前
|
网络协议
muduo源码剖析之TcpClient客户端类
muduo用TcpClient发起连接,TcpClient有一个Connector连接器,TCPClient使用Conneccor发起连接, 连接建立成功后, 用socket创建TcpConnection来管理连接, 每个TcpClient class只管理一个TcpConnecction,连接建立成功后设置相应的回调函数。很显然,TcpClient用来管理客户端连接,真正连接交给Connector。
89 0
muduo源码剖析之TcpClient客户端类
|
7月前
|
缓存 Java API
【Netty 网络通信】Channel 接口解析
【1月更文挑战第9天】【Netty 网络通信】Channel 接口解析
|
网络协议 前端开发 UED
Netty之服务端channel的初始化
Netty之服务端channel的初始化
116 0
|
存储 数据可视化 安全
彻底搞懂channel原理(一)
彻底搞懂channel原理(一)
194 0
彻底搞懂channel原理(一)
|
消息中间件 存储 Go
彻底搞懂channel原理(三)
彻底搞懂channel原理(三)
235 0
彻底搞懂channel原理(三)