1. 程式人生 > >muduo原始碼分析--我對muduo的理解

muduo原始碼分析--我對muduo的理解

分為幾個模組 EventLoop、TcpServer、Acceptor、TcpConnection、Channel等

對於EventLoop來說:

他僅僅關注裡面的主驅動力,EventLoop中僅僅關注poll,這類系統呼叫使得其成為Reactor模式,EventLoop中有屬於這個loop的全部Channel,這個loop屬於哪一個Server.

幾個類存在的意義:

從應用層使用的角度來看。使用者須要初始化一個EventLoop。然後初始化一個TcpServer(當然也能夠自己定義個TcpServer,自己定義資料處理函式須要註冊到TcpServer內),然後呼叫TcpServer的start函式,然後呼叫EventLoop的loop()函式。

整個使用者層的使用流程就是這種。

從使用者層的應用方法來解析Muduo庫的設計思想:

首先來看TcpServer這個類,從名字來看。它是一個server,裡面肯定須要有一個用於監聽某個地址的套接字,這個是Acceptor類,這是由TcpServer引出的第一個類。在Acceptor類中封裝了監聽套接字,Acceptor負責了一個socketfd,這個socketfd就是一個監聽套接字。

當這個套接字上有可讀事件時,呼叫了Acceptor的handleRead函式,此函式的內部就是accept()系統呼叫了。函式返回產生了一個連線套接字,緊接著就是呼叫Acceptor中的回撥函式newConnectionCallback_,那麼這個回撥是誰註冊的呢?肯定是誰擁有Acceptor誰就負責初始化Acceptor中的newConnectionCallback_回撥嘍!那麼就是TcpServer負責註冊!在進行TcpServer初始化時呼叫Acceptor中的setNewConnectionCallback()函式將newConnection賦值給newConnectionCallback_。也就是說,在Acceptor中一旦accept()系統呼叫成功返回就立刻呼叫newConnection函式。

到眼下為止,遺留下了下面幾個問題:

1、  Acceptor中的handleRead()函式是什麼時候被呼叫的!

2、  newConnecion雖說屬於TcpServer,可是newConnection函式的作用是建立了一個類!這個類的作用也是舉足輕重!

接下來介紹下由TcpServer引出的Acceptor類:

首先這個類是屬於內部類。既然這個類是管理監聽套接字的,那麼這個監聽套接字的生命週期就是由Acceptor類來管理。

這個套接字在Acceptor就是Socket。同一時候也有一個EventLoop指標,表明這個Acceptor屬於某一個EventLoop(由於Acceptor依賴於某一個TcpSever,同一時候TcpServe和EventLoop是有依賴關係的)。

同一時候另一個newConnectionCallback_函式。這個函式是在TcpServer初始化的時候被賦值的。Listening_表示當前這個監聽套接字的狀態,idleFd_是一個輸出錯誤的描寫敘述符。這裡另一個新的類—Channel!這個類在整個庫中起著橋接的作用。整個這個類將有些東西單獨提取。是的其它各個類的功能更加單一,關於這個類的介紹不在這裡,畢竟Acceptor類是一個內部類,假設這個一個龐大的類由內部類引出,顯得不夠重視。呵呵!這裡臨時雪藏Channel類。

關於Acceptor類的介面。僅僅有非常少的三個:

當中一個是setNewConnectionCallback,因為Acceptor類屬於TcpServer類,所以呼叫合格函式的肯定是屬於Acceptor的全部者,也就是TcpServer類,這個函式在TcpServer的建構函式中被呼叫,將newConnectionCallback_函式賦值為newConnection。已經說過了,有點囉嗦了!

呵呵。另外一個就是listen()函式。從感覺上來看,這個是使得Acceptor類中的acceptSocket_處於監聽狀態的函式。臨時記住這個函式。尤其是這個函式中的最後一句,這事欠下的有待解決的問題。

有待解決的問題:

1、  在Acceptor中的listen()函式中。屬於Channel類中的enableReading()是幹什麼的?

2、  Acceptor的listen()何時被呼叫!

到此須要記住的幾點:

監聽套接字是單獨的類Acceptor,是脫離TcpServer類存在的一個類!

同一時候TcpServer類中不包括不論什麼一個套接字(不管是監聽套接字還是連線套接字。監聽套接字屬於Acceptor,連線套接字在以下一個類的介紹)。

TcpServer類中是沒有監聽套接字的。可是他負責註冊監聽套接字上接受到一個連線後的響應操作(也就是TcpServer::newConnection。關於這個函式在介紹完EventLoop這個大塊頭再來介紹,不然銜接不上!)

至此我們大概介紹完了由TcpServer引出的第一個類Acceptor!

繼續來看TcpServer類,發現裡面有幾個函式回撥。 connectionCallback_、messageCallback_、writeCompleteCallback_函式,這幾個函式臨時留著以下解釋.在這裡有一個Map型別的變數connection_。既然是一個server,那麼肯定保留著在這個server上的全部連線。這個連線的結合就是connecions_。

跟蹤到最後,這個變數儲存的變數就是TcpConnection,由此也就引出了另外一個重要的類TcpConnecion!事實上TcpServer中並沒有直接託管全部的client連線,map僅僅是保留了指向每個連線的指標,所以全部TcpConnection所屬權並不在TcpServer!

從名字上來看,TcpConneciton類是管理著一個連線到server上的一個連線,不錯,每個TcpConnectin管理著一個連線套接字。這個連線套接字就是Acceptor呼叫accept()系統呼叫後建立個那麼套接字,可是這兩者是怎麼聯絡的呢?到眼下為止還沒見到server監聽,怎麼就開始扯到建立連線這個地步呢?

還記得剛開始muduo庫用法麼?記得TcpServer註冊到Acceptor中的newConnectionCallback_函式麼?

在應用層程式碼中呼叫了TcpServer中的start()函式。這個函式就是的Acceptor處於監聽狀態(注意這裡還遺留了一個問題,既然這裡muudo是一個I/O複用的庫,怎麼沒看到呼叫epoll這類函式就開始監聽了呢?(事實上在Acceptor類中的listen()函式的最後一句就是將監聽套接字放置到epoll管理的檔案描寫敘述符內),事實上是Acceptor中的listen()函式中的最後一句話,下文解釋!

),使得監聽套接字處於監聽狀態以後,就能夠接受外部連結了。那麼接受函式accept()是在Acceptor中的handleRead()函式中呼叫的。那麼這裡就又要遺留一個問題了。handleRead()是在哪裡呼叫的呢?

臨時無論遺留的幾個問題,咱們僅僅知道TcpServer中的start()函式使得管理監聽套接字的Acceptor類中的監聽套接字處於監聽狀態,Acceptor中的handleRead()函式被觸發以後呼叫accept()系統呼叫來接受一個新的連線,同一時候呼叫了TcpServer註冊的回撥函式newConnection,正是這個函式將TcpConneciotn類拉上了舞臺!

分析newConnection印發額一系列操作:

當server中的Acceptor接受到一個連線,就呼叫了這個函式。在這個函式內建立了一個TcpConnection類,並且從threadPoo中選擇一個EveentLoop,將這個新的連線交付給這個EventLoop。(這句話的兩個新詞很重要。正是這個構建了muduo的per reactor per thread的框架。首先從執行緒池內選擇一個EventLoop。將這個連線委託給這個EveentLoop,並且我們知道一個EventLoop就是一個Reactor,這就是所謂的main Reactor和sub Reactor的思想!假設這裡沒有建立threadPool_。那麼我們就僅僅有一個EventLoop。並且這個EventLoop是就是使用者空間定義的那個EventLoop,假設使用者程式碼設定了建立threadPool,也就是建立了多個sub Reactor的話,這裡就能夠選擇一個EventLoop了!)同一時候這個函式還進行了幾個設定。呼叫的函式都是set*系列,那麼這些函式引數都是從哪裡來的呢?非常明顯,newConneciton屬於TcpServer,函式引數自然就是TcpServer的變數嘍,在上面也提到了TcpServer中存在的幾個函式定義(connectionCallback_、messageCallback_、writeCompleteCallback_),那麼這些函式定義是從哪裡來呢?看誰在使用TcpServer,這麼說來就是使用者了。使用者使用了TcpServer,那麼使用者就必須負責給TcpServer中的這個幾個變數進行賦值。這麼一說,從使用者層定義的這幾個函式賦值給了TcpServer,然後在滲透到TcpConnection中!我們假設系統僅僅有一個Reactor,也就是僅僅有一個EventLoop。這newConneciton這個函式中set系列的函式僅僅是賦值,可是最後一行是執行,由於僅僅有一個EventLoop,所以我們覺得那句話就是直接執行TcpConnection::connectEstable函式。(在這個函式中我們好像見到了在Acceptor類中的listen()函式也見到的一個呼叫enableReading(),好熟悉,可是隱約感覺到了它的偉大。)然後就是呼叫connectionCallback_函式,記住這個函式是在使用者層定義通過TcpServer滲透過來的!這麼一來,在這裡使用了使用者層的程式碼。分析了引出TcpConneciton這個類的newConneciton函式,來看看這個類。

回過頭來看,TcpServer引出的Accpetor管理著監聽套接字,解析TcpServer::newConnection函式引出的TcpConnection類管理著連線套接字。而在TcpServer僅僅須要管理著一個Acceptor(如果一個server僅僅管理一個監聽套接字)再管理一個TcpConnection的指標集合集合(Connectionmap)!在TcpConnection類中還是有一個EventLoop指標(眼下為止介紹的三個類都存在了這麼一個定義),在管理套接字的類(Acceptor類和TcpConnection類)中還會另一個Channel。Channel和EventLoop都是重量級的類!

TcpConnection類中沒有什麼特別的東西。僅僅是管理了一個連線套接字和幾個回撥(並且這幾個回撥都是從使用者層傳遞給TcpServer然後再滲透到這裡的),可是裡面有幾個非常有重量的函式,從感覺上來說,連線套接字上可讀、可寫、可關閉、可錯誤處理,還記得Acceptor的接受是在哪個函式中挖成的麼?在Acceptor內的handleRead()函式,在TcpConnection類中有handlRead()、handleWrite()、handleClose()函式,我們非常清楚僅僅要是套接字上,肯定是須要互動的,肯定是有可讀可寫發生的。從上面的分析,我們恍惚感覺到了是管理套接字(監聽&連線)的類的handleRead handleWrite系列函式完畢了套接字上的讀寫操作。那麼這些函式是什麼時候在哪裡被激發的呢?這裡我們須要引入Channel類了,由Accetor和TcpConnection類一起來引入這個Channel類!

也就是說。管理套接字的類中都會有一個Channel類。在之前說過Channel是有一個橋接作用的,那麼它橋接的是什麼呢?(冥冥之中我們應該有一定意識,由於到眼下為止,仍然沒有介紹muduo中的Reactor驅動器。還沒有牽連到I/O複用的操作),在這之前,我們先來看看Channel類的內容!

這個類中的內容很工整,所說Channel不能擁有套接字,可是在建立這個類的時候都傳遞了這個套接字!

既然Acceptor和TcpConnection類中都使用了Channel類,那麼我們就挑選TcpConneciton來分析怎麼使用Channel類的,在TcpConnection的建構函式中,使用了Channe類的set*系列函式進行復制,將TcpConnection中的handleRead hadleWite handleClose handleError(要知道在這些函式中呼叫了從使用者層傳遞給TcpServer而且滲透到TcpConnection中的messageCallback_ writeCompleteCallback_函式)函式賦值給了Channel中的readCallback_writeCallback_ closeCallback_。

同一時候我們也看到了前面提到的感覺非常偉大的enableReading()函式,在Acceptor中的listen()函式中呼叫了Channel中的enableReding()函式,在TcpConnection中的connectEstablished()函式也呼叫了這個函式,那麼connectEstablished什麼時候被呼叫了呢?能不能猜得到,應該在建立一個新的連線的時候吧,也就是TcpServer::newConnection中被呼叫的。

Channel的這個函式是幹什麼用的呢?尤其是最後的那個update()函式。還有和這個函式類似的enableWriteing(),我們跟蹤這個函式,這麼一來,發現呼叫了EventLoop的updateChannel()函式。這麼一來。我們就必須引入EventLoop這個大塊頭了?

在Channel中另一個函式就是handleEvent()函式。先來解釋這個。我們發如今這個函式中最後呼叫了Channel中的readCallback_ writeCallback_ errorCallback_等這些函式,但是這些函式是在哪裡註冊的呢?是擁有Channel的類中的進行註冊的!那麼就是TcpConnection和Acceptor,後者將內部的handleRead handleWrite handleClose(當然這裡但是有從使用者滲透過來的訊息處理函式的)這些函式註冊到Channel中的readCallback_ writeCallback_ errorCallback。這麼一來。我們已經知道訊息處理的函式呼叫是在Channel的handlEvent函式中被呼叫的。當某個套接字上有事件發生時。我們僅僅須要呼叫和這個套接字繫結的Channel類的handleEvent函式就可以!到此為止,我們明確了事件的處理流程,已經使用者的訊息處理是怎樣被傳遞的,如今唯一的關鍵就是Channel中的handleEvent何時被呼叫!

事已至此。我們也不得不引入EventLoop類了,這個類是有Channel的update引入的。我們已經明確EvenLoop就是一個Reactor,就是一個驅動器,我們是不是感覺到Channel是套接字和EvenTLoop之間的橋樑。是連線套接字和驅動器的橋樑。可是我們知道一個Channel中有一個套接字,可是這個Channel不擁有套接字,他是無論理套接字的生命週期的!他們之間僅僅是繫結,套接字的擁有者是Acceptor和TcpConnection。

介紹EventLop:

我們已經才想到這個一個Reactor。那麼它肯定有一個I/O複用,就是一個驅動器。就是變數poller_。那麼poller_須要知道它所要關注的全部套接字,那麼poller_怎麼知道呢,就是通過Channel中的enableReading()呼叫update()函式。呼叫EventLoop的updateChannel來實現的。

因為每一個套接字和一個Channel相關聯。所以EventLoop僅僅須要管理全部須要關注的套接字相關的Channel就可以。所以這裡有一個ChannelList,EventLoop僅僅須要關注有事件的套接字,在Poller_返回後將有事件發生的套接字作為一個集合,activeChannels_就是被啟用的套機字所在的Channel組成的結合!還記得剛開始介紹muudo庫用法的時候介紹的呼叫EvengLoop的loop()函式麼?在這個函式中,首先呼叫I/O複用。等待著有啟用事件的發生,將全部的被啟用的事件存放到activeChannels中。然後呼叫每一個Channel的handleEvent函式(還記得這個函式的威力麼,在這個函式內,來辨別這個套接字上的可讀可寫事件,然後呼叫readCallback_ writeCallback_closeCallback_等一系列的函式,這些函式是Acceptor和TcpConnection中的handleReadhandleWrite handleClose函式,而這些函式中呼叫了使用者層定義的通過TcpServer傳遞滲透到TcpConnection中的訊息處理函式)

走到這裡,事實上我們是為了超找loop->updataChannel這個函式而來的,不覺間已經走偏了!這個函式中呼叫了poller_->updateChannel()函式。到了這裡,我們就不再深究了,我明白的告訴你。這個poller_->updateChannel()函式就是更新了I/O複用的關注的事件集合!

走到這裡。我們已經大概把muduo庫的但Reactor模式的工作流程已經介紹完了!以下再梳理下各個類的作用:

TcpServer:

1、裡面沒有一個套接字,而是由一個管理監聽套接字的類Acceptor來管理,裡面僅僅有這麼一個套接字。

2、它無論理連線套接字,僅僅有一個map管理這指向連線套接字的指標。同一時候這個server須要使用者層的訊息處理函式的註冊(通過TcpServer穿過TcpConnection,然後經過TcpConnection的handleRead handleWrite handleClose等一系列的函式註冊到Channel的readCallbackwriteCallback,而Channel中的handleEvent允許接管Channel的readCallback writeCallback)

2、一旦接受到一個client的連線,就會呼叫TcpServer中的newConnection函式。

3、start()函式使得Acceptor類管理的監聽套接字處於監聽狀態。

Acceptor類:

1、  這個類中管理著一個監聽套接字,在被TcPServer初始化的時候就收了newConnection函式來。後者是建立一個連線套接字屬於的類

2、  Listen被TcpServer中的start函式呼叫,最後的enablereading()使得監聽套接字加入到epoll中。

3、  監聽套接字上可讀,那麼監聽套接字相應的Channel呼叫handleEvent來處理。就呼叫了Acceptor中的handleRead函式,內部使用了TcpServer註冊給他的newConnection來建立一個新的client連線!在newConnection中選擇一個合適的EveentLoop將這個套接字進行託管!

TcpConnection類:

1、表示一個新的連線。定義了Channel須要的handleReadhandleWrite handleClose等函式

屬於一個內部的類,所以對外的介面沒有!

EventLoop:

1、  驅動器。關於被啟用的事件!

成員變數poller_包括著這個驅動器須要關注的全部套接字。這個套接字是怎麼被加入的呢?對於Acceptor來說。在Listen()函式中,呼叫了Channel->enablereading(),然後呼叫了eventLoop的updateChannel函式!

2、  對於連結套接字,在newConnection中的connectEstablished函式中完畢加入!

到這裡為止,我們是在接受單個Reactor的流程。這並不muduo的真意。他的思想是:

有一個main reactor,這個main reactor僅僅管接受新的練級,一旦建立好新的連線,就從EventloopThreadPool中選擇一個合適的EventLoop來託管這個連線套接字。這個EventLoop就是一個sub reactor。

至於這樣的模式的用法和流程,下回分解!

EvenLoop內部的WakeupFd_是供執行緒內部使用的套接字,不是用來通訊的!由於這裡執行緒間也不是必需通訊!

(個人理解)

我認為正是pendingFunctors_和wakeupFd_使得非常多個Reactor處理非常easy。

比方在main  reactor中接收到一個新的連線,那麼就是在Acceptor中的handleRead函式中的accept結束後呼叫了newConnection,在這個函式中從EventLoopThreadPoll中選擇一個EventLoop,讓這個子reactor執行接下來的任務(就是connectionEstablished來將這個連線套接字加入到sub reactor中,那麼就是呼叫了EventLoop的runInLoop函式,此函式最後呼叫了queueInLoop函式,queueInLoop函式將函式加入到pendingFunctors_中,然後直接呼叫wakeup()來喚醒這個執行緒,為啥要喚醒呢?由於一旦喚醒,那麼就是EventLoop中的loop())函式返回,在函式返回以後有一個專門處理pendingFunctors_集合的函式,那麼什麼時候須要喚醒呢?假設呼叫runInLoop函式的執行緒和runInLoop所在的EvenLoop所屬的執行緒不是同一個(要明確TcpSercver中的EventLoopThredPool,使得每個執行緒都擁有一個EventLoop)或前的EventLoop正在處理pendingFunctors_中的函式。

那麼這樣的事情什麼時候發生呢?我們明確TcpServer中肯定擁有一個EventLoop,由於在使用者層定義了一個EventLoop,TcpServer繫結到這個EventLoop上。假設使用者使用了TcpServer中的EventLoopThreadPool,那麼每一個執行緒中包括了一個EventLoop。還記得main Reactor負責接收新的連線吧。TcpServer中的Acceptor呼叫了accept後直接回調了TcpServer中的newConnection,在最後選擇了一個ioLoop作為託管新連線的EventLoop。然後呼叫了ioLoop->runInLoop(),那麼這個時候就須要喚醒了。由於呼叫runInLoop的執行緒和runInloop所線上程不是同一個。那麼將個呼叫(也就是connectEstablished)加入到pendingFunctors_中,然後喚醒本執行緒,使得pendingFunctors_內的connectEstablished能夠被呼叫!