Muduo分析及总结(二)Channel

Channel类

  • 每个Channel对象自始至终只属于一个EventLoop,因此每个Channel对象都只属于某一个IO线程。
  • 每个Channel对象自始至终只负责一个文件描述符(fd)的IO事件分发,但它并不拥有这个fd,也不会在析构的时候关闭这个fd。
  • Muduo用户一般不直接使用Channel,而会使用更上层的封装,如TcpConnection。
  • Channel的生命期由其owner calss负责管理。
  • Channel的成员函数都只能在IO线程调用,因此更新数据成员都不必加锁。

一、回调触发时序图

             

这里只展现了有事件产生时,如何调用的Channel回调函数的一个大致流程。

二、源码分析

由于Channel类相对来说比较简单,这里只分析下源码。

1、Channel.h

class Channel : boost::noncopyable
{
 public:
  typedef boost::function<void()> EventCallback;  //事件回调
  typedef boost::function<void(Timestamp)> ReadEventCallback; //读事件回调,带有事件参数

  Channel(EventLoop* loop, int fd);
  ~Channel();

  void handleEvent(Timestamp receiveTime);//当通道产生事件时,EventLoop首先调用的方法。
  void setReadCallback(const ReadEventCallback& cb) //设置读回调
  { readCallback_ = cb; }
  void setWriteCallback(const EventCallback& cb) //设置写回调
  { writeCallback_ = cb; }
  void setCloseCallback(const EventCallback& cb) //设置关闭回调
  { closeCallback_ = cb; }
  void setErrorCallback(const EventCallback& cb) //设置错误回调
  { errorCallback_ = cb; }


  //tie此方法是防止Channel类还在执行,上层调用导致 
  //Channel提前释放而出现的异常问题,下文会详细解释。 
  /// Tie this channel to the owner object managed by shared_ptr,
  /// prevent the owner object being destroyed in handleEvent.
  void tie(const boost::shared_ptr<void>&); 

  int fd() const { return fd_; } //Channel拥有的fd
  int events() const { return events_; } //Channel当前处理的事件类型
  void set_revents(int revt) { revents_ = revt; } // used by pollers
  // int revents() const { return revents_; }

  //检查当前Channel是否未处理任何事件
  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; }

  // for Poller
  int index() { return index_; } //每个Channel在Poller管理时都有一个index
  void set_index(int idx) { index_ = idx; }

  // for debug
  string reventsToString() const; //事件信息转换,便于调试

  void doNotLogHup() { logHup_ = false; } //是否输出POLLHUP挂起日志

  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_;
  int        events_;
  int        revents_;
  int        index_; // used by Poller.
  bool       logHup_;

  boost::weak_ptr<void> tie_;  //用于tie()方法
  bool tied_; //用于tie()方法
  bool eventHandling_; //表示当前是否正在处理事件
  ReadEventCallback readCallback_;
  EventCallback writeCallback_;
  EventCallback closeCallback_;
  EventCallback errorCallback_;
};

2、Channel.cc

//读写事件值初始化
const int Channel::kNoneEvent = 0;
const int Channel::kReadEvent = POLLIN | POLLPRI;
const int Channel::kWriteEvent = POLLOUT;

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

Channel::~Channel()
{
  //析构时,如果当前Channel
  //事件还在处理,则异常。
  assert(!eventHandling_);
}

void Channel::tie(const boost::shared_ptr<void>& obj)
{
  //捆绑Channel的拥有者,防止Channel还在使用时,
  //拥有者将Channel析构了。
  tie_ = obj;
  tied_ = true;
}

void Channel::update()
{
  //调用EventLoop更新此通道信息
  loop_->updateChannel(this);
}

void Channel::remove()
{
  //当前无任何事件的情况下,
  //移除Channel管理队列中本Channel。
  assert(isNoneEvent());
  loop_->removeChannel(this);
}

void Channel::handleEvent(Timestamp receiveTime)
{
  boost::shared_ptr<void> guard;
  if (tied_)
  {
    //捆绑了Channel的拥有者的处理方式。
    //tie_用的是boost::weak_ptr,所以
    //要先lock获取下,然后判断是否可用。
    guard = tie_.lock();
    if (guard)
    {
      //事件处理
      handleEventWithGuard(receiveTime);
    }
  }
  else
  {
    //事件处理
    handleEventWithGuard(receiveTime);
  }
}

void Channel::handleEventWithGuard(Timestamp receiveTime)
{

  //事件处理时,设置下此状态,
  //Channel析构时,用到此状态  
  eventHandling_ = true;
  if ((revents_ & POLLHUP) && !(revents_ & POLLIN))
  {
    //文件描述符挂起,并且不是读事件
    //POLLHUP 描述符挂起,比如管道的写端被关闭后,读端描述符将收到此事件
    if (logHup_)
    {
      LOG_WARN << "Channel::handle_event() POLLHUP";
    }
    if (closeCallback_) closeCallback_();
  }

  if (revents_ & POLLNVAL)
  {
    //指定的文件描述符非法,
    //输出日志便于errorCallback_区分错误。
    LOG_WARN << "Channel::handle_event() POLLNVAL";
  }

  if (revents_ & (POLLERR | POLLNVAL))
  {
    //POLLERR 指定的描述符发生错误。
    //POLLNVAL 指定的描述符非法(描述符未打开)。
    if (errorCallback_) errorCallback_();
  }
  if (revents_ & (POLLIN | POLLPRI | POLLRDHUP))
  {
    //POLLIN 普通数据可读(普通数据+优先级数据)
    //POLLPRI 紧急数据(优先级数据)
    //POLLRDHUP TCP连接被对方关闭,或者对方关闭了写操作,由GNU引入。
    if (readCallback_) readCallback_(receiveTime);
  }
  if (revents_ & POLLOUT)
  {
    //数据可写
    if (writeCallback_) writeCallback_();
  }

  eventHandling_ = false;
}

//此方法就是将对应的事件转换为字符串输出,便于调试。
string Channel::reventsToString() const
{
  std::ostringstream oss;
  oss << fd_ << ": ";
  if (revents_ & POLLIN)
    oss << "IN ";
  if (revents_ & POLLPRI)
    oss << "PRI ";
  if (revents_ & POLLOUT)
    oss << "OUT ";
  if (revents_ & POLLHUP)
    oss << "HUP ";
  if (revents_ & POLLRDHUP)
    oss << "RDHUP ";
  if (revents_ & POLLERR)
    oss << "ERR ";
  if (revents_ & POLLNVAL)
    oss << "NVAL ";

  return oss.str().c_str();
}

三、关键点

这里详细说下Channel::tie()方法。

1、作用

先看一个调用时序图。

                    

                                                                              图3-1

当对方断开TCP连接,这个IO事件会触发Channel::handleEvent()调用,后者会调用用户提供的CloseCallback,而用户代码在onClose()中有可能析构Channel对象,这就造成了灾难。等于说Channel::handleEvent()执行到一半的时候,其所属的Channel对象本身被销毁了。这时程序立刻core dump就是最好的结果了。

Muduo的解决办法是提供Channel::tie(const boost::shared_ptr<void>&)这个函数,用于延长某些对象(可以是Channel对象,也可以是其owner对象)的生命期,使之长过Channel::handleEvent()函数。

Muduo TcpConnection采用shared_ptr管理对象生命期的原因之一就是因为这个。

 


2、示例

这里举个TcpClient使用TcpConnection时,TcpConnection调用Channel::tie()的例子。

1)TcpClient设置自己的关闭回调:

void TcpClient::newConnection(int sockfd)
{
  loop_->assertInLoopThread();
  InetAddress peerAddr(sockets::getPeerAddr(sockfd));
  char buf[32];
  snprintf(buf, sizeof buf, ":%s#%d", peerAddr.toHostPort().c_str(), nextConnId_);
  ++nextConnId_;
  string connName = buf;

  InetAddress localAddr(sockets::getLocalAddr(sockfd));
  // FIXME poll with zero timeout to double confirm the new connection
  // FIXME use make_shared if necessary
  TcpConnectionPtr conn(new TcpConnection(loop_,
                                          connName,
                                          sockfd,
                                          localAddr,
                                          peerAddr));

  conn->setConnectionCallback(connectionCallback_);
  conn->setMessageCallback(messageCallback_);
  conn->setWriteCompleteCallback(writeCompleteCallback_);

  //设置关闭回调
  conn->setCloseCallback(
      boost::bind(&TcpClient::removeConnection, this, _1)); // FIXME: unsafe
  {
    MutexLockGuard lock(mutex_);
    connection_ = conn;
  }
  conn->connectEstablished();
}

void TcpClient::removeConnection(const TcpConnectionPtr& conn)
{
  loop_->assertInLoopThread();
  assert(loop_ == conn->getLoop());

  {
    MutexLockGuard lock(mutex_);
    assert(connection_ == conn);
    connection_.reset();
  }
  
  //回调函数中执行TcpConnection::connectDestroyed
  loop_->queueInLoop(boost::bind(&TcpConnection::connectDestroyed, conn));
  if (retry_ && connect_)
  {
    LOG_INFO << "TcpClient::connect[" << this << "] - Reconnecting to "
             << connector_->serverAddress().toHostPort();
    connector_->restart();
  }
}

2) TcpConnection将关闭回调设置到Channel

TcpConnection::TcpConnection(EventLoop* loop,
                             const string& nameArg,
                             int sockfd,
                             const InetAddress& localAddr,
                             const InetAddress& peerAddr)
  : loop_(CHECK_NOTNULL(loop)),
    name_(nameArg),
    state_(kConnecting),
    socket_(new Socket(sockfd)),
    channel_(new Channel(loop, sockfd)),
    localAddr_(localAddr),
    peerAddr_(peerAddr),
    highWaterMark_(64*1024*1024)
{
  channel_->setReadCallback(
      boost::bind(&TcpConnection::handleRead, this, _1));
  channel_->setWriteCallback(
      boost::bind(&TcpConnection::handleWrite, this));

  
  //设置关闭回调
  channel_->setCloseCallback(
      boost::bind(&TcpConnection::handleClose, this));
 

 channel_->setErrorCallback(
      boost::bind(&TcpConnection::handleError, this));
  LOG_DEBUG << "TcpConnection::ctor[" <<  name_ << "] at " << this
            << " fd=" << sockfd;
  socket_->setKeepAlive(true);
}

3) TcpConnection调用Channel::tie()

void TcpConnection::connectEstablished()
{
  loop_->assertInLoopThread();
  assert(state_ == kConnecting);
  setState(kConnected);

  //tie调用
  channel_->tie(shared_from_this());
  channel_->enableReading();

  connectionCallback_(shared_from_this());
}

4)Channel关闭事件回调

 当产生关闭事件时,Channel::handleEvent就好调用TcpConnection::handleClose()

void TcpConnection::handleClose()
{
  loop_->assertInLoopThread();
  LOG_TRACE << "TcpConnection::handleClose state = " << state_;
  assert(state_ == kConnected || state_ == kDisconnecting);
  // we don't close fd, leave it to dtor, so we can find leaks easily.
  setState(kDisconnected);
  channel_->disableAll();

  TcpConnectionPtr guardThis(shared_from_this());
  connectionCallback_(guardThis);

  //由上次调用TcpConnection设置的关闭回调
  // must be the last line
  closeCallback_(guardThis);
}

 

5)TcpConnection::connectDestroyed  

void TcpConnection::connectDestroyed()
{
  loop_->assertInLoopThread();
  if (state_ == kConnected)
  {
    setState(kDisconnected);
    channel_->disableAll();

    connectionCallback_(shared_from_this());
  }

  //此处会释放Channel
  channel_->remove();
}

当有关闭事件时,调用流程如下:

Channel::handleEvent -> TcpConnection::handleClose ->TcpClient::removeConnection ->TcpConnection::connectDestroyed

->channel_->remove()。

  • 1、为了在Channel::handleEvent处理期间,防止因其owner对象被修改,进而导致Channel被析构,最后出现不可预估错误。 Channel::tie()的作用就是将Channel的owner对象进行绑定保护起来。
  •  2、另外channel->remove的作用是删除channel在Poll中的地址拷贝,而不是销毁channel。channel的销毁由其owner对象决定。            
  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值