DIOCP 运作核心探密
來自網(wǎng)友天地弦的DIOCP早已經(jīng)廣為人知了,有很多的同學(xué)都用上了它,甚至各種變異、修改版本也出了不少。我最近也在學(xué)習(xí)DIOCP,打算將它用于自己的服務(wù)端,今天讓我們來一起探密它(DIOCP)的運作核心吧。
?
DIOCP作為對Windows的IOCP完成端口封裝,擁有了很高的性能,經(jīng)過對ECHO示例的測試,它能輕松應(yīng)對幾萬連接和并發(fā)。網(wǎng)絡(luò)通訊一般分為6大階段:請求連接、接受連接、接收數(shù)據(jù)、處理數(shù)據(jù)、回復(fù)數(shù)據(jù)、斷開連接,下面我就從這6大階段入手,來看看DIOCP是如何實現(xiàn)的。
?
一、 請求連接
?
實際上這第一階段由客戶端發(fā)起,指定HOST和Port請求連接我們的DIOCP服務(wù)。
?
二、 接受連接
?
在第一階段,客戶端請求連接后,我們的DIOCP服務(wù)會收到一個連接請求,這時默認會接受連接。在iocp.Sockets中,可以看到我們的服務(wù)類TIocpCustomTcpServer,它繼承自TIocpCustom,就是TIocpCustomTcpServer完成了這整個網(wǎng)絡(luò)通訊的各種請求的管理。
?
TIocpCustomTcpServer是一個用戶能直接使用的DIOCP服務(wù)端類,在TIocpCustomTcpServer被調(diào)用Open(或Start)方法后,它先是開啟IOCP任務(wù)引擎IocpEngine,初始化監(jiān)聽Socket,綁定監(jiān)聽端口,開始監(jiān)聽并將Socket綁定到IOCP句柄。接下來它會初始化指定數(shù)量的請求接受對象,然后再調(diào)用TiocpAcceptExRequest.PostRequest(內(nèi)部調(diào)用IocpAcceptEx),像望夫石一樣的守候著監(jiān)聽端口,等著客戶端的連接。有人可能會問了,任務(wù)引擎怎么知道任務(wù)是什么,讓誰來處理?好吧,我們可以看看TiocpRequest,它內(nèi)部有一個Foverlapped,在Create時,Foverlapped.iocpRequest被設(shè)定為Self, TiocpAcceptExRequest是繼承自TiocpRequest的。在PostRequest方法中調(diào)用AcceptEx時,有一個參數(shù)就是@FOverlapped,在任務(wù)引擎中GetQueuedCompletionStatus函數(shù)會返回Overlapped,這下明白了吧。
?
監(jiān)聽Socket:用于監(jiān)聽客戶連接請求,開啟指定的端口進入偵聽狀態(tài),并調(diào)用任務(wù)引擎中的IocpCore對象Bind自己的Socket句柄到任務(wù)引擎的IOCP句柄(實際就是調(diào)用CreateIoCompletionPort函數(shù)來實現(xiàn)),這樣監(jiān)聽Socket就可以在接收到IO請求時,由內(nèi)核將請求加入IOCP任務(wù)隊列,在IOCP引擎的工作線程中就可以通過GetQueuedCompletionStatus函數(shù)來直接取到任務(wù)進行處理了。請求響應(yīng)、分配工作線程都是由任務(wù)引擎完成的。
?
在監(jiān)聽Socket收到連接請求時,會對Request進行必要的初始化(如狀態(tài)設(shè)置、記錄工作開始時間等),然后調(diào)用Request的FonResponse或HandleResponse。這里會優(yōu)先調(diào)用FonResponse,目的是如果有指定外部的響應(yīng)函數(shù),就完全由外部接管,這樣的封裝增加了整體的靈活性。
?
在TIocpAcceptExRequest.HandleResponse中,會調(diào)用getPeerInfo函數(shù)獲取遠程客戶端的IP地址和當(dāng)前連接通訊端口,再產(chǎn)生一個OnAcceptedEx事件。接著調(diào)用Owner(TiocpCustomTcpServer)的DoAcceptExResponse方法,這時如果設(shè)置了OnContextAccept事件,則會產(chǎn)生此事件,你可以在這個事件中確定是否接受連接,默認會接受連接。接受連接后,根據(jù)KeepAlive開關(guān)判斷是否設(shè)置TCP心跳。再調(diào)用IocpCore.Bind將當(dāng)前連接的SocketHandle綁定到IOCP端口,如果成功會調(diào)用Context的DoConnected方法,在DoConnected里面會為當(dāng)前連接分配一個標(biāo)識句柄(實際上是一個計數(shù)器),設(shè)置Active狀態(tài)為True,添加到在線列表,然后產(chǎn)生OnContextConnected事件,并調(diào)用OnConnected方法(你可以在子類中在這個地方做額外的處理),Context將狀態(tài)設(shè)置為連接成功狀態(tài),并請求接收數(shù)據(jù)。如果在建立連接的過程中發(fā)生了錯誤,會關(guān)閉當(dāng)前連接,產(chǎn)生OnContextError事件。
?
另外,在TiocpCustomTcpServer中內(nèi)設(shè)了一個連接請求管理器IocpAcceptorMgr,它內(nèi)部有一個TIocpAcceptExRequest對象池,目的是為了提升性能。IocpAcceptorMgr還能控制并發(fā)的最大請求數(shù),超過上限時不再立即接受連接,而是等待對像池有空閑的對象時才返回。這里實際上就是一個排隊效果了。
?
三、 接收數(shù)據(jù)
?
在第二階段,連接成功后會馬上調(diào)用當(dāng)前連接的PostWSARecvRequest方法,請求接受數(shù)據(jù)。在TiocpCustomContext中,本身會初始化一個TIocpRecvRequest對象,它的作用就是產(chǎn)生一個數(shù)據(jù)接收請求,并在收到請求時,在HandleResponse方法中進行初步處理。
?
先來看看PostWSARecvRequest,它的實現(xiàn)很簡單,就是直接去調(diào)用TiocpRecvRequest. PostRequest。 PostRequest函數(shù)會調(diào)用系統(tǒng)WSARecv函數(shù)產(chǎn)生一個接收數(shù)據(jù)的請求。這個請求當(dāng)然也是異步的。由于TiocpRecvRequest也是繼承于TiocpRequest,所以在調(diào)用WSARecv時也通過Foverlapped參數(shù)將自己與一次IO事件綁定了,在任務(wù)引擎中接收到數(shù)據(jù)時,會自動進入TiocpRecvRequest的HandleResponse方法。如果PostRequest失敗,會觸發(fā)OnContextError事件。
?
在TiocpRecvRequest. HandleResponse中,如果前面沒有出錯,會調(diào)用DoReceiveData,觸發(fā)Context.OnRecvBuffer虛方法和IocpTcpServer的OnDataReceived事件,在這兩個地方,用戶可以對數(shù)據(jù)進行處理。緊接著,HandleResponse函數(shù)會再次產(chǎn)生一個接收請求,用于接收新的數(shù)據(jù)。(必須的哦,比如1G的文件,顯然不能一個包就發(fā)送完^_^)
?
四、 處理數(shù)據(jù)
?
我們在使用TiocpCustomTcpServer時,可以通過注冊一個被重載OnRecvBuffer的Context類來處理數(shù)據(jù),也可以在OnDataReceived事件中處理。整體來說這個封裝還是很自由的。
?
五、 回復(fù)數(shù)據(jù)
?
在處理數(shù)據(jù)時,我們經(jīng)常需要回復(fù)一些東西給客戶端。以使用OnDataReceived處理數(shù)據(jù)為例,我們先看看這個事件的聲明:
?
在事件中,有當(dāng)前接收的數(shù)據(jù)緩沖區(qū)地址、長度,還有一個Context。我們要回復(fù)數(shù)據(jù)或是查看遠程客戶端的IP端口等信息,就需要用到它。使用Context.Send()函數(shù)就可以發(fā)送我們的數(shù)據(jù)了。Send函數(shù)有幾個重載版本,其中有一個里面包含BufReleaseType參數(shù)的,是指定正要被發(fā)送的數(shù)據(jù)緩沖區(qū)釋放方式,默認dtNone,即不管它。如果使用dtFreeMem和dtDispose,則分別是調(diào)用FreeMem或Dispose來自動釋放緩沖區(qū)內(nèi)存。
?
Send函數(shù)同樣也是異步的,會立即返回。在內(nèi)部實際上是產(chǎn)生一個PostWSASendRequest請求。當(dāng)然你也可以直接調(diào)用PostWSASendRequest請求,Send只是一個簡化使用的封裝。在PostWSASendRequest函數(shù)中,首先通過調(diào)用Owner(TIocpTcpServer).GetSendRequest函數(shù)從FsendRequestPool池中獲取一個請求對象,將數(shù)據(jù)與這個請求對象綁定,即調(diào)用SendRequest. SetBuffer函數(shù)來初始化。在初始化完成后,將這個請求加入待發(fā)送隊列中,成功后立即調(diào)用CheckNextSendRequest函數(shù)一次。
?
為何要調(diào)用CheckNextSendRequest? 其實CheckNextSendRequest是一個觸發(fā)函數(shù),它會從隊列中Pop一個發(fā)送請求,成功后再調(diào)用TiocpSendRequest. ExecuteSend函數(shù),在ExecuteSend里面會再次判斷要發(fā)送的數(shù)據(jù)長度是否為0,然后再通過內(nèi)部的InnerPostRequest來真正產(chǎn)生一個發(fā)送IO請求。這里面是通過WSASend來產(chǎn)生IO請求的,由于TiocpSendRequest也是基于TiocpRequest,所以WSASend時使用的Foverlapped參數(shù)就將對象與本次IO事件綁定了。在系統(tǒng)內(nèi)核發(fā)送完數(shù)據(jù)或出錯時,任務(wù)引擎會自動調(diào)用這個請求的HandleResponse方法。
?
在TIocpSendRequest.HandleResponse中,如果數(shù)據(jù)發(fā)送失敗會產(chǎn)生OnContextError事件,成功則調(diào)用Context的虛方法DoSendRequestCompleted。然后立即調(diào)用Conetext. PostNextSendRequest方法,處理隊列中的下一個發(fā)送請求。從這里可以看出,我們在Send之后調(diào)用CheckNextSendRequest時,可能并不是當(dāng)前投遞的待發(fā)送請求被響應(yīng)。我們Send的請求可能會在前面的請求HandleResponse后才真正發(fā)送。
?
在Send數(shù)據(jù)時,我們用到了隊列,其目的一是保證數(shù)據(jù)的發(fā)送順序,二是能通過設(shè)置隊列的大小來增強系統(tǒng)的穩(wěn)定性,三是我們隨時能觀測到服務(wù)的狀態(tài)。另外我們還使用了發(fā)送請求對象池,用來提升性能。
?
六、斷開連接
?
IOCP服務(wù)在與客戶建立連接后,內(nèi)部只在發(fā)生錯誤或系統(tǒng)退出的時候才主動斷開連接。平常時候默認由客戶端來斷開。在處理數(shù)據(jù)時,我們也可以直接調(diào)用Context. Disconnect來斷開當(dāng)前連接。在調(diào)用Disconnect時,會關(guān)閉當(dāng)前連接的Socket,產(chǎn)生OnContextDisconnected事件,調(diào)用Context. OnDisconnected虛方法,并從在線列表中刪除這個連接。在刪除時Context會被還回到ContextPool中。
?
我們可以在OnContextDisconnected事件,或重載的Context的OnDisconnected方法對斷開連接作額外的處理。
?
需要注意的是,前面的接受連接、接收數(shù)據(jù)、回發(fā)數(shù)據(jù)等都是異步的,只有斷開連接是立即的。
?
七、結(jié)束語
?
至此,本文已經(jīng)差不多結(jié)束了。在上面我們分析了DIOCP整個網(wǎng)絡(luò)通訊的運作流程和基本使用方法。通過這些分析,你會發(fā)現(xiàn),DIOCP到現(xiàn)在的V5階段,整體流程已經(jīng)很合理高效了,至少我暫時沒發(fā)現(xiàn)明顯的沉余。至于優(yōu)化我想的是,在Send拷貝時,可以將GetMem換成環(huán)形內(nèi)存,降低內(nèi)存碎片的產(chǎn)生。
?
另外,本文講的DIOCP為自己修改后的版本,已經(jīng)將原來diocp-v5中的diocp.sockets.pas和diocp.tcp.server、diocp.tcp.client合并,部分類名有些細小的改變。將在線列表、HashTable換成了我自己的TYXDHashMapLinkTable,它是一個將HashTable和雙向鏈表綜合起來的怪物,個人感覺還是比較好用的。
?
本文只是我個人的一些初步理解,必競才學(xué)習(xí)Diocp三天(三天打魚兩天曬網(wǎng) :( ),很可能存在一些錯誤,歡迎大家指正。
?
最后感謝弦弦哥,能將這么好的東西奉獻給大家,真是辛苦了。
有興趣了解更多DIOCP的同學(xué)還可以直接訪問它的官方網(wǎng)站: www.diocp.org
也可以直接加入QQ群: 320641073
?
(此文最初發(fā)表于QDAC官方網(wǎng)站,現(xiàn)在轉(zhuǎn)回家里來)
?
轉(zhuǎn)載于:https://www.cnblogs.com/yangyxd/p/5146798.html
總結(jié)
以上是生活随笔為你收集整理的DIOCP 运作核心探密的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 西瓜视频如何投屏到电视上(高清免费在线视
- 下一篇: 龙族幻想村雨核心怎么出