muduo网络库学习笔记(13):TcpConnection生命期的管理

本篇通过分析muduo中TcpConnection对断开连接事件的处理,来学习muduo网络库对TcpConnection生命期的管理。

TcpConnection对连接断开事件的处理

首先,我们来看一下TcpConnection处理连接断开事件时函数调用的流程:
这里写图片描述
我们这里所指的连接断开,都是指被动关闭,即对方先关闭连接,本地read(2)返回0,触发关闭逻辑。

分析:一个服务器(TcpServer)维护了一个连接列表,当一个连接断开时,TcpConnection中的通道处于活跃的状态,EventLoop的事件循环返回了这个活跃的通道,然后调用通道的handleEvent()函数来处理。连接关闭是可读事件,进而回调了TcpConnection的handleRead()函数,handleRead()中又调用了read()返回为0,判断read()返回为0又会调用handleClose()函数。handleClose()函数会回调TcpServer的removeConnection()函数,其中会调用erase()将该连接从连接列表移除。

这里我们需要注意的是——一般情况下,将连接从连接列表移除后,我们就可以将这个连接对象销毁(delete)掉了,但是在这里我们不能立即销毁这个连接对象,原因如下:

如果我们销毁了这个对象,TcpConnection所包含的Channel对象也就跟着被销毁了,而我们当前正在调用Channel对象的handleEvent()函数,就会出现core dump。所以,我们必须保证TcpConnection的生存期长于Channel::handleEvent()函数。

muduo选择用智能指针shared_ptr来管理TcpConnection的生命期,并且让TcpConnection类继承自boost::enable_shared_from_this。

源码分析

具体代码改动如下:

代码片段1:Channel的改动
文件名:Channel.cc

// 析构函数中会判断Channel是否仍处于事件处理状态
// 在事件处理期间Channel对象不会析构
Channel::~Channel()
{
  assert(!eventHandling_);
}

// 开始处理事件之前,会将事件处理标志位置为true
// 直到事件处理完,事件处理标志位再置为false
void Channel::handleEventWithGuard(Timestamp receiveTime)
{
  eventHandling_ = true;
  if ((revents_ & POLLHUP) && !(revents_ & POLLIN))
  {
    if (logHup_)
    {
      LOG_WARN << "Channel::handle_event() POLLHUP";
    }
    if (closeCallback_) closeCallback_();
  }
  if (revents_ & POLLNVAL)
  {
    LOG_WARN << "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:TcpConnection::handleRead()函数的改动
文件名:TcpConnection.cc

void TcpConnection::handleRead(Timestamp receiveTime)
{
  loop_->assertInLoopThread();
  int savedErrno = 0;
  char buf[65536];
  ssize_t n = ::read(channel_->fd(), buf, sizeof buf);
  // 根据read(2)的返回值分别调用messageCallback_()、handleClose()和handleError()
  if (n > 0)
  {
    messageCallback_(shared_from_this(), buf, n);
  }
  else if (n == 0)
  {
    handleClose();
  }
  else
  {
    errno = savedErrno;
    LOG_SYSERR << "TcpConnection::handleRead";
    handleError();
  }  
}
代码片段3:TcpConnection::handleClose()函数
文件名:TcpConnection.cc

void TcpConnection::handleClose()
{
  loop_->assertInLoopThread();
  LOG_TRACE << "fd = " << channel_->fd() << " state = " << state_;
  // 断定此时连接处于已连接状态
  assert(state_ == kConnected);
  // we don't close fd, leave it to dtor, so we can find leaks easily.
  channel_->disableAll();

  // must be the last line
  // 调用所设置的连接关闭回调函数
  closeCallback_(shared_from_this()); 
}
代码片段4:TcpServer::removeConnection()函数
文件名:TcpServer.cc

void TcpServer::removeConnection(const TcpConnectionPtr& conn)
{
  loop_->assertInLoopThread();
  LOG_INFO << "TcpServer::removeConnectionInLoop [" << name_
           << "] - connection " << conn->name();
  // 将断开的连接从连接列表中移除
  size_t n = connections_.erase(conn->name());
  (void)n;
  assert(n == 1);
  // 此处一定要用EventLoop::queueInLoop(),避免Channel对象被提前销毁
  // 这里用boost::bind让TcpConnection的生命期长到调用connectDestroyed()的时刻
  // 使用boost::bind得到一个boost::function对象,会把conn传递进去,引用计数会加1
  loop_->queueInLoop(
      boost::bind(&TcpConnection::connectDestroyed, conn));
}

Channel::handleEvent()事件处理完后就会调用functors(见博客“muduo网络库学习笔记(11):有用的runInLoop()函数”)即调用TcpConnection::connectDestroyed()。

代码片段5:TcpConnection::connectDestroyed()函数
文件名:TcpConnection.cc

// connectDestroyed()是TcpConnection析构前最后调用的一个成员函数
// 它通知用户连接已断开
void TcpConnection::connectDestroyed()
{
  loop_->assertInLoopThread();
  assert(state_ == kConnected);
  setState(kDisconnected);
  channel_->disableAll();
  connectionCallback_(shared_from_this());  // 回调用户设定的回调函数
  loop_->removeChannel(get_pointer(channel_));  // 从poll/epoll中移除channel
}

boost::enable_shared_from_this

由于TcpConnection模糊的生命期,我们用到了shared_ptr来管理它的生命期,并让TcpConnection类继承自boost::enable_shared_from_this。那么,boost::enable_shared_from_this的作用是什么呢?

使用示例:

#include <boost/enable_shared_from_this.hpp>
#include <boost/shared_ptr.hpp>
#include <cassert>

// class Y继承自boost::enable_shared_from_this<Y>
class Y: public boost::enable_shared_from_this<Y>
{
public:
    boost::shared_ptr<Y> f()
    {
        return shared_from_this();  // 返回指向自身的shared_ptr
    }

    Y* f2()
    {
        return this; 
    }
};

int main()
{
    boost::shared_ptr<Y> p(new Y);  // p的引用计数为1
    boost::shared_ptr<Y> q = p->f();  // 将当前对象转换为一个shared_ptr,赋值给q,此时p/q引用计数为2

    Y* r = p->f2(); 
    assert(p == q);  // 断言正确
    assert(p.get() == r);  // 断言正确

    std::cout << p.use_count() << std::endl;  // 输出:2

    // 构造一个shared_ptr对象s,将r赋给s
    // 打印出的s的引用计数应该为1,而不是3
    // 因为此时构造了一个新的、独立的shared_ptr对象,而不是将一个shared_ptr对象赋值给另一个shared_ptr对象
    boost::shared_ptr<Y> s(r); 
    std::cout << s.use_count() << std::endl;
    assert(p == s);  // 断言失败

    return 0;
}

测试结果如图:
这里写图片描述

所以,在TcpConnection的生命期管理过程中,如果我们直接用this指针传递对象,可能会构建一个新的shared_ptr对象,并不是直接将我们之前管理的对象的shared_ptr拷贝过去从而使引用计数加1,故我们需要用到boost::enable_shared_from_this的shared_from_this()函数。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值