1. 程式人生 > >muduo庫整體架構簡析

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

 (封裝poll), EpollPoller (封裝epoll) 實現,這是muduo庫中唯一一個用面向物件的思想實現的,通過虛擬函式提供回撥功能。Poll中的updateChannel方法用於註冊和更新關注的事件,所有的 fd 都需要呼叫它新增到事件迴圈中。 除了用TiemQueue和Poller管理時間事件和IO事件外,EvenLoop還包含一個任務佇列,它用來做一些計算任務,你可以將自己的任務新增到任務佇列中,EvenLoop在一次事件迴圈中處理完IO事件就會進行依次取出這些任務進行執行,這樣當多個執行緒需要處理同一資源時可以減少鎖的複雜性, 將資源的管理固定地交由一個執行緒來處理,其他執行緒對資源的處理只需要新增到該執行緒的任務佇列中,由該執行緒非同步執行, 如此只需要在任務佇列加鎖即可,其他地方無需上鎖,減少鎖的濫用。 但是有一個問題,如果EvenLoop阻塞在epoll_wait處就無法處理這些計算任務了,畢竟計算任務是在處理完IO事件後才執行的,所以此時需要通過某種通訊方式喚醒該執行緒,被喚醒後就取出佇列中的任務進行執行。muduo採用 eventfd(2) 來非同步喚醒。

  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中做一些處理,然後轉交給上層,提交到上層的具體體現就是呼叫上層註冊的回撥函式(又是一樣的套路