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會在此析構
在這裏插入圖片描述

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