在WinSock上使用IOCP

在WinSock上使用IOCP
本文章假設你已經理解WindowsNT的I/O模型以及I/O完成端口(IOCP),並且比較熟悉將要用到的API,如果你打算學習IOCP,請參考Jeffery Richter的Advanced Windows(第三版),第15章I/O設備,裏面有極好的關於完成端口的討論以及對即將使用API的說明。
IOCP提供了一個用於開發高效率和易擴展程序的模型。Winsock2提供了對IOCP的支持,並在WindowsNT平臺得到了完整的實現。然而IOCP是所有WindowsNT I/O模型中最難理解和實現的,爲了幫助你使用IOCP設計一個更好的Socket服務,本文提供了一些訣竅。

Tip 1:使用Winsock2 IOCP函數例如WSASend和WSARecv,如同Win32文件I/O函數,例如WriteFile和ReadFile。
微軟提供的Socket句柄是一個可安裝文件系統(IFS)句柄,因此你可以使用Win32的文件I/O函數調用這個句柄,然而,將Socket句柄和文件系統聯繫起來,你不得不陷入很多的Kernal/User模式轉換的問題中,例如線程的上下文轉換,花費的代價還包括參數的重新排列導致的性能降低。
因此你應該使用只被Winsock2中IOCP允許的函數來使用IOCP。在ReadFile和WriteFile中會發生的額外的參數重整以及模式轉換隻會發生在一種情況下,那就是如果句柄的提供者並沒有將自己的WSAPROTOCOL_INFO結構中的DwServiceFlags1設置爲XP1_IFS_HANDLES。
註解:即使使用WSASend和WSARecv,這些提供者仍然具有不可避免的額外的模式轉換,當然ReadFile和WriteFile需要更多的轉換。

TIP 2: 確定併發工作線程數量和產生的工作線程總量。
併發工作線程的數量和工作線程的數量並不是同一概念。你可以決定IOCP使用最多2個的併發線程以及包括10個工作線程的線程池。工作線程池擁有的線程多於或者等於併發線程的數量時,工作線程處理隊列中一個封包的時候可以調用win32的Wait函數,這樣可以無延遲的處理隊列中另外的封包。
如果隊列中有正在等待被處理的封包,系統將會喚醒一個工作線程處理他,最後,第一個線程確認正在休眠並且可以被再次調用,此時,可調用線程數量會多於IOCP允許的併發線程數量(例如,NumberOFConcurrentThreads)。然而,當下一個線程調用GetQueueCompletionStatus並且進入等待狀態,系統不會喚醒他。一般來說,系統會試圖保持你設定的併發工作線程數量。
一般來講,每擁有一個CPU,在IOCP中你可以使用一個併發工作線程,要做到這點,當你第一次初始化IOCP的時候,可以在調用CreateIOCompletionPort的時候將NumberOfConcurrentThreads設置爲0。

TIP 3:將一個提交的I/O操作和完成封包的出列聯繫起來。
當對一個封包進行出列,可以調用GetQueuedCompletionStatus返回一個完成Key和一個複合的結構體給I/O。你可以分別的使用這兩個結構體來返回一個句柄和一個I/O操作信息,當你將IOCP提供的句柄信息註冊給Socket,那麼你可以將註冊的Socket句柄當做一個完成Key來使用。爲每一個I/O的"extend"操作提供一個包含你的應用程序IO狀態信息的複合結構體。當然,必須確定你爲每個的I/O提供的是唯一的複合結構體。當I/O完成的時候,會返回一個指向結構體的指針。

TIP 4:I/O完成封包隊列的行爲
IOCP中完成封包隊列的等待次序並不決定於Winsock2 I/O調用產生的順序。如果一個Winsock2的I/O調用返回了SUCCESS或者IO_PENDING,那麼他保證當I/O操作完成後,完成封包會進入IOCP的等待隊列,而不管Socket句柄是否已經關閉。如果你關閉了socket句柄,那麼將來調用WSASend,WSASendTo,WSARecv和WSARecvFrom會失敗並返回一個不同於SUCCES或者IO_PENDING的代碼,這時將不會產生一個完成封包。而在這種情況下,前一次使用GetQueuedCompletionStatus提交的I/O操作所得到的完成封包,會顯示一個失敗的信息。
如果你刪除了IOCP本身,那麼不會有任何I/O請求發送給IOCP,因爲IOCP的句柄已經不可用,儘管系統底層的IOCP核心結構並不會在所有已提交I/O請求完成之前被移除。

TIP5:IOCP的清除
很重要的一件事是使用複合I/O時候的IOCP清除:如果一個I/O操作尚未完成,那麼千萬不要釋放該操作創建的複合結構體。HasOverlappedIoCompleted函數可以幫助你檢查一個I/O操作是否已經完成。
關閉服務一般有兩種情況,第一種你並不關心尚未結束的I/O操作的完成狀態,你只希望儘可能快的關閉他。第二種,你打算關閉服務,但是你需要獲知未結束I/O操作的完成狀態。
第一種情況你可以調用PostQueueCompletionStatus(N次,N等於你的工作線程數量)來提交一個特殊的完成封包,他通知所有的工作線程立即退出,關閉所有socket句柄和他們關聯的複合結構體,然後關閉完成端口(IOCP)。在關閉複合結構體之前使用HasOverlappedIOCompleted檢查他的完成狀態。如果一個socket關閉了,所有基於他的未結束的I/O操作會很快的完成。
在第二種情況,你可以延遲工作線程的退出來保證所有的完成封包可以被適當的出列。你可以首先關閉所有的socket句柄和IOCP。可是,你需要維護一個未完成I/O的數字,以便你的線程可以知道可以安全退出的時間。儘管當隊列中有很多完成封包在等待的時候,活動的工作線程不能立即退出,但是在IOCP服務中使用全局I/O計數器並且使用臨界區保護他的代價並不會象你想象的那樣昂貴。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章