任建華內蒙古體育職業學院,內蒙古呼和浩特 010051
Windows下大容量服務器軟件的開發與設計
任建華
內蒙古體育職業學院,內蒙古呼和浩特 010051
互聯網的普及使得同時訪問一個服務器的用戶越來越多,本文通過詳細對比幾種Winsock I/O方法的優缺點,分析了在服務器開發中如何使用IOCP技術、重疊I/O技術、線程池技術、內存池等技術,并通過性能測試表明此服務器能夠處理海量套接字連接請求,可以使服務器具有極佳的性能和強大的擴展能力,為開發大容量、可伸縮性服務器打下了良好的基礎。
服務器軟件;完成端口;Winsock;多線程
服務器是指網絡中能對其他機器提供某些服務的計算機系統軟件或者設備。服務器作為網絡節點,存儲、處理網絡上80%的數據、信息,因此也被稱為網絡的靈魂。對于用戶來說,一臺計算機所能承受的負荷是多多益善的。微軟公司winsock2中引入了內核級的完成端口(IOCP)模型,IOCP(I/O Completion Ports,I/O完成端口)技術是應用程序使用線程池處理異步I/O請求的一種機制,作為Windows服務平臺上一種比較成熟的I/O處理技術,完成端口模型可以利用為數不多的線程為成千上萬的套接字同時提供網絡服務,隨著UNIX操作系統的廣泛應用,套接字成為當前最流行的網絡通信應用程序接口之一。
完成端口技術是伸縮性最好的一種I/O模型,能隨著系統內安裝的CPU數量的增多而線性的提升服務器的性能。把完成端口模型封裝成一個比較普通的C++類,只要繼承這個類,改寫其中的兩個虛函數(HandleData和DataAction)就可以滿足各種服務器的需要。通過用AcceptEx代替accept和使用LOOKASIDE LIST來管理內存,服務器的性能有了較大的提升。

服務器主要分為2個模塊:主窗口模塊、服務器模塊。主窗口模塊負責初始化和啟動服務器模塊。服務器模塊為整個程序的核心,利用多個I/O工作線程在完成端口上處理來自眾多客戶端的異步I/O請求。
1.2.1 主窗口模塊
主窗口模塊主要用于初始化,同時負責啟動、設置服務器并設置IP地址和端口號。
1.2.2 服務器模塊
此模塊中,CompletionPortModel類是這個服務器的核心。這個類使用了IOCP技術,它可以非常高效的為大量客戶提供服務。CompletionPortModel類有多個I/O工作線程在完成端口上處理異步I/O調用,當網絡事件發生時,這些線程調用類中的虛函數(HandleData和DataAction)來為事件服務。CompletionPortModel類接收到啟動命令后,首先創建監聽線程,再由監聽線程創建I/O工作線程。服務器啟動期間,監聽線程一直運行,為I/O工作線程提供服務。
1 )完成端口類的聲明
2 )開始服務
成員函數Init用于初始化,創建完成端口、創建完成端口處理線程,首先調用類成員函數InitWinsock初始化Winsock、建立一個監聽套接字m_ListenSocket,并將此套接字同完成端口關聯起來,獲取AcceptEx指針。其次再調用BindAndListenSocket()將監聽套接字m_ListenSocket綁定到主窗口模塊輸入的本地IP地址和端口。并置于監聽模式。
3 )內存管理
(1)使用旁視列表管理內存
每當有一個新連接到來的時候,服務器程序都要為該程序創建一個結構體(單句柄數據)用于存儲該客戶端的socket信息,而在每一次的WSASend和WSARecv中,工作線程也要向系統投遞一個重疊類型的結構體(單IO數據)用于數據的收發。為了高效的分配和釋放數據,采用Lookaside List(旁視列表)來管理內存的分配與回收。
(2)內存資源異常處理
應用服務器管理眾多客戶端I/O請求時,數據緩沖區很可能會被鎖定。操作系統對鎖定內存的數量有限制,達到這個限制時,重疊操作就會以WSAENOBUFS錯誤失敗。實際設計中,當服務器處理大量并發客戶端時,可以在每個連接上投遞一個0字節的接收操作,這樣就不會有內存被鎖定。0字節的接收操作完成以后,服務器可以執行一個非阻塞的接收來獲取緩沖區中的所有數據。
4 )連接管理
(1)使用AcceptEx管理客戶端連接
服務器端程序在接受客戶端連接的時候通常調用Accept函數,該函數是一個阻塞函數,當該Accept函數沒有返回時又有新客戶端連接請求已經發過來時,新客戶端就要處于等待。在本程序中,把FD_ACCPET事件和一個Event關聯起來,然后用WaitForSingleObject等待這個Event,當已經發出的AcceptEx調用數目耗盡而又有新的客戶端需要連接時,FD_ACCEPT事件將被觸發,Event變為已傳信狀態,WaitForSingleObject返回,然后調用成員函數PostAccetpEx()重新發出10個AcceptEx調用。
(2)惡意客戶端連接問題
惡意的客戶連接是指客戶端出現壞鏈接,有的終端既不發送數據,也不關閉連接,就會造成AcceptEx投遞的大量重疊操作不能返回,為了滿足其他客戶端請求,服務器不得不再繼續投遞更多的接收I/O,占用了大量的系統資源,為了避免這個事件發生,應用服務器記錄了所有AcceptEx投遞的未決I/O請求,在現場中定時遍歷它們,對每個客戶端套接字以SO_CONNECT_TIME為參數調用getsockop函數,檢查套接字建立的時間,如果某個連接沒有按時收發過數據,則通過使用PostQueuedCompletionStatus()函數強制關閉該套接字發送強制關閉。
5 )數據處理
(1)基本的數據處理
為了測試服務器能否響應大數量的客戶端連接,設計了回應服務器,通過自定義枚舉型數據結構,用來標識套接字的I/O操作類型,對應單IO數據結構中的一個參數,這樣HandleData()和DataAction()即可根據詳細的操作類型來進行下一步的IO操作。當服務器端完成了用WSARecv數據接收完畢時,就調用WSASend把剛收到的數據重新發送給客戶端(即將IO操作標志設為IoWrite);如果上一次服務器端完成了WSASend將數據發送完成時,就將后續的操作標志設為關閉(IoEnd)。
(2)包重新排序問題
使用I/O完成端口的操作會按照它們被提交的順序完成,但是線程調度可能會影響最終結果。本文采用向提交的緩沖區對象中添加序列號的方法來解決上面的問題,如果緩沖區序列號是連續的,就處理緩沖區中的數據,因此有錯誤序列號的緩沖區要被保存下來,以便今后使用。
在本機(Intel Core2 Duo CPU T7500 2.2GHz 2GB內存)上同時運行了服務器和虛擬客戶端。測試時,虛擬客戶端分別啟動了100,、200、300、400、500、600、700、800、900、1000、1100、1200、1300、1400、1500、1600、1700、1800、1900、2000個線程,代表不同數量的客戶端同時訪問服務器。

圖2 服務器測試結果
從圖2可以看出,隨著連接客戶數量的增多,服務器對CPU的占用率并沒有急速增長。而是緩慢增長,這是服務器伸縮性好的一種表現。另外,服務器對內存的消耗量也是穩定的呈線性緩慢增長,說明服務器在內存消耗上是高效的,穩定的。本文給出的設計實例,已在上調試通過,該方法簡單實用,為分析提供了參考資料,有助于進一步研究以為內核的具有豐富硬件資源的操作系統的移植。
[1]Anthony Jones,Windows網絡編程技術[M].北京:機械工業出版社,2000,89-242.
[2]王艷平,張越.Windows網絡與通信程序設計.北京:人民郵電出版社,2006:13-99.
[3]馬金鑫,袁丁.基于IOCP的高并發通信服務器的設計與實現[J].通信技術,2009(7):248-250.
[4]吳星,黃愛萍.用完成端口實現可擴展的服務器應用[期刊論文].計算機科學.2002,29(11):144-164.
TP31
A
1674-6708(2012)59-0129-02