歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> 關於Linux >> muduo源碼分析--我對muduo的理解

muduo源碼分析--我對muduo的理解

日期:2017/3/1 12:10:33   编辑:關於Linux
分為幾個模塊 EventLoop、TcpServer、Acceptor、TcpConnection、Channel等 對於EventLoop來說: 他只關注裡面的主驅動力,EventLoop中只關注poll,這類系統調用使得其成為Reactor模式,EventLoop中有屬於這個loop的所有Channel,這個loop屬於哪一個Server. 幾個類存在的意義: 從應用層使用的角度來看,用戶需要初始化一個EventLoop,然後初始化一個TcpServer(當然也可以自定義個TcpServer,自定義數據處理函數需要注冊到TcpServer內),然後調用TcpServer的start函數,然後調用EventLoop的loop()函數。整個用戶層的使用流程就是這樣的! 從用戶層的應用方法來解析Muduo庫的設計思想: 首先來看TcpServer這個類,從名字來看,它是一個服務器,裡面肯定需要有一個用於監聽某個地址的套接字,這個是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_,既然是一個服務器,那麼肯定保留著在這個服務器上的所有連接,這個連接的結合就是connecions_。跟蹤到最後,這個變量保存的變量就是TcpConnection,由此也就引出了另外一個重要的類TcpConnecion!其實TcpServer中並沒有直接托管所有的客戶端連接,map只是保留了指向每一個連接的指針,所以所有TcpConnection所屬權並不在TcpServer! 從名字上來看,TcpConneciton類是管理著一個連接到服務器上的一個連接,不錯,每一個TcpConnectin管理著一個連接套接字,這個連接套接字就是Acceptor調用accept()系統調用後創建個那麼套接字,但是這兩者是怎麼聯系的呢?到目前為止還沒見到服務器監聽,怎麼就開始扯到創建連接這個地步呢? 還記得剛開始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印發額一系列操作: 當服務器中的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(假設一個服務器只管理一個監聽套接字)再管理一個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管理這指向連接套接字的指針,同時這個服務器需要用戶層的消息處理函數的注冊(通過TcpServer穿過TcpConnection,然後經過TcpConnection的handleRead handleWrite handleClose等一系列的函數注冊到Channel的readCallbackwriteCallback,而Channel中的handleEvent同意接管Channel的readCallback writeCallback) 2、一旦接受到一個客戶端的連接,就會調用TcpServer中的newConnection函數。 3、start()函數使得Acceptor類管理的監聽套接字處於監聽狀態。 Acceptor類: 1、 這個類中管理著一個監聽套接字,在被TcPServer初始化的時候就收了newConnection函數來,後者是創建一個連接套接字屬於的類 2、 Listen被TcpServer中的start函數調用,最後的enablereading()使得監聽套接字添加到epoll中。 3、 監聽套接字上可讀,那麼監聽套接字對應的Channel調用handleEvent來處理,就調用了Acceptor中的handleRead函數,內部使用了TcpServer注冊給他的newConnection來創建一個新的客戶端連接!在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處理很簡單。 比如在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可以被調用!
Copyright © Linux教程網 All Rights Reserved