我將會寫一系列文章,介紹用 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 Proxy 伺服器,包括簡單的 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 快嗎?