HP-Socket
HP-Socket 是一套通用的高性能 Windows Socket 組件包,包含服務端組件(IOCP 模型)和客戶端組件(Event Select 模型),廣泛適用於 Windows 平台的 TCP 通信系統。HP-Socket 對通信層實現完全封裝,上層應用不必關注通信層的任何細節;HP-Socket 提供基於事件通知模型的 API 接口,能非常簡單高效地整合到各類應用程序中;另外,為了讓大家能更方便的學習 HP-Socket,特此精心制作了一個功能測試示例(Test Echo)和一個性能測試示例(Test Echo-PFM),用戶可以通過這兩個測試示例入手,迅速掌握組件的設計思想和使用方法。
----------------------------------------------------------------
通用性
- 通信組件的唯一職責就是接受和發送字節流,絕對不能參與上層協議解析等工作;
- 與上層使用者解耦、互不依賴,組件與使用者通過操作接口和監聽器接口進行交互,組件實現操作接口為上層提供操作方法;使用者實現監聽器接口把自己注冊為組件的 Listener,接收組件通知。因此,任何使用者只要實現了監聽器接口都可以使用組件;另一方面,甚至可以自己重新寫一個實現方式完全不同的組件實現給使用者調用,只要該組件遵從組件的操作接口,這也是 DIP 設計原則的體現。
可用性
可用性對所有通用組件都是至關重要的,如果太難用還不如自己重頭寫一個來得方便。因此,組件的操作接口和監聽器接口設計得盡量簡單易用(通俗來說就是“傻瓜化”),這兩個接口的主要方法均不超過 5 個。另外,組件完全封裝了所有的底層 Socket 通信,上層應用看不到任何通信細節,不必也不能干預任何通信操作,Socket 連接被抽象為 Connection ID,該參數作為連接標識提供給上層應用識別不同的連接。
高性能
作為底層的通用組件,性能問題是必須考慮的,絕對不能成為系統的瓶頸。而另一方面,從實際出發,根據客戶端組件與服務端組件的性能要求采用不同的 Socket 模型。組件在設計上充分考慮了性能、現實使用情景、可用性和實現復雜性等因素,確保滿足性能要求的同時又不會寫得太復雜。做出以下兩點設計決策:
- 客戶端:在單獨線程中實現 Socket 通信交互。這樣可以避免與主線程或其他線程相互干擾;I/O 模型選擇 Event Select 通信模型。
- 服務端:采用 Windows 平台效率最高的 IOCP 通信模型;利用緩存池技術,在通信的過程中,通常需要頻繁的申請和釋放內存緩沖區,建立了動態緩存池, 只有當緩存池中沒有可用對象時才創建新對象,而當緩存對象過多時則會壓縮緩存池;另外,組件的動態內存通過私有堆(Private Heap)機制分配,避免與 new / malloc 競爭同時又減少內存空洞。
伸縮性
可以根據實際的使用環境要求設置組件的各項性能參數(如:工作線程的數量、各種緩存池的大小、收發緩沖區的大小、Socket 監聽隊列的大小、Accep 派發的數目以及心跳檢查的間隔等)。
*** v2.1.1 更新 ***
> Server:
-----------------
- IServerSocketListener 取消 OnPrepareSocket(connID, socket) 通知方法
- IServerSocketListener 修改 OnAccept((connID, soClient) 通知方法,增加參數‘soClient’,用於實現原 OnPrepareSocket(connID, socket) 通知方法的功能
- IServerSocketListener 增加 OnPrepareListen(soListen) 通知方法,用於設置監聽 socket 的 SOCKET 選項
- ISocketServer 增加方法 GetListenAddress(strAddress, usPort),用於獲取監聽 Socket 的地址信息
- ISocketServer 增加方法 GetClientAddress(connID, strAddress, usPort),用於某個客戶端連接的地址信息
- 優化 Socket 緩存池和內存塊緩存池管理
- 調整一些屬性訪問方法的方法名
- 修復BUG:特殊情形下可能出現死鎖現象
> Client:
-----------------
- ISocketServer 增加方法 GetLocalAddress(strAddress, usPort),用於獲取 Client Socket 的地址信息
- 優化數據發送方式,提升數據發送效率
> 其他更新:
-----------------
- 優化 TestEcho 和 TestEcho-PFM 測試程序
- 在 SocketHelper.h (.cpp) 中添加若干幫助函數
- 為 SocketHelper.h 中定義的所有接口、類和方法加入注釋
*** v2.0.1 更新 ***
> Server:
-----------------
- IServerSocketListener 增加 OnPrepareSocket(connID, socket) 通知方法用於在使用 socket 前設置 SOCKET 選項或過濾客戶端連接
- ISocketServer 增加方法 Disconnect(connID) 用於主動斷開客戶端連接
- 增加 IServerSocketListener 的子類 CServerSocketListener,提供默認(空的)通知處理方法
> Client:
-----------------
- IClientSocketListener 增加 OnPrepareSocket(connID, socket) 通知方法用於在使用 socket 前設置 SOCKET 選項
- 支持異步 Connect:ISocketServer 的 Start() 方法增加一個參數 (BOOL bAsyncConnect) 設置是否采用異步 Connect
- 增加 IClientSocketListener 的子類 CClientSocketListener,提供默認(空的)通知處理方法。
- 修復BUG:超高負載情形下出現丟包現象
> 其他更新:
-----------------
- 支持 Windows x64 平台
- 優化 TestEcho 和 TestEcho-PFM 測試程序
- TestEcho 客戶端程序加入“異步連接”示例
- TestEcho 服務端程序加入“連接過濾”和“主動斷開連接”示例
原文:《高性能 Windows Socket 服務端與客戶端組件(源代碼及測試用例下載)》
自從本座發表了兩篇關於 Windows Socket 通信組件實現的文章后,收到不少讀者的留言,希望能分享完整的源代碼。此時,本座不敢弊帚自珍。特意在此提供服務端組件和客戶端組件的完整代碼。另外,為便於讀者學習和理解,花了一點點時間精心制作了兩個測試用例,一個用於功能測試(TestEcho),另一個用於性能測試(TestEcho-PFM)。讀者可以通過這兩個測試用例入手,迅速掌握組件的使用方法。希望對大家有所幫助,謝謝 ~ ^_^ ~
原文:《基於 IOCP 的通用異步 Windows Socket TCP 高性能服務端組件的設計與實現》
設計概述
服務端通信組件的設計是一項非常嚴謹的工作,其中性能、伸縮性和穩定性是必須考慮的硬性質量指標,若要把組件設計為通用組件提供給多種已知或未知的上層應用使用,則設計的難度更會大大增加,通用性、可用性和靈活性必須考慮在內。
現以一個基於 IOCP 的通用異步 Windows Socket TCP 服務端組件為例子,講述其設計與實現相關的問題,希望能引發大家的思考,對大家日后開展相關類似工作時有所幫助。關於通用性、可用性、Socket 模型選型以及接口模型的設計等問題已經在本座前段時間發表的《通用異步 Windows Socket TCP 客戶端組件的設計與實現》中進行過闡述,此處就不再重復了。現在主要針對服務端通信組件的特點闡述設計其設計和實現相關的問題。
一、線程結構
與組件相關的線程有 3 種:使用者線程、Accept 線程和工作線程,其中后 2 種由組件實現。
-
- 使用者線程:通過調用 Start/Stop/Send 等組件方法操作組件的一個或多個線程,通常是程序的主線程或其它業務邏輯線程。
- Accept 線程:使用 AcceptEx() 接收客戶端連接請求並創建 Client Socket 的線程,將其獨立出來,實現為單獨的線程將使組件的模塊划分更清晰,更重要的是避免與業務邏輯和通信處理的相互影響。
- 工作線程:使用 GetQueuedCompletionStatus() 監聽網絡事件並處理網絡交互的多個線程,工作線程處理完網絡事件后會向上層應用發送 OnAccept/OnSend/OnReceive 等組件通知。工作線程的數量可以根據實際情況之行設置(通常建議為:CPU Core Number * 2 + 2)。
注意:如果上層應用在接收到 OnAccept/OnSend/OnReceive 這些組件通知時直接進行業務邏輯處理並在其中操作組件,則工作線程也成為了使用者線程。另外,如果要處理的業務邏輯比較耗時,上層應用應該在接收到組件通知后交由其他線程處理。
二、性能
組件采用 Windows 平台效率最高的 IOCP Socket 通信模型,因此在通信接口的性能方面是有保證的,這里就不多說了。現在從組件的設計與實現的角度來來闡述性能的優化。組件在代碼級別做了很多優化,一些看似多余或繁瑣的代碼其實都是為了性能服務;組件在設計方面主要采用了 2 中優化策略:緩存池和私有堆。
- 緩存池:在通信的過程中,通常需要頻繁的申請和釋放內存緩沖區(TBufferObj)和 Socket 相關的結構體(TSocketObj),這會大大影響組件的性能,因此,組件為 TBufferObj 和 TSocketObj 建立了動態緩存池, 只有當緩存池中沒有可用對象時才創建新對象,而當緩存對象過多時則會壓縮緩存池。
- 私有堆(Private Heap):在操作系統中,new / malloc 等操作是串行化的,雖然一般的應用程序不用太在乎這個問題,但是在一個高並發的服務器中則是個不可忽略的問題,另外 TBufferObj 和 TSocketObj 均為大小固定的結構體,因此非常適合在私有堆中分配內存,避免與 new / malloc 競爭同時又減少內存空洞。(關於私有堆的使用方法請參考這里 ^_^)
三、通用性與可用性
與《通用異步 Windows Socket TCP 客戶端組件的設計與實現》描述的客戶端接口一樣,服務端組件也提供了兩組接口:ISocketServer 接口提供組件操作方法,由上層應用直接調用;IServerSocketListener 接口提供組件通知方法,由上層應用實現,這兩個接口設計得非常簡單,主要方法均不超過 5 個。由於組件自身功能完備(不需要附帶其它庫或代碼)並且職責單一(只管通信,不參與業務邏輯),因此可以十分方便第整合到任何類型的應用程序中。
四、伸縮性
可以根據實際的使用環境要求設置工作線程的數量、 TBufferObj 和 TSocketObj 緩存池的大小、TBufferObj 緩沖區的大小、Socket 監聽隊列的大小、AccepEx 派發的數目以及心跳檢查的間隔等。
五、連接標識
組件完全封裝了所有的底層 Socket 通信,上層應用看不到任何通信細節,不必也不能干預任何通信操作。另外,組件在 IServerSocketListener 通知接口的所有方法中都有一個 Connection ID 參數,該參數作為連接標識提供給上層應用識別不同的連接。
原文:《通用異步 Windows Socket TCP 客戶端組件的設計與實現》
設計概述
編寫 Windows Socket TCP 客戶端其實並不困難,Windows 提供了6種 I/O 通信模型供大家選擇。但本座看過很多客戶端程序都把 Socket 通信和業務邏輯混在一起,剪不斷理還亂。每個程序都 Copy / Parse 類似的代碼再進行修改,實在有點情何以堪。因此本座利用一些閑暇時光寫了一個基於 IOCP 的通用異步 Windows Socket TCP 高性能服務端組件和一個通用異步 Windows Socket TCP 客戶端組件供各位看官參詳參詳,希望能激發下大家的靈感。本篇文章講述客戶端組件。閑話少說,我們現在步入正題。
- 最重要的第一個問題:如何才能達到通用?
答:很簡單。
1、限制組件的職能,說白了,通信組件的唯一職責就是接受和發送字節流,絕對不能參與上層協議解析等工作。不在其位不謀其政就是這個意思。
2、與上層使用者解耦、互不依賴,組件與使用者通過接口方法進行交互,組件實現 ISocketClient 接口為上層提供操作方法;使用者通過 IClientSocketListener 接口把自己注冊為組件的 Listener,接收組件通知。因此,任何使用者只要實現了 IClientSocketListener 接口都可以使用組件;另一方面,你甚至可以自己重新寫一個實現方式完全不同的組件實現給使用者調用,只要該組件遵從 ISocketClient 接口。這也是 DIP 設計原則的體現(若想了解更多關於設計原則的內容請猛擊這里 ^_^)。
- 最重要的第二個問題:可用性如何,也就是說使用起來是否是否方便?
答:這個問題問得很好,可用性對所有通用組件都是至關重要的,如果太難用還不如自己重頭寫一個來得方便。因此,ISocketClient 和 IClientSocketListener 接口設計得盡量簡單易用(通俗來說就是“傻瓜化”),這兩個接口的主要方法均不超過 5 個。
- 最重要的第三個問題:組件的性能如何?
作為底層的通用組件,性能問題是必須考慮的,絕對不能成為系統的瓶頸。而另一方面,從實際出發,畢竟只是一個客戶端組件,它的並發性要求遠沒有服務端那么高。因此,組件在設計上充分考慮了性能、現實使用情景、可用性和實現復雜性等因素,確保滿足性能要求的同時又不會寫得太復雜。做出以下兩點設計決策:
- 在單獨線程中實現 Socket 通信交互。這樣可以避免與主線程或其他線程相互干擾。
- I/O 模型選擇 WSAEventSelect。細說一下選擇這種 I/O 模型的原因:(各種 I/O 模型的性能比較可以參考:《Windows 網絡編程(中文第二版)》第 154 頁)
- 阻塞模型:(不解析,你懂的^_^)
- 非阻塞模型:(性能太低)
- WSAAsyncSelect: (兩個原因:a、性能太低;b、對於純 Console 程序還要背負 HWND 實在是傷不起呀!)
- 重疊 I/O:(有點復雜了)
- 完成端口:(何必呢?)