在網絡編程的學習曲線中,掌握幾個最基礎的 TCP 協議是絕佳的切入點。本文將帶領讀者透過 Muduo 網絡庫,逐步實作 Discard、Daytime、Time 等經典協議,並深入剖析長連線與短連線的事件處理差異。
Discard 協議(RFC 863)的行為極其單純:伺服端接受連線後,只需處理「訊息到達」事件,將收到的資料直接丟棄並記錄日誌。在 Muduo 中,核心邏輯僅需實作 onMessage 回呼:
void DiscardServer::onMessage(const TcpConnectionPtr& conn,
Buffer* buf,
Timestamp time)
{
string msg(buf->retrieveAsString()); // 取回所有讀到的資料
LOG_INFO << conn->name() << " discards " << msg.size()
<< " bytes at " << time.toString();
}
其餘皆為樣板程式碼:定義 DiscardServer 類別,持有 TcpServer 成員,在建構函式中註冊 onConnection 與 onMessage 回呼,最後在 main() 中啟動 EventLoop 即可運行。
Daytime(RFC 867)屬於短連線協議:連線建立後,伺服端立即發送當前時間字串,隨即主動關閉連線。因此只需關注「連線已建立」事件:
void DaytimeServer::onConnection(const TcpConnectionPtr& conn)
{
LOG_INFO << "DaytimeServer - " << conn->peerAddress().toHostPort()
<< " -> " << conn->localAddress().toHostPort()
<< " is " << (conn->connected() ? "UP" : "DOWN");
if (conn->connected())
{
conn->send(Timestamp::now().toFormattedString() + "\n");
conn->shutdown(); // 主動斷開
}
}
客戶端可用 nc 127.0.0.1 2013 測試,即可收到類似 2011-02-02 03:31:26.622647 的回應。
Time 協議(RFC 868)與 Daytime 類似,差別在於回傳的是網路序的 32 位元整數,代表自 1970-01-01 00:00:00 UTC 以來的秒數(即經典的「2038 年問題」來源)。處理邏輯幾乎相同:
void TimeServer::onConnection(const TcpConnectionPtr& conn)
{
if (conn->connected())
{
int32_t now = sockets::hostToNetwork32(static_cast<int>(::time(NULL)));
conn->send(&now, sizeof now); // 發送 4 個位元組
conn->shutdown();
}
}
這三個協議雖然簡單,卻完美展示了 Muduo 以事件驅動、回呼為核心的設計哲學:長連線處理 onMessage,短連線處理 onConnection。掌握這些基礎,便能輕鬆延伸至更複雜的應用層協議開發。