muduo庫整體架構簡析
muduo是一個高質量的Reactor網路庫,採用one loop per thread + thread pool架構實現,程式碼簡潔,邏輯清晰,是學習網路程式設計的很好的典範。
muduo的程式碼分為兩部分,base和net,base部分實現一些基礎功能,例如log, thread, threadpool, mutex, queue 等,這些基礎模組在後面網路庫中很多地方都可以複用, base庫的類相互之間耦合性較低,原始碼閱讀起來並不困難,此處不做過多探究。
net部分使用base中的工具類實現更高層次的邏輯,網路程式設計無非是對socket和其使用的epoll/poll等進行封裝,使其便於使用,遮蔽掉底層網路庫的一些 "坑", 在滿足了基礎的網路IO之後,就需要考慮高效能,高併發的問題,muduo 的是由poll/epoll 這些非同步IO構成,但是單個IO執行緒在面對大量請求時難免處理不過來,所以就需要結合多執行緒或者執行緒池,一個執行緒對應一個epoll進行網路IO,這樣就可以充分利用硬體多核系統。從軟硬兩方面綜合提升效能。net部分封裝的較為徹底,對上層提供的介面簡單易用,所以涉及複雜的內部處理,接下來就對其內部實現進行探究。
下面這張圖是陳碩提供的muduo 網路庫的類圖,本次講解主要是圍繞下面這張圖,弄明白這樣就相當於弄明白這個架構了。(圖中灰色的類是內部類,白色的是外部類)
首先是EvenLoop類,他是事件迴圈(反應器 Reactor),每個執行緒只能有一個 EventLoop 實體,它負責 IO 和定時器事件的分派。 它用 TimerQueue 作為計時器管理,用 Poller 作為 IO Multiplexing。TimeQueue底層使用timerfd_*系列函式將定時器轉換為fd新增到事件迴圈中,當時間到達後就會自動觸發事件, 其內部使用 set 管理一些註冊好的Timer,由於set有自動排序功能,所以註冊到事件迴圈的總是第一個需要處理的Timer。Poller是IO mutiplexing的實現,它是一個抽象類,具體實現由其子類PollPoller
muduo中通過Channel對fd 進行封裝,其實更合適的說法是對fd事件相關方法的封裝,例如負責註冊fd的可讀或可寫事件到EvenLoop,又如fd產生事件後要如何響應。 一個fd對應一個channel, 它們是聚合關係,Channel在解構函式中並不會close掉這個fd。 它有一個handleEvent方法,當該fd有事件產生時EvenLoop會呼叫handleEvent方法進行處理,在handleEvent內部根據可讀或可寫事件呼叫不同的回撥函式(回撥函式可事先註冊)。 它一般做為其他類的成員,例如EvenLoop通過一個vector<Channel*> 對註冊到其內的眾多fd的管理,畢竟有了Channel就有了fd及其對應的事件處理方法,所以你會看到上圖中EvenLoop與Channel是一對多的關係。
Socket也是對fd的封裝,但不同與channel, 它僅封裝 ::socket 產生的fd, 並且提供的方法也是一些獲取或設定網路連線屬性的方法,他和 fd 是組合關係,當Socke析構時會close掉這個fd。不管如何封裝fd, 一些系統函式傳遞的引數總是fd,所以你會看到上圖中一些類中既有 fd 又有Channel或Socket, 這也是在所難免的。
TcpConection是對一個連線的抽象,一個TcpConnection包含一個Socket和一個Channel, 上面說到channel::handleEvent會在產生事件後呼叫事先註冊的回撥函式,其實在TcpConnection構造的時候就會為其所屬的Channel註冊好這些回撥函式,handleRead,handleWrite....分別對應可讀可寫事件產生後呼叫的回撥函式。事件產生後會呼叫handleRead(或handleWrite), TcpConceton會在handleRead中做一些處理,然後轉交給上層,提交到上層的具體體現就是呼叫上層註冊的回撥函式(又是一樣的套路