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

li27z發表於2016-11-08

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狀態
這裡寫圖片描述

這裡寫圖片描述

相關文章