天天看點

muduo庫學習之設計與實作06——TcpServer 接受新連接配接

東陽的學習筆記
muduo 盡量讓依賴是單向的,比如,TcpServer 會用到 Acceptor,但 Acceptor 并不知道 TcpServer 的存在。

本文會介紹 TcpServer 并初步實作 TcpConnection,本文隻處理連接配接的建立,下一篇部落格會處理連接配接的斷開。

TcpServer 建立連接配接的相關函數調用順序見圖 8-4 (有的函數名簡寫,省略了 poll(2) 調用)。

  • 其中 Channel::handleEvent() 的觸發條件是

    listening socket

    可讀,表明有新連接配接到達。
  • TcpServer 會為新連接配接建立對應的

    TcpConnection

    對象。
    muduo庫學習之設計與實作06——TcpServer 接受新連接配接

一、TcpServer class

TcpServer class 的功能是管理 accept(2) 獲得的 TcpConnection。TcpServer 是供使用者直接使用的,生命期由使用者控制。TcpServer 的接口如下,使用者隻需要設定好 callback,再調用 start() 即可
  • TcpServer 内部使用 Acceptor 來獲得新連接配接的 fd。
  • TcpServer 儲存使用者提供的

    ConnectionCallback

    MessageCallback

    ,在建立

    TcpConnection

    時, 傳給其構造函數。
  • TcpServer 持有目前存活的 TcpConnection 的 shared_ptr (即

    TcpConnectionPtr

    ),因為 TcpConnection

    對象的生命期是模糊的

    ,使用者也可以持有

    TcpCOnnectionPtr

class TcpServer : boost::noncopyable
{
public:

 TcpServer(EventLoop* loop, const InetAddress& listenAddr);
 ~TcpServer();  // force out-line dtor, for scoped_ptr members.

 /// Starts the server if it's not listenning.
 ///
 /// It's harmless to call it multiple times.
 /// Thread safe.
 void start();

 /// Set connection callback.
 /// Not thread safe.
 void setConnectionCallback(const ConnectionCallback& cb)
 { connectionCallback_ = cb; }

 /// Set message callback.
 /// Not thread safe.
 void setMessageCallback(const MessageCallback& cb)
 { messageCallback_ = cb; }
           

1.1 TcpSever 的資料成員

每個 TcpConnect 對象有一個名字,這個名字是由其所屬的 TcpServer 在建立 TcpConnection 對象時生成,

名字是 ConnectionMap 的 key.

private:
 /// Not thread safe, but in loop
 void newConnection(int sockfd, const InetAddress& peerAddr);

 typedef std::map<std::string, TcpConnectionPtr> ConnectionMap;

 EventLoop* loop_;  // the acceptor loop
 const std::string name_;   // 名字是 ConnectionMap 的 key.
 boost::scoped_ptr<Acceptor> acceptor_; // avoid revealing Acceptor
 ConnectionCallback connectionCallback_;
 MessageCallback messageCallback_;
 bool started_;
 int nextConnId_;  // always in loop thread
 ConnectionMap connections_;
};
           

1.2 TcpServer::newConnection()

新連接配接到達時,Acceptor 會回調 newConnection(),
  1. 後者會建立 TcpConnection 對象 conn

  2. 建立好 conn, 并将其放入

    ConnectionMap

    ,設定好 callback。
  3. 再調用 conn->connectEstablished(),其中會回調使用者提供的 ConnectionCallback。

&16 可以使用 make_share 節約一次 new(為啥?make_share 代替 new)

見通過new和make_shared構造shared_ptr的性能差異

void TcpServer::newConnection(int sockfd, const InetAddress& peerAddr)
{
 loop_->assertInLoopThread();
 char buf[32];
 snprintf(buf, sizeof buf, "#%d", nextConnId_);
 ++nextConnId_;
 std::string connName = name_ + buf;

 LOG_INFO << "TcpServer::newConnection [" << name_
          << "] - new connection [" << connName
          << "] from " << peerAddr.toHostPort();
 InetAddress localAddr(sockets::getLocalAddr(sockfd));
 // FIXME poll with zero timeout to double confirm the new connection
 TcpConnectionPtr conn(
     new TcpConnection(loop_, connName, sockfd, localAddr, peerAddr));
 connections_[connName] = conn;
 conn->setConnectionCallback(connectionCallback_);
 conn->setMessageCallback(messageCallback_);
 conn->connectEstablished();
}
           

二、TcpConnection class

TcpConnection class 可謂是 muduo

最核心也是最複雜

的 class(源檔案和頭檔案一共有450多行,是 muduo 中最大的 class)

TcpConnection 是 muduo 裡唯一使用 shared_ptr 來管理的 class,也是唯一繼承

enable_shared_from_this

的class,這源于其模糊的生命期。
  • TcpConnection 表示的是

    一次TCP連接配接

    ,它是不可再生的,一但連接配接斷開,這個 TcpConnection 對象就沒啥用了。
  • TcpConnection 沒有發起連接配接的功能,其構造函數的參數是已經建立連接配接的 socket fd (無論是

    TcpServer

    被動接受還是 TcpClient 主動發起),是以其初始狀态是

    kConnection

這裡的 TcpConnection 沒有可供使用者使用的接口。主動發起
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 std::string& name,
               int sockfd,
               const InetAddress& localAddr,
               const InetAddress& peerAddr);
 ~TcpConnection();

 EventLoop* getLoop() const { return loop_; }
 const std::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; }

 /// Internal use only.

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

private:
 enum StateE { kConnecting, kConnected, };     // 在s05中隻有這兩個,後面還會擴充

 void setState(StateE s) { state_ = s; }
 void handleRead();

 EventLoop* loop_;
 std::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_;
};
           

2.1 MessageCallback()

這個版本MessageCallback 定義很原始(簡陋):
  • 沒有使用Buffer class,而隻是把(const char * buf, int len) 傳給使用者,這種接口用起來無疑是很不友善的。
void TcpConnection::handleRead()
{
 char buf[65536];
 ssize_t n = ::read(channel_->fd(), buf, sizeof buf);
 messageCallback_(shared_from_this(), buf, n);
 // FIXME: close connection if n == 0
 // s05中的版本沒有處理斷開連接配接
}