muduo_net库源码分析(十)

本文详细解析了TCP连接管理中的生存期问题,通过分析TcpConnection类与Channel对象的交互,阐述了如何确保TcpConnection对象在Channel::handleEvent执行结束后仍能存活。核心在于利用shared_ptr和弱引用来控制对象生命周期,确保在连接关闭时正确地关闭和删除连接对象。
摘要由CSDN通过智能技术生成

TcpConnection生存期管理

每建立一个新连接就会创建一个TcpConnection类的对象。当连接断开时,显然要求TcpConnection类的对象的生存期应该长于Channel::handleEvent函数。因此,难点在于如何实现“TcpConnection类的对象的生存期长于Channel::handleEvent函数”。
在这里插入图片描述

调用顺序

调用1

创建EventLoop类的对象–>通过该对象调用EventLoop::loop–>EventLoop::loop中调用Poller::poll(多态)返回活跃的Channel对象–>调用Channel::handleEvent–>在Channel::handleEvent中,如果到来的是连接关闭事件,由于该事件也是读事件,所以调用读事件回调函数(这里已经在TcpConnection类的构造函数中通过其Channel对象调用Channel::setReadCallback将TcpConnection::handleRead设置为读事件回调函数)–>在TcpConnection::handleRead中,由于此时调用::read返回结果为0,所以会调用TcpConnection::handleClose(这里已经在TcpConnection类的构造函数中通过其Channel对象调用Channel::setCloseCallback将TcpConnection::handleClose设置为连接关闭事件回调函数)–>在TcpConnection::handleClose中,调用closeCallback_即TcpServer::removeConnection(监听事件到来时,在TcpServer::newConnection中,首先创建了TcpConnection类的对象,并将其加入std::map(即连接列表)中;接着将TcpServer类中的连接到来时的回调函数connectionCallback_、消息到来时的回调函数messageCallback_和连接断开时的回调函数TcpServer::removeConnection通过调用TcpConnection::setConnectionCallback、TcpConnection::setMessageCallback和TcpConnection::setCloseCallback 赋值给TcpConnection类中的connectionCallback_、messageCallback_和closeCallback_)–>在TcpServer::removeConnection函数中,调用::erase将当前TcpConnection类的对象所对应的boost::shared_ptr<TcpConnection>类型的对象从std::map(即连接列表)中移除,此时shared_ptr引用计数-1;接着,通过TcpServer类所属EventLoop类的对象指针调用EventLoop::queueInLoop(该函数可跨线程调用),给IO线程布置任务,即将TcpConnection::connectDestroyed函数添加到IO线程的任务队列中–>在TcpConnection::connectDestroyed中,按顺序先后调用Channel::disableAll和Channel::remove,将当前事件从epoll的红黑树上移除

调用2

在TcpConnection::connectEstablished函数中,TcpConnection类中的Channel对象调用Channel::tie函数,传入该函数的实参shared_from_this()boost::shared_ptr<TcpConnection>类型的对象(注意,TcpConnection类公有继承于boost::enable_shared_from_this<TcpConnection>类,因此,shared_from_this()就是返回监管TcpConnection类的对象自身的shared_ptr对象),并在Channel::tie函数体中将其赋值给该Channel对象的对象成员boost::weak_ptr<void> tie_;(这是弱引用,并不会把shared_ptr对象的引用计数+1)。

接管TcpConnection对象的shared_ptr对象的引用计数(相当复杂^^)

1.TcpServer::newConnection中新创建TcpConnection对象后马上创建shared_ptr对象conn将其接管,此时引用计数+1,引用计数为1;
2.将shared_ptr对象conn加入std::map(即连接列表)中,此时引用计数+1,引用计数为2;
3.TcpServer::newConnection中通过conn调用了TcpConnection::connectEstablished–>在TcpConnection::connectEstablished函数中,TcpConnection类中的Channel对象调用Channel::tie函数,传入该函数的实参shared_from_this()boost::shared_ptr<TcpConnection>类型的对象(注意,TcpConnection类公有继承于boost::enable_shared_from_this<TcpConnection>类,因此,shared_from_this()就是返回TcpConnection类自身的shared_ptr对象),并在Channel::tie函数体中将其赋值给该Channel对象的对象成员boost::weak_ptr<void> tie_;(这是弱引用,并不会把shared_ptr对象的引用计数+1);每次调用shared_from_this函数都会创建一个监管当前TcpConnection对象的shared_ptr临时对象,引用计数会+1,当该shared_ptr临时对象销毁后,引用计数又会-1;因此,当TcpConnection::connectEstablished执行完毕后,引用计数仍然为2;
4.当TcpServer::newConnection也执行完毕后,1中创建shared_ptr对象conn由于是局部对象也被销毁,此时引用计数-1,引用计数为1;
5.当连接关闭时:
在这里插入图片描述
在Channel::handleEvent中,显然此时if (tied_)为true,tie_.lock()将弱引用提升,返回一个shared_ptr对象guard,此时引用计数+1,引用计数为2–>接着在Channel::handleEvent中调用了Channel::handleEventWithGuard–>在Channel::handleEventWithGuard中调用了TcpConnection::handleRead–>在TcpConnection::handleRead中,由于此时调用::read返回结果为0,所以会调用TcpConnection::handleClose
在这里插入图片描述
–>在TcpConnection::handleClose中,TcpConnectionPtr guardThis(shared_from_this());先通过shared_from_this()创建临时的shared_ptr对象,然后拷贝构造创建了局部shared_ptr对象guardThis,执行完后引用计数+1,引用计数为3;接着调用closeCallback_即TcpServer::removeConnection
在这里插入图片描述
–>在TcpServer::removeConnection函数中,调用::erase将当前TcpConnection类的对象所对应的boost::shared_ptr<TcpConnection>类型的对象从std::map(即连接列表)中移除,此时引用计数-1,引用计数为2;接着通过TcpServer类所属EventLoop类的对象指针调用EventLoop::queueInLoop(该函数可跨线程调用),给IO线程布置任务,即将TcpConnection::connectDestroyed函数添加到IO线程的任务队列中,这里调用boost::bind为TcpConnection::connectDestroyed函数创建boost::function对象时进行值传递,传入的是boost::shared_ptr<TcpConnection>类型的对象的copy,导致引用计数+1,引用计数为3–>TcpServer::removeConnection函数执行完毕,引用计数仍然为3–>TcpConnection::handleClose函数执行完毕,局部对象guardThis被销毁,此时引用计数-1,引用计数为2–>Channel::handleEvent函数执行完毕,局部对象guard被销毁,此时引用计数-1,引用计数为1,而这个1就是为TcpConnection::connectDestroyed函数调用boost::bind创建boost::function对象时产生的,因此,最后当IO线程执行完TcpConnection::connectDestroyed函数后,引用计数-1,引用计数为0,最终将当前TcpConnection类的对象销毁。
综上,使用shared_ptr实现了“TcpConnection类的对象的生存期长于Channel::handleEvent函数”的目的。

源码

见“34net库源码”

测试代码

测试代码仍然用Reactor_test09.cc

#include <muduo/net/TcpServer.h>
#include <muduo/net/EventLoop.h>
#include <muduo/net/InetAddress.h>

#include <boost/bind.hpp>

#include <stdio.h>

using namespace muduo;
using namespace muduo::net;
//作者在这里又进一步封装,多搞出了一个TestServer类,类中记录了所属的IO线程(即所属的EventLoop对象,one loop per thread),使程序整体看起来更加清爽、简洁。
class TestServer
{
 public:
  TestServer(EventLoop* loop,
             const InetAddress& listenAddr)
    : loop_(loop),
      server_(loop, listenAddr, "TestServer")
  {
    server_.setConnectionCallback(
        boost::bind(&TestServer::onConnection, this, _1));//类的成员函数使用bind
    server_.setMessageCallback(
        boost::bind(&TestServer::onMessage, this, _1, _2, _3));
  }

  void start()
  {
	  server_.start();
  }

 private:
  //自定义的新连接到来时的回调函数
  void onConnection(const TcpConnectionPtr& conn)
  {
    if (conn->connected())
    {
      printf("onConnection(): new connection [%s] from %s\n",
             conn->name().c_str(),
             conn->peerAddress().toIpPort().c_str());
    }
    else//连接断开时会进入这里执行
    {
      printf("onConnection(): connection [%s] is down\n",
             conn->name().c_str());
    }
  }
  //自定义的消息到来时的回调函数
  void onMessage(const TcpConnectionPtr& conn,
                   const char* data,
                   ssize_t len)
  {
    printf("onMessage(): received %zd bytes from connection [%s]\n",
           len, conn->name().c_str());
  }

  EventLoop* loop_;
  TcpServer server_;
};


int main()
{
  printf("main(): pid = %d\n", getpid());

  InetAddress listenAddr(8888);
  EventLoop loop;

  TestServer server(&loop, listenAddr);
  server.start();

  loop.loop();
}

在这里插入图片描述
再开一个终端:
在这里插入图片描述
原终端:
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值