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