MUDUO-channel

Channel概述

在TCP ⽹络编程中,想要通过 IO 多路复⽤(epoll / poll)监听某个⽂件描述符,就需要把这个 fd 和该 fd 感兴趣 的事件通过 epoll_ctl 注册到 IO 多路复⽤模块上。当 IO 多路复⽤模块监听到该 fd 发⽣了某个事件。事件监听器返回发⽣事件的 fd 集合(有哪些 fd 发⽣了事件)以及每个 fd 的事件集合(每个 fd 具体发⽣了什么事件)。

Channel 类则封装了⼀个 fd 和这个 fd 感兴趣事件以及 IO 多路复⽤模块监听到的每个 fd 的事件集合。同时 Channel类还提供了设置该 fd 的感兴趣事件,以及将该fd及其感兴趣事件注册到事件监听器或从事件监听器上移 除,以及保存了该 fd 的每种事件对应的处理函数。

每个 Channel 对象只属于⼀个 EventLoop ,即只属于⼀个 IO 线程。只负责⼀个⽂件描述符(fd)的 IO 时间分发,但不拥有这个 fd。 Channel 把不同的 IO 事件分发为不同的回调,回调⽤ C++11 的特性 function 表示。声 明周期由拥有它的类负责。

Channel类其实相当于一个文件描述符的保姆!  在TCP网络编程中,想要IO多路复用监听某个文件描述符,就要把这个fd和该fd感兴趣的事件通过epoll_ctl注册到IO多路复用模块(我管它叫事件监听器)上。当事件监听器监听到该fd发生了某个事件。事件监听器返回 [发生事件的fd集合]以及[每个fd都发生了什么事件]

Channel类则封装了一个 [fd] 和这个 [fd感兴趣事件] 以及事件监听器监听到 [该fd实际发生的事件]。

Channel类还提供了设置该fd的感兴趣事件,以及将该fd及其感兴趣事件注册到事件监听器或从事件监听器上移除,以及保存了该fd的每种事件对应的处理函数。

Channel重要成员变量

     static const int kNoneEvent;    //无事件
     static const int kReadEvent;    //读事件
     static const int kWriteEvent;   //写事件
 ​
 ​
     EventLoop *loop_; // 事件循环使用的loop_
     const int fd_;    // fd, Poller监听的对象,即要往poller上注册的文件描述符
     int events_;      // 注册fd感兴趣的事件
     int revents_;     // poller返回的具体发生的事件
     int index_;
 ​
     // weak_ptr是⼀个观测者(不会增加或减少引⽤计数),同时也没有重载->,和*等运算符 所以不能直接使⽤
     // 可以通过lock函数得到它的shared_ptr(对象没销毁就返回,销毁了就返回空shared_ptr)
     std::weak_ptr<void> tie_;
     bool tied_;
 ​
     //  因为channel通道里面能够获知fd最终发送的具体的事件revents,所以它负责调用具体事件的回调操作
     ReadEventCallback readCallback_;    //读事件发生时的回调函数
     EventCallback writeCallback_;       //写事件发生时的回调函数
     EventCallback closeCallback_;       //关闭事件发生时的回调函数
     EventCallback errorCallback_;       //发生错误时的回调函数
  • kNoneEventkReadEventkWriteEvent:事件状态设置会使用的变量

  • EventLoop* loop_:这个 Channel 属于哪个EventLoop对象,因为 muduo 采用的是 one loop per thread 模型,所以我们有不止一个 EventLoop。我们的 manLoop 接收新连接,将新连接相关事件注册到线程池中的某一线程的 subLoop 上(轮询)。我们不希望跨线程的处理函数,所以每个 Channel 都需要记录是哪个 EventLoop 在处理自己的事情,这其中还涉及到了线程判断的问题。

  • int fd_:这个Channel对象照看的文件描述符

  • int events_:代表fd感兴趣的事件类型集合

  • int revents_:代表事件监听器实际监听到该fd发生的事件类型集合,当事件监听器监听到一个fd发生了什么事件,通过Channel::set_revents()函数来设置revents值。

  • read_callback_write_callback_close_callback_error_callback_:这些是 std::function 类型,代表着这个Channel为这个文件描述符保存的各事件类型发生时的处理函数。比如这个fd发生了可读事件,需要执行可读事件处理函数,这时候Channel类都替你保管好了这些可调用函数。到时候交给 EventLoop 执行即可。

  • index :我们使用 index 来记录 channel 与 Poller 相关的几种状态,Poller 类会判断当前 channel 的状态然后处理不同的事情。

    • kNew:是否还未被poll监视 -1

    • kAdded:是否已在被监视中 1

    • kDeleted:是否已被移除 2

Channel重要成员函数

     // fd得到poller通知以后,处理事件的。
     void handleEvent(Timestamp receiveTime);
 ​
     // 设置回调函数对象
     // 使用右值引用,延长了临时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_; }
     int set_revents(int revt) { revents_ = revt; }  //设置具体发生事件
 ​
 ​
     // 设置fd相应的事件状态,update()其本质调用epoll_ctl
     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; }

用于增加TcpConnection生命周期的tie方法(防止用户误删操作)

tie_绑定共享指针对象,当TcpConnection断开时,无法升级为智能指针,不执行回调函数

当tcpconnection对象在执行回调函数时,如果tcp连接断开了,回调函数的结果无法预料,所以此时使用weak_ptr.lock() 控制tcpconnection对象的生命周期,这时哪怕外面断开连接,内部的connection对象仍存活直至回调函数执行结束

用户使用muduo库的时候,会利用到TcpConnection。用户可以看见 TcpConnection,如果用户注册了要监视的事件和处理的回调函数,并在处理 subLoop 处理过程中「误删」了 TcpConnection 的话会发生什么呢?

总之,EventLoop 肯定不能很顺畅的运行下去。毕竟它的生命周期小于 TcpConnection。为了防止用户误删的情况,TcpConnection 在创建之初 TcpConnection::connectEstablished 会调用此函数来提升对象生命周期。

实现方案是在处理事件时,如果对被调用了tie()方法的Channel对象,我们让一个共享型智能指针指向它,在处理事件期间延长它的生命周期。哪怕外面「误删」了此对象,也会因为多出来的引用计数而避免销毁操作

 // weak_ptr是⼀个观测者(不会增加或减少引⽤计数),同时也没有重载->,和*等运算符 所以不能直接使⽤
 // 可以通过lock函数得到它的shared_ptr(对象没销毁就返回,销毁了就返回空shared_ptr)
 std::weak_ptr<void> tie_;
 bool tied_;
 ​
 // channel的tie方法什么时候调用过?一个TcpConnection新连接创建的时候 TcpConnection => Channel
 void Channel::tie(const std::shared_ptr<void> &obj)
 {
     tie_ = obj;
     tied_ = true;
 }
 ​
 // fd得到poller通知以后,处理事件的
 void Channel::handleEvent(Timestamp receiveTime)
 {
     if (tied_)
     {
         std::shared_ptr<void> guard = tie_.lock(); // TcpConnection断开时,无法升级为智能指针,不执行回调函数
         if (guard)                                 // 一个TcpConnection绑定一个Channel
         {
             handleEventWithGuard(receiveTime);
         }
     }
     else
     {
         handleEventWithGuard(receiveTime);
     }
 }
 ​
 // 连接建立
 void TcpConnection::connectEstablished()
 {
     setState(kConnected); // 建立连接,设置一开始状态为连接态
     /**
      * channel_->tie(shared_from_this());
      * tie相当于在底层有一个强引用指针记录着,防止析构
      * 为了防止TcpConnection这个资源被误删掉,而这个时候还有许多事件要处理
      * channel->tie 会进行一次判断,是否将弱引用指针变成强引用,变成得话就防止了计数为0而被析构得可能
      */
     channel_->tie(shared_from_this());
     channel_->enableReading(); // 向poller注册channel的EPOLLIN读事件
 ​
     // 新连接建立 执行回调
     connectionCallback_(shared_from_this());
 }

注意,传递的是 this 指针,所以是在 Channel 的内部增加对 TcpConnection 对象的引用计数(而不是 Channel 对象)。这里体现了 shared_ptr 的一处妙用,可以通过引用计数来控制变量的生命周期。巧妙地在内部增加一个引用计数,假设在外面误删,也不会因为引用计数为 0 而删除对象。

weak_ptr.lock() 会返回 shared_ptr(如果 weak_ptr 不为空)。

更新Channel关注的事件

update/remove

channel::update() => EvectLoop::updateChannel(channel) => EPollPoller::updateChannel() => EPollPoller::update()

channel::remove() => EvectLoop::removeChannel(channel) => EPollPoller::removeChannel() => EPollPoller::update()

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

根据相应事件执行Channel保存的回调函数

我们的Channel里面保存了许多回调函数,这些都是在对应的事件下被调用的。用户提前设置写好此事件的回调函数,并绑定到Channel的成员里。等到事件发生时,Channel自然的调用事件处理方法。借由回调操作实现了异步的操作。

 // 根据poller通知的channel发生的具体事件, 由channel负责调用具体的回调操作
 void Channel::handleEventWithGuard(Timestamp receiveTime)
 {
     // 对端关闭事件
     // LOG_INFO("channel handleEvent revents:%d\n", revents_);
     // 当TcpConnection对应Channel,通过shutdown关闭写端,epoll触发EPOLLHUP
     if ((revents_ & EPOLLHUP) && !(revents_ & EPOLLIN))
     {
         if (closeCallback_)
         {
             closeCallback_();
         }
     }
 ​
     // 错误事件
     if (revents_ & EPOLLERR)
     {
         LOG_ERROR << "the fd = " << this->fd();
         if (errorCallback_)
         {
             errorCallback_();
         }
     }
 ​
     // 读事件 且是高优先级读
     if (revents_ & (EPOLLIN | EPOLLPRI))
     {
         LOG_DEBUG << "channel have read events, the fd = " << this->fd();
         if (readCallback_)
         {
             LOG_DEBUG << "channel call the readCallback_(), the fd = " << this->fd();
             readCallback_(receiveTime);
         }
     }
 ​
     // 写事件
     if (revents_ & EPOLLOUT)
     {
         if (writeCallback_)
         {
             writeCallback_();
         }
     }
 }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值