陳碩 (giantchen_AT_gmail)
Blog.csdn.net/Solstice
Muduo 全系列文章清單: http://blog.csdn.net/Solstice/category/779646.aspx
用于測試兩台機器的往返延遲的 roundtrip
用于測試兩台機器的帶寬的 pingpong
檔案傳輸
一個基于 TCP 的應用層廣播 hub
socks4a 代理伺服器,包括簡單的 TCP 中繼(relay)。
一個 Sudoku 伺服器的演變,從單線程到多線程,從阻塞到 event-based。
一個提供短址服務的 httpd 伺服器
這些例子都比較簡單,邏輯不複雜,代碼也很短,适合摘取關鍵部分放到部落格上。其中一些有一定的代表性與針對性,比如“如何傳輸完整的檔案”估計是網絡程式設計的初學者經常遇到的問題。請注意,muduo 是設計來開發内網的網絡程式,它沒有做任何安全方面的加強措施,如果用在公網上可能會受到攻擊,在後面的例子中我會談到這一點。
我認為,TCP 網絡程式設計最本質的是處理三個半事件:
連接配接的建立,包括服務端接受 (accept) 新連接配接和用戶端成功發起 (connect) 連接配接。
連接配接的斷開,包括主動斷開 (close 或 shutdown) 和被動斷開 (read 傳回 0)。
消息到達,檔案描述符可讀。這是最為重要的一個事件,對它的處理方式決定了網絡程式設計的風格(阻塞還是非阻塞,如何處理分包,應用層的緩沖如何設計等等)。
消息發送完畢,這算半個。對于低流量的服務,可以不必關心這個事件;另外,這裡“發送完畢”是指将資料寫入作業系統的緩沖區,将由 TCP 協定棧負責資料的發送與重傳,不代表對方已經收到資料。
這其中有很多難點,也有很多細節需要注意,比方說:
如果要主動關閉連接配接,如何保證對方已經收到全部資料?如果應用層有緩沖(這在非阻塞網絡程式設計中是必須的,見下文),那麼如何保證先發送完緩沖區中的資料,然後再斷開連接配接。直接調用 close(2) 恐怕是不行的。
如果主動發起連接配接,但是對方主動拒絕,如何定期 (帶 back-off) 重試?
非阻塞網絡程式設計該用邊沿觸發(edge trigger)還是電平觸發(level trigger)?(這兩個中文術語有其他譯法,我選擇了一個電子工程師熟悉的說法。)如果是電平觸發,那麼什麼時候關注 EPOLLOUT 事件?會不會造成 busy-loop?如果是邊沿觸發,如何防止漏讀造成的饑餓?epoll 一定比 poll 快嗎?
在非阻塞網絡程式設計中,如何設計并使用緩沖區?一方面我們希望減少系統調用,一次讀的資料越多越劃算,那麼似乎應該準備一個大的緩沖區。另一方面,我們系統減少記憶體占用。如果有 10k 個連接配接,每個連接配接一建立就配置設定 64k 的讀緩沖的話,将占用 640M 記憶體,而大多數時候這些緩沖區的使用率很低。muduo 用 readv 結合棧上空間巧妙地解決了這個問題。
如果使用發送緩沖區,萬一接收方處理緩慢,資料會不會一直堆積在發送方,造成記憶體暴漲?如何做應用層的流量控制?
如何設計并實作定時器?并使之與網絡 IO 共用一個線程,以避免鎖。
這些問題在 muduo 的代碼中可以找到答案。
我編寫 Muduo 網絡庫的目的之一就是簡化日常的 TCP 網絡程式設計,讓程式員能把精力集中在業務邏輯的實作上,而不要天天和 Sockets API 較勁。借用 Brooks 的話說,我希望 Muduo 能減少網絡程式設計中的偶發複雜性 (accidental complexity)。
Muduo 的使用非常簡單,不需要從指定的類派生,也不用覆寫虛函數,隻需要注冊幾個回調函數去處理前面提到的三個半事件就行了。
以經典的 echo 回顯服務為例:
1 #ifndef MUDUO_EXAMPLES_SIMPLE_ECHO_ECHO_H
2 #define MUDUO_EXAMPLES_SIMPLE_ECHO_ECHO_H
3 #include <muduo/net/TcpServer.h>
4 // RFC 862
5 class EchoServer
6 {
7 public:
8 EchoServer(muduo::net::EventLoop* loop,
9 const muduo::net::InetAddress& listenAddr);
10 void start();
11 private:
12 void onConnection(const muduo::net::TcpConnectionPtr& conn);
13 void onMessage(const muduo::net::TcpConnectionPtr& conn,
14 muduo::net::Buffer* buf,
15 muduo::Timestamp time);
16 muduo::net::EventLoop* loop_;
17 muduo::net::TcpServer server_;
18 };
19 #endif // MUDUO_EXAMPLES_SIMPLE_ECHO_ECHO_H
1 EchoServer::EchoServer(EventLoop* loop,
2 const InetAddress& listenAddr)
3 : loop_(loop),
4 server_(loop, listenAddr, "EchoServer")
5 {
6 server_.setConnectionCallback(
7 boost::bind(&EchoServer::onConnection, this, _1));
8 server_.setMessageCallback(
9 boost::bind(&EchoServer::onMessage, this, _1, _2, _3));
10 }
11
12 void EchoServer::start()
13 {
14 server_.start();
15 }
1 void EchoServer::onConnection(const TcpConnectionPtr& conn)
2 {
3 LOG_INFO << "EchoServer - " << conn->peerAddress().toHostPort() << " -> "
4 << conn->localAddress().toHostPort() << " is "
5 << (conn->connected() ? "UP" : "DOWN");
6 }
7
8 void EchoServer::onMessage(const TcpConnectionPtr& conn,
9 Buffer* buf,
10 Timestamp time)
11 {
12 string msg(buf->retrieveAsString());
13 LOG_INFO << conn->name() << " echo " << msg.size() << " bytes at " << time.toString();
14 conn->send(msg);
1 #include "echo.h"
2 #include <muduo/base/Logging.h>
3 #include <muduo/net/EventLoop.h>
4 using namespace muduo;
5 using namespace muduo::net;
6 int main()
7 {
8 LOG_INFO << "pid = " << getpid();
9 EventLoop loop;
10 InetAddress listenAddr(2007);
11 EchoServer server(&loop, listenAddr);
12 server.start();
13 loop.loop();
14 }
完整的代碼見 muduo/examples/simple/echo。
這個幾十行的小程式實作了一個并發的 echo 服務程式,可以同時處理多個連接配接。
對這個程式的詳細分析見下一篇部落格《Muduo 網絡程式設計示例之一:五個簡單 TCP 協定》
(待續)
<a href="http://www.cnblogs.com/Solstice/archive/2011/02/02/1948814.html">http://www.cnblogs.com/Solstice/archive/2011/02/02/1948814.html</a>