muduo網絡庫學習筆記(12):TcpServer和TcpConnection類

TcpServer類的主要功能是管理accept(2)獲得的TcpConnection,TcpConnection則表示的是“一次TCP連接”,一旦連接斷開,這個TcpConnection對象就沒啥用了。

由於TcpConnection類較複雜,本篇我們先學習TcpConnection類關於連接建立的處理。

當有新連接到來時,TcpServer新建連接的相關函數的調用時序圖如下:
這裏寫圖片描述

分析:當一個連接到來,EventLoop的事件循環loop()函數返回一個活躍的通道Channel,然後調用Channel的handleEvent()函數來處理這個事件。連接到來屬於可讀事件,又會回調Acceptor的handleRead()函數,在handleRead()函數中又調用accept()來接受這個新的連接,然後又回調了TcpServer的newConnection()函數。newConnection()函數會創建一個TcpConnection對象,然後這個對象調用TcpConnection的成員函數connectEstablished(),在這個函數中,回調了用戶設置的連接到來回調函數connCb()。

TcpServer類

TcpServer類的接口簡單易懂,如下:

代碼片段1:TcpServer的接口
文件名:TcpServer.h

class TcpServer : boost::noncopyable
{
 public:
  // 構造函數,InetAddress是對網際地址sockaddr_in的封裝
  TcpServer(EventLoop* loop,
            const InetAddress& listenAddr,
            const string& nameArg);
  // 析構函數
  ~TcpServer();  

  const string& hostport() const { return hostport_; }
  const string& name() const { return name_; }

  void start(); // 啓動TcpServer

  // 設置連接到來或者連接關閉回調函數
  void setConnectionCallback(const ConnectionCallback& cb)
  { connectionCallback_ = cb; } 

  // 設置消息到來回調函數
  void setMessageCallback(const MessageCallback& cb)
  { messageCallback_ = cb; } 

 private:
  // 此函數會創建TcpConnection對象,下面會着重分析
  void newConnection(int sockfd, const InetAddress& peerAddr);

  // 連接列表
  // key爲TcpConnection的名字,value是指向TcpConnectinon對象的指針
  typedef std::map<string, TcpConnectionPtr> ConnectionMap; 

  EventLoop* loop_;  
  const string hostport_;       // 服務端口
  const string name_;           // 服務名
  boost::scoped_ptr<Acceptor> acceptor_; // Acceptor中有對accept()的封裝,獲得新的連接

  // 用戶提供的ConnectionCallback和MessageCallback
  // 在新建TcpConnection時會原樣傳給後者
  ConnectionCallback connectionCallback_; 
  MessageCallback messageCallback_;

  bool started_;                // TcpServer是否啓動
  int nextConnId_;              // 下一個連接ID
  ConnectionMap connections_;   // 連接列表
};
代碼片段2:TcpServer::newConnection()
文件名:TcpServer.cc

void TcpServer::newConnection(int sockfd, const InetAddress& peerAddr)
{
  loop_->assertInLoopThread();
  char buf[32];
  snprintf(buf, sizeof buf, ":%s#%d", hostport_.c_str(), nextConnId_);
  ++nextConnId_;

  // 連接名稱以 服務名+服務端口+連接ID 格式命名
  string connName = name_ + buf;

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

  // FIXME poll with zero timeout to double confirm the new connection
  // FIXME use make_shared if necessary,此處使用make_shared()可以節約一次new
  // 創建TcpConnection對象
  TcpConnectionPtr conn(new TcpConnection(loop_,
                                          connName,
                                          sockfd,
                                          localAddr,
                                          peerAddr));
  // 把TcpConnection對象加入ConnectionMap
  connections_[connName] = conn;

  // 設置ConnectionCallback和MessageCallback
  conn->setConnectionCallback(connectionCallback_);
  conn->setMessageCallback(messageCallback_);

  // 此函數會回調用戶提供的ConnectionCallback
  conn->connectEstablished();
}

TcpConnection類

本篇暫只討論TcpConnection類關於連接建立的處理,接口也很簡單:

代碼片段3:TcpConnection的接口
文件名:TcpConnection.h

class TcpConnection : boost::noncopyable,
                      public boost::enable_shared_from_this<TcpConnection>
{
 public:
  /// Constructs a TcpConnection with a connected sockfd
  /// User should not create this object.
  // 構造函數
  TcpConnection(EventLoop* loop,
                const string& name,
                int sockfd,
                const InetAddress& localAddr,
                const InetAddress& peerAddr);
  // 析構函數
  ~TcpConnection();

  EventLoop* getLoop() const { return loop_; }
  const string& name() const { return name_; }
  const InetAddress& localAddress() { return localAddr_; }
  const InetAddress& peerAddress() { return peerAddr_; }

  // 判斷是否已連接
  bool connected() const { return state_ == kConnected; } 

  void setConnectionCallback(const ConnectionCallback& cb)
  { connectionCallback_ = cb; }

  void setMessageCallback(const MessageCallback& cb)
  { messageCallback_ = cb; }

  // called when TcpServer accepts a new connection
  void connectEstablished();   // should be called only once

 private:
  enum StateE { kConnecting, kConnected }; // 連接狀態

  void handleRead(Timestamp receiveTime); 
  void setState(StateE s) { state_ = s; } // 設置連接狀態

  EventLoop* loop_;         // 所屬EventLoop
  string name_;             // 連接名
  StateE state_;            // FIXME: use atomic variable
  // we don't expose those classes to client.
  boost::scoped_ptr<Socket> socket_;
  boost::scoped_ptr<Channel> channel_;
  InetAddress localAddr_;    // 本地地址
  InetAddress peerAddr_;     // 對等方地址
  ConnectionCallback connectionCallback_;
  MessageCallback messageCallback_;
};
代碼片段4:TcpConnection.cc(處理連接建立事件)
文件名:TcpConnection.cc

#include <muduo/net/TcpConnection.h>
#include <muduo/base/Logging.h>
#include <muduo/net/Channel.h>
#include <muduo/net/EventLoop.h>
#include <muduo/net/Socket.h>
#include <muduo/net/SocketsOps.h>
#include <boost/bind.hpp>
#include <errno.h>
#include <stdio.h>

using namespace muduo;
using namespace muduo::net;

// 構造函數
// TcpConnection沒有發起連接的功能
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)
{
  // 通道可讀事件到來的時候,回調TcpConnection::handleRead,_1是事件發生時間
  channel_->setReadCallback(
      boost::bind(&TcpConnection::handleRead, this, _1));
  LOG_DEBUG << "TcpConnection::ctor[" <<  name_ << "] at " << this
            << " fd=" << sockfd;
  socket_->setKeepAlive(true); // 設置SO_KEEPALIVE選項
}

// 析構函數
TcpConnection::~TcpConnection()
{
  LOG_DEBUG << "TcpConnection::dtor[" <<  name_ << "] at " << this
            << " fd=" << channel_->fd();
}

void TcpConnection::connectEstablished()
{
  loop_->assertInLoopThread();

  // 斷言連接狀態爲正在建立連接
  assert(state_ == kConnecting);

  // 設置連接狀態爲已連接
  setState(kConnected);
  channel_->tie(shared_from_this());
  channel_->enableReading(); // 將TcpConnection所對應的通道加入到Poller關注

  // 回調用戶設置的函數
  connectionCallback_(shared_from_this());
}

void TcpConnection::handleRead(Timestamp receiveTime)
{
  loop_->assertInLoopThread();
  char buf[65536];
  // 調用read將消息讀到buf中
  ssize_t n = ::read(channel_->fd(), buf, sizeof buf);
  messageCallback_(shared_from_this(), buf, n); 
}

示例

測試程序:

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

using namespace muduo;
using namespace muduo::net;

// 連接建立回調函數
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());
}

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

  InetAddress listenAddr(8888); // 構造地址INADDR_ANY,端口號爲8888
  EventLoop loop; // 構造一個EventLoop對象

  TcpServer server(&loop, listenAddr, "TestServer"); // 構造一個TcpServer對象,傳入EventLoop對象、地址和服務名

  // 設置回調函數
  server.setConnectionCallback(onConnection);
  server.setMessageCallback(onMessage);

  // 啓動TcpServer
  server.start();

  // 啓動事件循環
  loop.loop();
}

測試結果:
1.啓動我們的測試服務
這裏寫圖片描述

2.客戶端使用telnet發起連接
這裏寫圖片描述

3.TcpServer接受新連接,處理連接到來事件
這裏寫圖片描述

4.斷開客戶端連接,由於還未實現對連接斷開事件的處理,服務端會陷入busy loop狀態
這裏寫圖片描述

這裏寫圖片描述

發佈了44 篇原創文章 · 獲贊 34 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章