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能夠被調用!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM