muduo库的TcpConnection类剖析

本文链接:https://blog.csdn.net/Mr_scott_j/article/details/111059068

TcpConnection是muduo中最为复杂的类,其实就是对已连接套接字的一个抽象。

TcpConnection使用Channel来获得socket上的IO事件,它可以自己处理writable事件,而把readable事件通过MessageCallback传达给客户。在TcpConnection析构时候会close(fd)(在Socket析构函数中发生)。

TcpConnection在构造函数中将handleRead注册到Channel::ReadCallback上,并在其中调用messageCallback_;handleWrite由自己处理。

TcpConnection::TcpConnection(EventLoop* loop,
                             const string& nameArg,
                             int sockfd,
                             const InetAddress& localAddr,
                             const InetAddress& peerAddr)
  : loop_(CHECK_NOTNULL(loop)),//不能为空,否则触发FATAL
    name_(nameArg),
    state_(kConnecting),
    reading_(true),
    socket_(new Socket(sockfd)),
    channel_(new Channel(loop, sockfd)),
    localAddr_(localAddr),//本地地址
    peerAddr_(peerAddr),
    highWaterMark_(64*1024*1024)
{
   
   
//handleRead中会调用messageCallback_
  channel_->setReadCallback(
      std::bind(&TcpConnection::handleRead, this, _1));
  channel_->setWriteCallback(
      std::bind(&TcpConnection::handleWrite, this));
  channel_->setCloseCallback(
      std::bind(&TcpConnection::handleClose, this));
  channel_->setErrorCallback(
      std::bind(&TcpConnection::handleError, this));
  LOG_DEBUG << "TcpConnection::ctor[" <<  name_ << "] at " << this
            << " fd=" << sockfd;
  socket_->setKeepAlive(true);
}

TcpConnection::CloseCallback是回调给TcpServer和TcpClient,用于通知它们去移除所持有的TcpConnectionPtr,这个接口对普通用户来说是不可见的。注:见newConnection代码31行
TcpConnectionPtr的一连串typedef如下:

typedef std::shared_ptr<TcpConnection> TcpConnectionPtr;
typedef std::map<string, TcpConnectionPtr> ConnectionMap;
ConnectionMap connections_;

在TcpServer::newConnection()中,我们把TcpServer::removeConnection()注册到TcpConnection的setCloseCallback上,用于接收连接断开的消息。

在新连接到达时,Acceptor会回调newConnection( ),然后TcpServer创建TcpConnection对象,并加入到Map当中;在连接断开时,TcpConnection会回调removeConnection(),并从Map中移除。

//在构造函数中给acceptor绑定了一个newConnection回调函数,所以当有客户端连接触发accept时,会调用该函数。
void TcpServer::newConnection(int sockfd, const InetAddress& peerAddr)
{
   
   
  loop_->assertInLoopThread();
  //使用round-robin选组一个I/O loop
  EventLoop* ioLoop = threadPool_->getNextLoop();
  //name_构造
  char buf[64];
  snprintf(buf, sizeof buf, "-%s#%d", ipPort_.c_str(), nextConnId_);
  ++nextConnId_;
  string connName = name_ + buf;

  LOG_INFO << "TcpServer::newConnection [" << name_
           << "] - new connection [" << connName
           << "] from " << peerAddr.toIpPort();
  //构造本地地址
  InetAddress localAddr(sockets::getLocalAddr(sockfd));

  TcpConnectionPtr conn(new TcpConnection(ioLoop,//创建一个连接对象
                                          connName,
                                          sockfd,
                                          localAddr,
                                          peerAddr));
//ConnectionMap  std::map<string, TcpConnectionPtr>   typedef std::shared_ptr<TcpConnection> TcpConnectionPtr;
  connections_[connName] = conn;
  //将用户提供的connectionCallback_等原样传给TcpConnection,TcpServer持有TcpConnection的shared_ptr
  conn->setConnectionCallback(connectionCallback_);
  conn->setMessageCallback(messageCallback_);
  conn->setWriteCompleteCallback(writeCompleteCallback_);
  //将TcpServer的removeConnection设置到TcpConnection的关闭回调函数中
  conn->setCloseCallback(
      std::bind(&TcpServer::removeConnection, this, _1)); // FIXME: unsafe
  //connectEstablished会调用用户提供的ConnectionCallback
  ioLoop->runInLoop(std::bind(&TcpConnection::connectEstablished, conn));
  //调用TcpConenction::connectEstablished函数内部会将use_count加一然后减一,此处仍为2
  //但是本函数结束后conn对象会析构掉,所以引用计数为1,仅剩connections_列表中存活一个
}

在TcpConnection::connectEstablished()中,我们会回调用户的connectionCallback_,并且加入到Poller中关注。

void TcpConnection::connectEstablished()
{
   
   
  loop_->assertInLoopThread();   //断言处于loop线程
  assert(state_ == kConnecting);   //断言处于未连接状态
  setState(kConnected);   //将状态设置为已连接
 
  //之前引用计数为2
  channel_->tie(shared_from_this());   //将自身这个TcpConnection对象提升,由于是智能指针,所以不能直接用this
  //shared_from_this()之后引用计数+1,为3,但是shared_from_this()是临时对象,析构后又会减一,
  //而tie是weak_ptr并不会改变引用计数,所以该函数执行完之后引用计数不会更改
  
  channel_->enableReading();   //一旦连接成功就关注它的可读事件,加入到Poller中关注
 
  connectionCallback_(shared_from_this());
}

enableReading代码如下:

//const int Channel::kReadEvent = POLLIN | POLLPRI;
 void enableReading() {
   
    events_ |= kReadEvent; update(); }

回到newConnection函数中来,因为TcpServer的生命周期一般情况下都大于TcpConnection,且~TcpServer()中会对连接进行关闭,因此该函数是安全的。

在此处将原本的一个removeConnection函数拆分成两个『removeConnection()』和『removeConnectionInLoop()』目的在于TcpConnection会在自己的ioLoop线程调用removeConnection(),将其移到TcpServer的loop_线程进行。

void TcpServer::removeConnection(const TcpConnectionPtr& conn)
{
   
   
  // FIXME: unsafe
  //
  loop_->runInLoop(std::bind(&TcpServer::removeConnectionInLoop, this, conn));
}

在倒数第三行,将connectDestroyed()移交回TcpConnection的ioLoop,目的是让connectDestroyed()调用的connectionCallback_始终在其ioLoop回调,方便客户端代码编写。
因为将conn从Map中移除,conn的引用计数已经降低为1。将TcpConnection的生命期长到调用connectDestroyed()的时候。

void TcpServer::removeConnectionInLoop(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* ioLoop = conn->getLoop();
  //将TcpConnection的生命期长到调用connectDestroyed的时候,conn为shared_ptr
  ioLoop->queueInLoop(
      std::bind(&TcpConnection::connectDestroyed, conn));
}

新连接建立时序图:
handleEvent()触发的条件为listening socket可读,表示有新连接到达。TcpServer创建对应的TcpConnection对象。
在这里插入图片描述

连接关闭时序图如下:X表示大多数情况下TcpConnection会在此析构
在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章