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狀態