天天看點

Muduo 網絡程式設計示例之零:前言 TCP 網絡程式設計本質論 Muduo 簡介

陳碩 (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>

繼續閱讀