完成埠做為windows上最高效的網路程式設計模型,做為眾多伺服器網路層的首選。網上有很多參考資料和示例原始碼,大多存在問題,本文將以開發乙個穩定易用的完成埠元件為目標,詳細討論開發過程中所遇到的細節問題,並給出相應的解決方案。閱讀本文需要你有這方面的開發經驗,對於iocp的工作流程以及上層的應用有清晰的了解。
1。元件需要提供什麼樣的服務
2。執行緒相關的問題
3。記憶體相關的問題
4。怎樣合理的傳送資料
5。怎樣合理的接收資料
6。連線關閉的處理
7。完成鍵的選擇
1。元件需要提供什麼樣的服務
乙個易用的元件,提供的介面並不複雜,只需要向上層提供連線通知,斷開通知,資料收到通知和資料傳送4個介面既可。對於每乙個連線而言,元件需要向上層給出乙個唯一的標識,在這裡我們選用socket作為標識。每乙個事件(連線,資料收發,斷開)通知,都將與該標識關聯。
2。執行緒相關的問題
工作執行緒(get***)按ms的推薦做法,cpu*2吧,檢測投遞請求是否用完的執行緒是必須的,另外我們還需要一類執行緒,就是接收資料通知執行緒,為了提高網路接收資料的效率,網路層在接收到資料後並不立即通知給上層應用(防止上層應用占用過多時間),而是放入乙個快取中,這個執行緒的任務就是從快取中讀取接收到的包,通知給上層應用。執行緒個數可以根據使用者需要指定。
是否需要執行緒池? 首先我們知道執行緒池的作用是降低建立管理執行緒的開銷,適用於頻繁的銷毀建立執行緒,在這裡,我們的執行緒個數基本上是固定的,生存週期是在元件啟動到停止的整個週期,個人認為,執行緒池是沒有必要的。
3。記憶體相關的問題
iocp隨時都有大量的請求,所以記憶體池是必須的,關於記憶體池的極限高效實現比較複雜,本文暫討論簡易一點的記憶體池,就是用標準庫容器(如map)去管理分配的記憶體,並設定閥值,在空閒記憶體超過一定數量的時候,就delete掉它。由於我們需要的記憶體塊大小有好幾種,這個記憶體池需要可以指定塊的大小。
4。怎樣合理的傳送資料
在網路上有不少關於資料只傳送了半包情況的討論,有的說不存在,理由是要麼將整個請求完成,要麼就將請求操作置為失敗;有的說存在。由於tcp緩衝區的大小是有限的,而投遞傳送請求的大小不確定,iocp本來就提供了完成位元組數的引數通知,個人認為,這種傳送出半包的情況理論上是存在的。這就引出乙個問題,我們要對乙個客戶端傳送乙個資料的時候,萬一它的上一次傳送請求還不知道有沒有成功怎麼辦?有兩種解決方案,第一種在傳送的時候檢測上一次傳送是否成功完成,沒有完成則等待,直到檢測到上一次傳送請求成功以後再傳送,第二種是對每乙個連線建立乙個傳送佇列,在傳送的時候,如果這個連線上沒有未決的傳送請求則直接傳送,如果有則放入傳送佇列。工作執行緒收到傳送完成通知時,檢測該連線傳送佇列是否有資料,有就將它取出來繼續傳送。在這裡需要對這個連線的傳送管理進行同步。這種傳送的思想就是在同一時間同一連線確保只有乙個傳送請求處於pending狀態。
5。怎樣合理的接收資料
網上有許多人在討論iocp由於多個工作執行緒導致接收資料亂序的問題,很明顯,如果有多個pending的接收請求存在,肯定會存在這一問題,這需要對資料進行組包,這是由於多執行緒的原因引起的資料報亂序。組包就需要對接收資料區域同步,效率和複雜性???在這裡推薦另一種實現方式,同傳送的思想一樣,就是在同一時間同一連線確保只有乙個接收請求處於pending狀態,我們在乙個連線上收到乙個資料以後,再投遞接收請求,這樣就不存在資料報亂序的問題了。當然,tcp傳送的流式資料,我們需要處理粘包問題,這個比較簡單,我們在接收的時候確保至少接收到乙個完整的package(使用者自定義)才交給接收快取,否則就繼續接收吧。資料報裡面至少要乙個特定的標識以及乙個標識包長度的變數。
也許有人會覺得只投遞乙個請求是不是完全丟掉了iocp的優勢了,其實不是的,只是對單個連線是這樣的,而對於眾多連線,它的請求還是很多的,對於總體效能是沒什麼影響的。既然選擇了完成埠,就是說明你需要對比較多的客戶端提供服務啊。
6。連線關閉的處理
這是乙個麻煩的事情,因為乙個連線總會有那麼點兒事兒吧(傳送,接收。。。),要確保傳送和接收處理過程中,與這個連線相關的資源(per_handle)不能被釋放吧,而工作執行緒又是多個,這裡可以採用引用計數機制進行同步處理。當然有那麼一點點麻煩。在介紹完完成鍵的選擇以後,我們再討論另一種方式。
7。完成鍵的選擇
看了一大部分資料,很多都選擇以per_handle為key,這樣做從表面上看很合理,實際上在資源釋放(連線關閉)處理的時候會帶來麻煩,如果關閉的時候就已經釋放了這個資源,那麼getxx返回的key就成了野指標,不崩潰都不行啊。雖然可以通過一些開發技巧來同步處理這個問題,但是問題就變得複雜了一些了。在這裡,我們推薦以socket做為完成鍵,每檢測到一次完成通知的時候,就去連線容器裡面查詢它的per_handle資料是否存在,這樣就不會出現野指標了。
回到上乙個連線關閉問題的處理,在這裡我們選擇了socket做為key,野指標的問題我們解決了,接下來我們需要解決釋放這個per_handle的問題,前面說過可以用引用計數解決這個問題。在這裡推薦另外一種方式,tr1庫裡面的shared_ptr,乙個執行緒安全且可以自定義刪除器的智慧型指標,其實它內部也是乙個引用計數,只是有現成的豈不是更爽?用shared_ptr管理這個per_handle,當沒有任何東西引用這個handle 的時候,它會自動釋放,這樣在關閉處理的時候,只需要將它從連線容器裡面清除掉,這時它的引用計數就減1了,如果沒有別的地方在引用它,這個時候它就該執行釋放操作了。如果有別的地方在引用,那麼在引用結束以後會自動執行刪除操作,這樣寫出來,程式不僅簡化,而且條理清晰,更重要的是,穩定啊!
今天能想到的就寫到這裡了,改天想到了別的,就繼續寫,以上只是個人總結思考出的一些解決方案,有不完善的地方還請大家多多指教。期待一起討論啊!!!!本人已經實現了乙個這樣的iocp元件,基本上的難點就是上面所講的,如果大家有興趣,就把原始碼發出來和大家共同進步。
設計,成本與開發細節的討論
這兩天經歷了不少事情,總結起來,讓我有了新的觀念 前天去參加乙個軟體招標會,軟體本身價值17w,附帶的測試手機那個公司賣5w,在中關村1k就拿下了。當時我那個吃驚啊,軟體是國產的,17w僅僅是零售版,而不是源 他賣乙份副本就是17w的利潤!這兩天很關心蘋果的macbook air,驚異於它的輕薄,仔...
設計,成本與開發細節的討論
這兩天經歷了不少事情,總結起來,讓我有了新的觀念 前天去參加乙個軟體招標會,軟體本身價值17w,附帶的測試手機那個公司賣5w,在中關村1k就拿下了。當時我那個吃驚啊,軟體是國產的,17w僅僅是零售版,而不是源 他賣乙份副本就是17w的利潤!這兩天很關心蘋果的macbook air,驚異於它的輕薄,仔...
IOCP 完成埠 開發手記 3
當建立iocp埠後,就要初始化連線監聽,這跟一般的socket是沒有什麼區別的,當然要把它關聯到iocp,否則就不會從iocp那裡得響應.接著就會建立滿足需要的接收請求,這樣就會收到連線進來.如果有連線進來,就會收在getqueued pletionstatus函式裡收到前面發出的請求包,接著就進行...