發(fā)布一個(gè)基于 Reactor 模式的 C++ 網(wǎng)絡(luò)庫(kù)
陳碩 (giantchen_AT_gmail)
Blog.csdn.net/Solstice
2010 Aug 30
本文主要介紹 muduo 網(wǎng)絡(luò)庫(kù)的使用。其設(shè)計(jì)與實(shí)現(xiàn)將有另文講解。
目錄
由來(lái) 1
下載與編譯 2
例子 2
基本結(jié)構(gòu) 3
公開接口 4
內(nèi)部實(shí)現(xiàn) 4
線程模型 5
結(jié)語(yǔ) 5
由來(lái)
半年前我寫了一篇《學(xué)之者生,用之者死——ACE歷史與簡(jiǎn)評(píng)》,其中提到“我心目中理想的網(wǎng)絡(luò)庫(kù)”的樣子:
- 線程安全,支持多核多線程
- 不考慮可移植性,不跨平臺(tái),只支持 Linux,不支持 Windows。
- 在不增加復(fù)雜度的前提下可以支持 FreeBSD/Darwin,方便將來(lái)用 Mac 作為開發(fā)用機(jī),但不為它做性能優(yōu)化。也就是說(shuō) IO multiplexing 使用 poll 和 epoll。
- 主要支持 x86-64,兼顧 IA32
- 不支持 UDP,只支持 TCP
- 不支持 IPv6,只支持 IPv4
- 不考慮廣域網(wǎng)應(yīng)用,只考慮局域網(wǎng)
- 只支持一種使用模式:non-blocking IO + one event loop per thread,不考慮阻塞 IO
- API 簡(jiǎn)單易用,只暴露具體類和標(biāo)準(zhǔn)庫(kù)里的類,不使用 non-trivial templates,也不使用虛函數(shù)
- 只滿足常用需求的 90%,不面面俱到,必要的時(shí)候以 app 來(lái)適應(yīng) lib
- 只做 library,不做成 framework
- 爭(zhēng)取全部代碼在 5000 行以內(nèi)(不含測(cè)試)
- 以上條件都滿足時(shí),可以考慮搭配 Google Protocol Buffers RPC
在想清楚這些目標(biāo)之后,我開始第三次嘗試編寫自己的 C++ 網(wǎng)絡(luò)庫(kù)。與前兩次不同,這次我一開始就想好了庫(kù)的名字,叫 muduo (木鐸),并在 Google code 上創(chuàng)建了項(xiàng)目: http://code.google.com/p/muduo/ 。muduo 的主體內(nèi)容在 5 月底已經(jīng)基本完成,現(xiàn)在我把它開源。
本文主要介紹 muduo 網(wǎng)絡(luò)庫(kù)的使用,其設(shè)計(jì)與實(shí)現(xiàn)將有另文講解。
下載與編譯
下載地址: http://muduo.googlecode.com/files/muduo-0.1.0-alpha.tar.gz
SHA1 Checksum: 5d3642e311177ded89ed0d15c10921738f8c984c
Muduo 使用了 Linux 較新的系統(tǒng)調(diào)用,要求 Linux 的內(nèi)核版本大于 2.6.28 (我自己用的是 2.6.32 )。在 Debian Squeeze / Ubuntu 10.04 LTS 上編譯測(cè)試通過(guò),32 位和 64 位系統(tǒng)都能使用。
Muduo 采用 CMake 為 build system,安裝方法:
$ sudo apt-get install cmake
Muduo 依賴 Boost,很容易安裝:
$ sudo apt-get install libboost1.40-dev # 或 libboost1.42-dev
編譯方法很簡(jiǎn)單:
$ tar zxf muduo-0.1.0-alpha.tar.gz
$ cd muduo/
$ ./build.sh
# 編譯生成的可執(zhí)行文件和靜態(tài)庫(kù)文件分別位于 ../build/debug/{bin,lib}
如果要編譯 release 版,可執(zhí)行
$ BUILD_TYPE=release ./build.sh
# 編譯生成的可執(zhí)行文件和靜態(tài)庫(kù)文件分別位于 ../build/release/{bin,lib}
編譯完成之后請(qǐng)?jiān)囘\(yùn)行其中的例子。比如 bin/inspector_test ,然后通過(guò)瀏覽器訪問 http://10.0.0.10:12345/ 或 http://10.0.0.10:12345/proc/status,其中 10.0.0.10 替換為你的 Linux box 的 IP。
例子
Muduo 附帶了幾十個(gè)小例子,位于 examples 目錄。其中包括從 Boost.Asio、JBoss Netty、Python Twisted 等處移植過(guò)來(lái)的例子。
examples
|-- simple # 簡(jiǎn)單網(wǎng)絡(luò)協(xié)議的實(shí)現(xiàn)
| |-- allinone # 在一個(gè)程序里同時(shí)實(shí)現(xiàn)下面 5 個(gè)協(xié)議
| |-- chargen # RFC 864,可測(cè)試帶寬
| |-- daytime # RFC 867
| |-- discard # RFC 863
| |-- echo # RFC 862
| |-- time # RFC 868
| `-- timeclient # time 協(xié)議的客戶端
|-- hub # 一個(gè)簡(jiǎn)單的 pub/sub/hub 服務(wù),演示應(yīng)用級(jí)的廣播
|-- roundtrip # 測(cè)試兩臺(tái)機(jī)器的網(wǎng)絡(luò)延時(shí)與時(shí)間差
|-- asio # 從 Boost.Asio 移植的例子
| |-- chat # 聊天服務(wù)
| `-- tutorial # 一系列 timers
|-- netty # 從 JBoss Netty 移植的例子
| |-- discard # 可用于測(cè)試帶寬,服務(wù)器可多線程運(yùn)行
| |-- echo # 可用于測(cè)試帶寬,服務(wù)器可多線程運(yùn)行
| `-- uptime # TCP 長(zhǎng)連接
`-- twisted # 從 Python Twisted 移植的例子
`-- finger # finger01 ~ 07
基本結(jié)構(gòu)
Muduo 的目錄結(jié)構(gòu)如下。
muduo
|-- base # 與網(wǎng)絡(luò)無(wú)關(guān)的基礎(chǔ)代碼,已提前發(fā)布
`-- net # 網(wǎng)絡(luò)庫(kù)
|-- http # 一個(gè)簡(jiǎn)單的可嵌入的 web 服務(wù)器
|-- inspect # 基于以上 web 服務(wù)器的“窺探器”,用于報(bào)告進(jìn)程的狀態(tài)
`-- poller # poll(2) 和 epoll(4) 兩種 IO multiplexing 后端
Muduo 是基于 Reactor 模式的網(wǎng)絡(luò)庫(kù),其核心是個(gè)事件循環(huán) EventLoop,用于響應(yīng)計(jì)時(shí)器和 IO 事件。Muduo 采用基于對(duì)象(object based)而非面向?qū)ο螅╫bject oriented)的設(shè)計(jì)風(fēng)格,其接口多以 boost::function + boost::bind 表達(dá)。
Muduo 的頭文件明確分為客戶可見和客戶不可見兩類。客戶可見的為白底,客戶不可見的為灰底。
這里簡(jiǎn)單介紹各個(gè)頭文件及 class 的作用,詳細(xì)的介紹留給以后的博客。
公開接口
- Buffer 仿 Netty ChannelBuffer 的 buffer class,數(shù)據(jù)的讀寫透過(guò) buffer 進(jìn)行
- InetAddress 封裝 IPv4 地址 (end point),注意,muduo 目前不能解析域名,只認(rèn) IP
- EventLoop 反應(yīng)器 Reactor,用戶可以注冊(cè)計(jì)時(shí)器回調(diào)
- EventLoopThread 啟動(dòng)一個(gè)線程,在其中運(yùn)行 EventLoop::loop()
- TcpConnection 整個(gè)網(wǎng)絡(luò)庫(kù)的核心,封裝一次 TCP 連接
- TcpClient 用于編寫網(wǎng)絡(luò)客戶端,能發(fā)起連接,并且有重試功能
- TcpServer 用于編寫網(wǎng)絡(luò)服務(wù)器,接受客戶的連接
- 在這些類中,TcpConnection 的生命期依靠 shared_ptr 控制(即用戶和庫(kù)共同控制)。Buffer 的生命期由 TcpConnection 控制。其余類的生命期由用戶控制。
- HttpServer 和 Inspector,暴露出一個(gè) http 界面,用于監(jiān)控進(jìn)程的狀態(tài),類似于 Java JMX。這么做的原因是,《程序員修煉之道》第 6 章第 34 條提到“對(duì)于更大、更復(fù)雜的服務(wù)器代碼,提供其操作的內(nèi)部試圖的一種漂亮技術(shù)是使用內(nèi)建的 Web 服務(wù)器”,Jeff Dean 也說(shuō)“(每個(gè) Google 的服務(wù)器進(jìn)程)Export HTML-based status pages for easy diagnosis”。
內(nèi)部實(shí)現(xiàn)
- Channel 是 selectable IO channel,負(fù)責(zé)注冊(cè)與響應(yīng) IO 事件,它不擁有 file descriptor。它是 Acceptor、Connector、EventLoop、TimerQueue、TcpConnection 的成員,生命期由后者控制。
- Socket 封裝一個(gè) file descriptor,并在析構(gòu)時(shí)關(guān)閉 fd。它是 Acceptor、TcpConnection 的成員,生命期由后者控制。EventLoop、TimerQueue 也擁有 fd,但是不封裝為 Socket。
- SocketsOps 封裝各種 sockets 系統(tǒng)調(diào)用。
- EventLoop 封裝事件循環(huán),也是事件分派的中心。它用 eventfd(2) 來(lái)異步喚醒,這有別于傳統(tǒng)的用一對(duì) pipe(2) 的辦法。它用 TimerQueue 作為計(jì)時(shí)器管理,用 Poller 作為 IO Multiplexing。
- Poller 是 PollPoller 和 EPollPoller 的基類,采用“電平觸發(fā)”的語(yǔ)意。它是 EventLoop 的成員,生命期由后者控制。
- PollPoller 和 EPollPoller 封裝 poll(2) 和 epoll(4) 兩種 IO Multiplexing 后端。Poll 的存在價(jià)值是便于調(diào)試,因?yàn)?poll(2) 調(diào)用是上下文無(wú)關(guān)的,用 strace 很容易知道庫(kù)的行為是否正確。
- Connector 用于發(fā)起 TCP 連接,它是 TcpClient 的成員,生命期由后者控制。
- Acceptor 用于接受 TCP 連接,它是 TcpServer 的成員,生命期由后者控制。
- TimerQueue 用 timerfd 實(shí)現(xiàn)定時(shí),這有別于傳統(tǒng)的設(shè)置 poll/epoll_wait 的等待時(shí)長(zhǎng)的辦法。為了簡(jiǎn)單起見,目前用鏈表來(lái)管理 Timer,如果有必要可改為優(yōu)先隊(duì)列,這樣復(fù)雜度可從 O(n) 降為 O(ln n) (某些操作甚至是 O(1))。它是 EventLoop 的成員,生命期由后者控制。
- EventLoopThreadPool 用于創(chuàng)建 IO 線程池,也就是說(shuō)把 TcpConnection 分派到一組運(yùn)行 EventLoop 的線程上。它是 TcpServer 的成員,生命期由后者控制。
線程模型
Muduo 的線程模型符合我主張的 one loop per thread + thread pool 模型。每個(gè)線程最多有一個(gè) EventLoop。每個(gè) TcpConnection 必須歸某個(gè) EventLoop 管理,所有的 IO 會(huì)轉(zhuǎn)移到這個(gè)線程,換句話說(shuō)一個(gè) file descriptor 只能由一個(gè)線程讀寫。TcpConnection 所在的線程由其所屬的 EventLoop 決定,這樣我們可以很方便地把不同的 TCP 連接放到不同的線程去,也可以把一些 TCP 連接放到一個(gè)線程里。TcpConnection 和 EventLoop 是線程安全的,可以跨線程調(diào)用。TcpServer 直接支持多線程,它有兩種模式:
1. 單線程,accept 與 TcpConnection 用同一個(gè)線程做 IO。
2. 多線程,accept 與 EventLoop 在同一個(gè)線程,另外創(chuàng)建一個(gè) EventLoopThreadPool,新到的連接會(huì)按 round-robin 方式分配到線程池中。
結(jié)語(yǔ)
Muduo 是我對(duì)常見網(wǎng)絡(luò)編程任務(wù)的總結(jié),用它我能很容易地編寫多線程的 TCP 服務(wù)器和客戶端。Muduo 是我業(yè)余時(shí)間的作品,代碼估計(jì)還有很多 bug,功能也不完善(例如不支持 signal 處理),待日后慢慢改進(jìn)吧。