程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> Muduo 網絡編程示例 - 前言

Muduo 網絡編程示例 - 前言

編輯:關於C++

我將會寫一系列文章,介紹用 muduo 網絡庫完成常見的 TCP 網絡編程任務。目前計劃如下:

UNP 中的簡單協議,包括 echo、daytime、time、discard 等。 

Boost.Asio 中的示例, 包括 timer2~6、chat 等。

Java Netty 中的示例,包括 discard、echo、uptime 等,其中的 discard 和 echo 帶流量統計功能。

Python twisted 中的示例,包括 finger01~07

用於測試兩台機器的往返延遲的 roundtrip

用於測試兩台機器的帶寬的 pingpong

雲風的串並轉換連接服務器  multiplexer,包括單線程和多線程兩個版本。

文件傳輸

一個基於 TCP 的應用層廣播 hub

socks4a 代理服務器,包括簡單的 TCP 中繼(relay)。

一個 Sudoku 服務器的演變,從單線程到多線程,從阻塞到 event-based。

一個提供短址服務的 httpd 服務器

其中前面 7 個已經放到了 muduo 代碼的 examples 目錄中,下載地址是: http://muduo.googlecode.com/files/muduo-0.1.5-alpha.tar.gz 

這些例子都比較簡單,邏輯不復雜,代碼也很短,適合摘取關鍵部分放到博客上。其中一些有 一定的代表性與針對性,比如“如何傳輸完整的文件”估計是網絡編程的初學者經常遇到的 問題。請注意,muduo 是設計來開發內網的網絡程序,它沒有做任何安全方面的加強措施,如果用在公 網上可能會受到攻擊,在後面的例子中我會談到這一點。

本系列文章適用於 Linux 2.6.x (x > 28),主要測試發行版為 Ubuntu 10.04 LTS 和 Debian 6.0 Squeeze,64-bit x86 硬件。

TCP 網絡編程本質論

我認為,TCP 網絡編程最本質的是處理三個半事件:

連接的建 立,包括服務端接受 (accept) 新連接和客戶端成功發起 (connect) 連接。

連接的斷開,包 括主動斷開 (close 或 shutdown) 和被動斷開 (read 返回 0)。

消息到達,文件描述符可讀 。這是最為重要的一個事件,對它的處理方式決定了網絡編程的風格(阻塞還是非阻塞,如何處理分包 ,應用層的緩沖如何設計等等)。

消息發送完畢,這算半個。對於低流量的服務,可以不必關 心這個事件;另外,這裡“發送完畢”是指將數據寫入操作系統的緩沖區,將由 TCP 協議 棧負責數據的發送與重傳,不代表對方已經收到數據。

這其中有很多難點,也有很多細節需要注意,比方說:

如果要主動關閉連接,如何保證對方 已經收到全部數據?如果應用層有緩沖(這在非阻塞網絡編程中是必須的,見下文),那麼如何保證先 發送完緩沖區中的數據,然後再斷開連接。直接調用 close(2) 恐怕是不行的。

如果主動發起 連接,但是對方主動拒絕,如何定期 (帶 back-off) 重試?

非阻塞網絡編程該用邊沿觸發 (edge trigger)還是電平觸發(level trigger)?(這兩個中文術語有其他譯法,我選擇了一個電子工 程師熟悉的說法。)如果是電平觸發,那麼什麼時候關注 EPOLLOUT 事件?會不會造成 busy-loop?如 果是邊沿觸發,如何防止漏讀造成的饑餓?epoll 一定比 poll 快嗎?

在非阻塞網絡編程中, 為什麼要使用應用層緩沖區?假如一次讀到的數據不夠一個完整的數據包,那麼這些已經讀到的數據是 不是應該先暫存在某個地方,等剩余的數據收到之後再一並處理?見 lighttpd 關於 rnrn 分包的 bug 。假如數據是一個字節一個字節地到達,間隔 10ms,每個字節觸發一次文件描述符可讀 (readable) 事件,程序是否還能正常工作?lighttpd 在這個問題上出過安全漏洞。

在非阻塞網絡編程中 ,如何設計並使用緩沖區?一方面我們希望減少系統調用,一次讀的數據越多越劃算,那麼似乎應該准 備一個大的緩沖區。另一方面,我們系統減少內存占用。如果有 10k 個連接,每個連接一建立就分配 64k 的讀緩沖的話,將占用 640M 內存,而大多數時候這些緩沖區的使用率很低。muduo 用 readv 結 合棧上空間巧妙地解決了這個問題。

如果使用發送緩沖區,萬一接收方處理緩慢,數據會 不會一直堆積在發送方,造成內存暴漲?如何做應用層的流量控制?

如何設計並實現定時器? 並使之與網絡 IO 共用一個線程,以避免鎖。

這些問題在 muduo 的代碼中可以找到答案。

Muduo 簡介

我編寫 Muduo 網絡庫的目的之一就是簡化日常的 TCP 網絡編程,讓程序員 能把精力集中在業務邏輯的實現上,而不要天天和 Sockets API 較勁。借用 Brooks 的話說,我希望 Muduo 能減少網絡編程中的偶發復雜性 (accidental complexity)。

Muduo 只支持 Linux 2.6.x 下的並發非阻塞 TCP 網絡編程,它的安裝方法見陳碩的 blog 文章。

Muduo 的使用非常 簡單,不需要從指定的類派生,也不用覆寫虛函數,只需要注冊幾個回調函數去處理前面提到的三個半 事件就行了。

以經典的 echo 回顯服務為例:

1. 定義 EchoServer class,不需要派生 自任何基類:

 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 }

2. 實現 EchoServer::onConnection() 和 EchoServer::onMessage():

 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); 
15 } 

3. 在 main() 裡用 EventLoop 讓整個程序跑起來:

 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 協議》

(待續)

查看本欄目

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved