当前位置 博文首页 > wanggao的专栏:muduo学习笔记:net部分之Channel

    wanggao的专栏:muduo学习笔记:net部分之Channel

    作者:[db:作者] 时间:2021-09-09 09:47

    正如其名,muduo中Channel是底层通过Socket建立TCP通信的通道,用于事件分发。Channel对fd的事件相关方法进行了封装,例如负责注册fd的可读或可写事件到EvenLoop,又如fd产生事件后要如何响应。

    Channel与fd是聚合关系,一个fd对应一个channel,不实际拥有fd,Channel析构是不会close当前fd。文件描述符fd可以是socketfdeventfdtimerfdsignalfd

    Channel一般作为其他类的成员,不被直接使用,而使用更上层的封装,例如用于服务端的Acceptor,用于客户端的Connector,用于一次客户端服务端通信的TcpConnection最重要的是,EventLoop通过一个vector<Channel*> 数组对注册到其内的众多fd的管理,在事件循环中能够接收并处理不同类型fd上的事件

    Channel的生命期由其owner calss负责管理,且成员函数都只能在IO线程调用,因此更新数据成员都不必加锁。

    1、Channel定义

    在外部使用Channel初始化时,需要指定所属的EvenetLoop,以及外部的fd文件描述符。

    fd上的事件实际是外部触发,例如,服务端Acceptor的监听有新的客户端连接accept(2)、客户端断开调用close(2);客户端Connector的主动发起连接connect(2)、断开连接close(2)等。Channel监测到fd上的事件发生后,调用对应的注册回调函数。

    ///
    /// A selectable I/O channel.
    ///
    /// This class doesn't own the file descriptor.
    /// The file descriptor could be a socket,
    /// an eventfd, a timerfd, or a signalfd
    class Channel : noncopyable
    {
     public:
      typedef std::function<void()> EventCallback;   // 事件回调函数定义
      typedef std::function<void(Timestamp)> ReadEventCallback; // 读事件回调函数定义
    
      Channel(EventLoop* loop, int fd);
      ~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); }
    
      /// Tie this channel to the owner object managed by shared_ptr,
      /// prevent the owner object being destroyed in 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; }  // 设置发生的事件:poller中调用
      // 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_; }
      void set_index(int idx) { index_ = idx; }
    
      // for debug
      string reventsToString() const;
      string eventsToString() const;
    
      void doNotLogHup() { logHup_ = false; }
    
      EventLoop* ownerLoop() { return loop_; }
      void remove();
    
     private:
      static string eventsToString(int fd, int ev);
    
      void update();    // 注册事件后更新到EventLoop
      void handleEventWithGuard(Timestamp receiveTime);  // 加锁的事件处理
    
      static const int kNoneEvent;
      static const int kReadEvent;
      static const int kWriteEvent;
    
      EventLoop* loop_;     //channel所属的EventLoop
      const int  fd_;       //channel负责的文件描述符,但不负责关闭该文件描述符
      int        events_;   //注册的事件
      int        revents_;  // poller返回接收到的就绪的事件
      int        index_;    //表示在poll的事件数组中的序号
      bool       logHup_;
    
      std::weak_ptr<void> tie_;
      bool tied_;
      
      bool eventHandling_;                // 是否处于处理事件中
      bool addedToLoop_;
      ReadEventCallback readCallback_;  // 读事件回调 
      EventCallback writeCallback_;     // 写事件回调 
      EventCallback closeCallback_;     // 关闭事件回调 
      EventCallback errorCallback_;     // 出错事件回调 
    };
    

    2、Channel实现

    着重说明其中几个函数。

    2.1、update()和remove()

    一旦Channel被创建且注册了读或写事件,需通过EventLoop将对应的fd事件监听交由Poller管理。Channel中更新、关闭fd上注册的事件也需要进行相应EventLoop中的处理。

    • update():把当前的channel加入到poll队列当中,或者更新fd的监听事件
    • remove():先关闭fd上注册的事件,并从其所属的EventLoop的Poller中移除当前channel
    void Channel::update()
    {
      addedToLoop_ = true;
      loop_->updateChannel(this);
    }
    
    void Channel::remove()
    {
      assert(isNoneEvent());
      addedToLoop_ = false;
      loop_->removeChannel(this);
    }
    

    2.2、handleEvent和handleEventWithGuard

    是Channel的核心,事件分发。由EventLoop::loop()调用,根据revents_的值分别调用不同的用户回调函数。

    void Channel::handleEvent(Timestamp receiveTime) //Timestamp用于读事件的回调函数的参数
    { 
      std::shared_ptr<void> guard;
      if (tied_){
        guard = tie_.lock();
        if (guard) {  handleEventWithGuard(receiveTime); }
      }
      else{  handleEventWithGuard(receiveTime);  }
    }
    
    // 查看epoll/或者poll返回的具体是什么事件,并根据事件的类型进行相应的处理
    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)){ //关于读的事件 
        if (readCallback_) readCallback_(receiveTime);
      }
      if (revents_ & POLLOUT){   //关于写的事件
        if (writeCallback_) writeCallback_();
      }
      eventHandling_ = false;
    }
    

    2.3、tie(const std::shared_ptr&)

    当TCP连接断开,这个IO事件会触发Channel::handleEvent()函数,而后者回调用户提供的CloseCallbask,用户代码在onClose()中可能可能析构Channel对象。这样就会引起Channel::handleEvent()执行到一半时,其所属的Channel被销毁了。

    为了避免core dump,需要延长某些对象的生命期,使其寿命长过Channel::handleEvent()函数。这也是muduo库TcpConnetion采用shared_ptr管理对象生命周期的原因。在TcpConnetion中建立连接后,调用Chanel::tie(TcpConnection::shared_from_this())来延长生命周期。

    boost::weak_ptr<void> tie_;//保证channel所在的类生命周期延长
    bool tied_;
    
    void Channel::tie(const std::shared_ptr<void>& obj)
    {
      tie_ = obj;
      tied_ = true;
    }
    
    void Channel::handleEvent(Timestamp receiveTime)
    { 
      std::shared_ptr<void> guard;
      if (tied_){ // 提升tie_为shared_ptr,如果提升成功,说明指向一个存在的对象
        guard = tie_.lock();
        if (guard) {  handleEventWithGuard(receiveTime); }
      }
      else{  handleEventWithGuard