摘要:該文介紹了I/O完成端口機制,并提出了基于I/O完成端口機制的高性能服務器設計的一種方法。改文所設計的高性能服務器能夠處理海量套接字連接請求,保證了數據通信的高效性,很好的解決了像網絡游戲服務器、web服務器以及代理服務器這些爆發式客戶端連接的問題。經過壓力測試,該IOCP服務器達到了6500客戶端同時在線的負載標準。
關鍵詞:套接字;I/O完成端口;套接字I/O模型;重疊I/O;服務器
中圖分類號:TP311文獻標識碼:A文章編號:1009-3044(2009)33-9607-04
Implementation and Optimization of High-performance I/O Completion Ports Server
LIN Qiang, LIU Tun-dong, CAI Jian-li, JIANG Hao
(Dept. of Automation, Information Science and Technology College, Xiamen University, Xiamen 361005, China)
Abstract: This paper mainly introduces the I/O completion ports mechanism and brings forward a method to design high-performance server which base on this mechanism. This kind of server has the ability to handling with massive connection request , the efficiency of data communication and deals with the outbreak client connection such as online game server, web server, proxy server and so on. After stress testing, this IOCP server has achieved the load requirements that 6500 client online at the same time.
Key words: socket; I/O completion ports; socket I/O models; overlapped I/O; server
在Winsock網絡通信應用中,通信的兩個進程間相互作用的主要模式是客戶/服務器(C/S)模式,即客戶端向服務器發起連接請求,服務器在收到連接請求后,與客戶端建立相應的連接并收發數據和進行數據處理的服務。當你開發不同類型的軟件時,總是需要進行c/s模型的開發。然而,完成一個完善的C/S模型代碼對于編碼人員來說并不是一件容易的事情。特別是當服務器需要處理大量客戶端連接的時候,利用普通的Winsock模型來進行編碼會顯得相當棘手。
本文所研究的服務器采用了IOCP(I/O Completion Port)技術,該技術可以在不喪失系統整體性能的前提下,有效解決成百上千個客戶端連接的問題。IOCP技術對于“一個客戶端一個線程”的技術瓶頸問題提出了一個有效地解決方案:只需要用到少量的處理線程和異步I/O處理函數。與此同時,基于IOCP技術的服務器會隨著cpu數量的增加而線性的提升系統的整體性能。本文主要闡述了IOCP技術的原理,并進一步探討IOCP的特性、開發技術、難點和為性能的提高而提出若干解決芳案,從而有效地利用系統資源、為高性能服務器的開發提供全面的支持,并已在某系統的服務器的通信的實際應用項目中實現了海量客戶服務需求。
1 IOCP技術原理
IOCP(I/O Completion Port)又稱為I/O完成端口,是Microsoft提供的用于Windows上高效處理各種設備I/O的一種機制,設備可以是文件、命名管道、套接字、串口、并口等。I/O完成端口是基于這樣一個理論基礎:并行運行的線程數目必須有一個上限,這是因為一旦運行的線程數目超過cpu的數目,系統就必須花費時間來進行線程上下文的切換,這樣會浪費cpu周期[1]。如果應用程序在初始化時創建了一個線程池,而且這些線程在應用程序執行期間是空閑的,應用程序的性能就能進一步提高。而完成端口本身就是使用了線程池技術。完成端口模型的原理圖如圖1所示。
完成端口要求創建一個win32完成端口對象來對重疊I/O請求進行管理,并通過創建一定量的工作線程來為已經完成的重疊I/O請求提供服務。我們可以把完成端口看成系統維護的一個隊列,由于是“操作完成”的事件通知,故取名為“完成端口”[2]。
1.1 套接字I/O模型
共有五種類型的套接字I/O模型,可以讓Winsock應用程序對I/O進行管理,包括:Select(選擇)模型、WSAAsyncSelect(異步選擇)模型、WSAEventSelect(事件選擇)模型、Overlapped(重疊)模型以及Completion Port(完成端口)模型。這里主要介紹Completion Port模型,其它模型本文不再贅述。
Completion Port模型是迄今為止最為復雜的一種I/O模型。從本質上說,完成端口模型要求我們創建一個Win32完成端口對象,通過指定數量的線程,對重疊I/O請求進行管理,以便為已經完成的重疊I/O請求提供服務。當你需要處理海量套接字連接,并且希望系統的系能隨著cpu數量的增加而線性提升時,完成端口模型是你的最佳選擇。
1.2 重疊I/O
完成端口的設計原理是讓應用程序使用重疊的數據結構,一次投遞一個或多個I/O請求,當這些請求完成后,應用程序可以為他們提供服務。這就要求我們在使用完成端口時必須要使用重疊I/O。重疊I/O,即當I/O功能調用時,不論I/O是否完成,函數馬上返回,由操作系統底層處理I/O的實際工作,而應用程序(進程)可以繼續做其它事情。因而,完成端口是處理完成重疊I/O的一種高效的機制。
1.3 完成端口
1.3.1 I/O完成通知
完成端口維護一個完成通知隊列。重疊I/O操作完成后系統會把已完成的重疊I/O請求通知放入該隊列隊尾。
1.3.2 工作線程
成功創建一個完成端口后,便可以開始將套接字句柄與完成端口對象關聯到一起。但在關聯套接字之前,首先必須創建一個或多個工作線程,以便在I/O請求投遞給完成端口對象后,為完成端口提供服務[3]。工作線程的個數取決于應用程序的總體設計情況。創建的工作線程由完成端口管理。當有I/O完成通知到來,則由完成端口喚醒一個工作線程接收I/O完成通知,并對其進行處理。完成端口自動對工作線程進行調度,喚醒哪個工作線程則由完成端口決定。若無I/O完成通知,則所有的工作線程都在等待。
需要注意的是,完成端口對工作線程的管理具有一定的原則:首先在創建完成端口時要指定最大并發線程數,一般情況是一個處理器對應一個線程,而工作線程的數量大于或等于最大并發線程數。考慮到線程會進入掛起的狀態,為了讓應有程序有足夠的工作線程為I/O請求服務,一般創建工作線程的個數為cpu個數的兩倍。
2 IOCP服務器難點及其實現
由于IOCP是以Windows NT為基礎的操作系統推出的內核高級處理機制,并利用該機制對Winsock的通信進行管理,所以IOCP機制的實現與Microsoft系列的技術聯系甚為緊密。筆者將在接下來的篇幅中剖析IOCP機制基于C#語言的實現方法,以及針對在實現IOCP服務器過程中可能會遇到的一些難點進行分析。
2.1 服務器實現
IOCP服務器的流程如圖2所示。
I/O完成端口的主線程處理流程和代碼大致如下:
1) 利用CreateIoCompletionPort函數創建一個完成端口。其中CreateIoCompletionPort函數的第四個參數設為0。
IntPtr CompletionPort=CreateIoCompletionPort(
INVALID_HANDLE_VALUE,
IntPtr.Zero,
IntPtr.Zero,
0);
2) 判斷系統內安裝的處理器個數。
3) 創建工作線程,根據步驟2)得到的處理器信息,在完成端口上為已完成的I/O請求提供服務。
for(int i=0;i { Thread thread=new Thread(ThreadProc); thread.Start(CompletionPort); } 4) 準備好一個監聽套接字,并在制定端口上監聽進入的連接請求。 5) 主線程循環調用accept函數等待客戶端的連接請求。 6) 創建一個數據結構,用于容納“單句柄數據”,同時在結構中存入接受的套接字句柄。 class PerHandleData { public SafeSocketHandle Socket; } …… PerHandleData perHandleData=new PerHandleData(); GCHandle gch_PerHandleData = GCHandle.Alloc(PerHandleData); PerHandleData.socket=Accept; 7) 調用CreateIoCompletionPort函數,將自accept函數返回的新套接字句柄同完成端口關聯到一起。通過CreateIoCompletionPort函數的完成鍵(CompletionKey)參數,將單句柄數據結構傳遞給CreateIoCompletionPort函數。 IntPtr IOCP=CreateIoCompletionPort( Accept.DangerousGetHandle(), CompletionPort, GCHandle.ToIntPtr(gch_PerHandleData), processorCount); 8) 開始在已接受的連接上進行I/O操作。在此,我們希望通過重疊I/O機制,在新建的套接字上投遞一個或多個異步WSARecv或WSASend請求。這些I/O請求完成后,一個工作線程會為I/O請求提供服務,同時繼續處理未來的I/O請求。 重復步驟5)—8),直至服務器停止或中止。 2.2 IOCP服務器設計的技術難點 進行IOCP服務器設計的時經常會遇到一些問題,而其中的一些問題卻不是那么直觀的。這些難點主要包括WSAENOBUGS錯誤問題、數據包重排序問題以及對內存資源的釋放問題這幾個方面。 2.2.1 WSAENOBUGS錯誤問題 每次進行重疊發送或接收操作時,被提交的數據緩沖區都是要被鎖住的。當內存鎖住時,這個緩沖區就不能被換頁到物理內存外。而一個操作系統限制了可以被鎖住的內存大小,當超出了限制范圍,重疊操作就會因為WSAENOBUGS錯誤而失敗[4]。 如果一個服務器在每個連接上進行了許多重疊的receive操作,那么限制會隨著連接數的增長而變化。如果一個服務器能夠預先估計可能產生的最大并發連接數,服務器可以投遞一個使用零緩沖區的receive操作到每一個連接上。因為當提交的操作沒有緩沖區時,那么也就不會存在內存被鎖定的問題。在使用這種辦法后,當你的receive操作事件完成并返回時,該socket底層緩沖區的數據會原封不動的留在其中,而不會被讀取到receive操作的緩沖區中。此時,服務器可以簡單的調用非阻塞式接收將socket緩沖區中的數據全部讀出來,直到返回 WSAEWOULDBLOCK 為止。 這種設計非常適合那些可以犧牲數據吞吐量而換取巨大并發連接數的服務器。當然,也需要意識到如何讓客戶端的行為盡量避免對服務器造成影響。因此,提出以下兩種解決方法: 1) 投遞使用空緩沖區的receive操作,當操作返回后,使用非阻塞式recv函數進行真實數據的讀取。因此在完成端口的每一個連接中需要使用一個循環的操作來不斷提交空緩沖區的receive操作。 2) 在投遞一個普通含有緩沖區的receive操作后,緊接著開始循環投遞一個空緩沖區的receive操作。這樣保證他們按照投遞順序依次返回,這樣我們就總能對被鎖定的內存進行解鎖。 2.2.2 數據包重排序問題 雖然I/O完成端口的提交操作總是按照它們被提交的順序完成,但線程調度安排可能使綁定到完成端口的實際工作不按指定的順序來處理[5]。例如,假設有兩個IO工作線程,應該要接收到的數據應該是“字塊1,字塊2,字塊3”,但可能接收到的數據順序是“字塊2,字塊1,字塊3”。這就意味著通過I/O完成端口進行數據發送時,數據可能會被以另外的順序進行發送。 一個實際的解決方法是添加一個順序號給buffer類,如果緩沖區順序號正確,則處理緩沖區中的數據。而順序號不正確的緩沖區必須保存下來以便以后用到,因為性能的原因,我們將這些緩沖區保存到一個hash map對象中。 2.2.3 內存資源的釋放問題 在進行IOCP服務器開發時,經常會遇到的一個難題就是與socket相關的緩沖區釋放不當帶來的錯誤,這種錯誤通常是由于多次對同一個指針執行了刪除操作引起的。例如在執行異步接收或者異步發送函數后返回了非IOPENDING的錯誤時,就需要對這種錯誤進行處理。通常情況下,我們會執行下面這兩步操作:1) 釋放此次操作所使用的緩沖區;2) 關閉當前操作所使用的socket資源。然而,我們完全有可能在工作線程中的GetQueuedCompletionStatus函數返回1時也進行了上述兩部相同的操作。此時,系統就會對同一緩沖區進行重復釋放,這就是錯誤產生的原因。針對前述情況,可以通過在clientsock的對象設計機制上使釋放操作歸一化。比如在進行異步發送或者異步接收操作時發生了非IOPENDING錯誤,此時并不釋放資源,而是通過PostQueuedCompletionStatus函數向完成端口拋出一條特殊標志的信息,這個特殊標志的信息可以通過GetQueuedCompletionStatus函數的第二個參數:即傳送字節來表示,我們可以選擇任何一個不可能出現的值,比如說一個跟它的初始值不相等的負數。經過這樣的機制處理后,便可以將各種需要釋放內存資源的情況統一到GetQueuedCompletionStatus函數上來處理。因此,需要執行釋放的邏輯有: 1) GetQueuedCompletionStatus函數返回值為FALSE; 2) 傳送的字節數為0; 3) GetQueuedCompletionStatus函數接收到PostQueuedCompletionStatus函數投遞的特殊標志信息。 3 服務器性能的提升 前面有提到完成端口的最大優點在于它管理海量連接時的處理效率,這里需要注意的是完成端口使用在需要管理的連接量巨大,并且每個連接上收發的數據包比較小的情況下最為合適。因此,我們對完成端口的優化首先應該放在海量連接的相關管理上。為此,我們引入“池”的概念。 在完成端口的設計中。“池”幾乎是必須采用的原則[6]。 “池”包含了多個方面,主要有線程池,內存池,連接池等等。我們知道,在大型在線系統中,數據空間的頻繁創建和釋放時相當占用系統資源的。為此,我們在數據空間的管理上引入內存池。根據完成端口的原理,在每次異步發送或者異步接收操作時,我們都要投遞單IO操作數據。而單IO操作數據空間的創建和釋放,可以有以下幾種方式: 1) 每次執行異步發送和異步接收操作時都聲明一個新的單IO操作數據空間,在完成端口處理完后在工作線程中銷毀; 2) 只有在每出現一個新連接時,我們才隨新連接建立一個新的單IO操作數據空間,將它與新的客戶端socket綁定在一起,只有當客戶端socket關閉時菜將它與客戶端對象一起銷毀; 3) 建立一定量的單IO操作數據空間,并將其統一放入一個空閑隊列,不管何時需要單IO操作數據空間,都首先從空閑隊列中取;如果此時空閑隊列中元素為空,則新建立一個單IO操作數據空間。用完后再將其放回空閑隊列。 從執行效率以及系統整體系能來考慮,采用方法3)來管理這些空閑單IO操作數據空間最為合適。 我們在使用傳統的accept函數接收客戶端的一個連接后,這個函數會返回一個創建成功的客戶端socket。這就是說,每次連接請求到來后,系統才會去創建新的客戶端socket。可以想象當有大量客戶端連接到來時,服務器就必須逐一的為他們創建客戶端socket,這樣勢必會占用比較多的系統資源。幸運的是,Windows給我們提供了一個acceptEx函數,它允許我們在接受連接之前就先創建好客戶端socket,并在接受連接的時候把它和客戶端關聯在一起。我們就可以事先創建適量的socket,并作為鏈表來管理。當有客戶端連接請求時,首先從鏈表中取個空閑的socket使之與客戶端相關聯,當客戶端關閉的時候再把socket插入到鏈表末尾。這樣一來,便可以對客戶端連接進行高效的管理了。 4 結束語 在為安徽某公司設計的造氣爐控制系統服務器的通信模塊中,采用了I/O完成端口技術,經過壓力測試,實現了6500人同時在線的項目需求。 此外,IOCP架構也有以下特點[7]: 1) 模型的普遍性。該模型還支持串行和無線的終端設備。此外,它也可以用于設計相似設備的平臺軟件。 2) 技術的通用性。基于IOCP機制的windows平臺可以被基于Linux平臺的EPOLL機制取代, ADO和ODBC技術同樣可以被JDO和JDBC技術取代。 3) 平臺的獨立性。無論是EPOLL機制,JDO或者JDBC技術還是Web服務技術,都是獨立于平臺的技術,因此這種模型也是獨立于平臺的。 與其它套接字I/O模型相比,完成端口在管理海量并發用戶連接請求方面具有巨大優勢,這種優勢隨著系統CPU數量的增加而愈發明顯。因此,對于開發大量客戶端連接的網絡應用服務器來說,基于完成端口技術的服務器設計是一個很好的解決方案。 參考文獻: [1] 盛利,劉旭.用完成端口管理Windows Socket 應用技術[J].現代計算機,2001(7):39-43. [2] 史美林,向勇,楊光信.計算機支持的協同工作理論與應用[M].北京:電子工業出版社,2000:213. [3] Jim Ohlund,Anthony Jones,and James Ohlund,Network Programming for Microsoft Windows[M].Microsoft Press, Feb 13,2002:172. [4] Gyu-baek Kim, An Effective Processing Server for Various Database Operations of Large-scale On-line Games[C]. IaSTED International Conference on Information and Knowledge Sharing, Arizona, U.S.A, 2003,1:188-192 [5] Leland W E. On the self-similar nature of Ethernet traffic[C].IEEE/ACM Transactions on Networking,1994,2:1-15. [6] 吳永明,何迪.基于完成端口的服務器底層通信模塊設計[J].信息技術,2007(3):115-118. [7] Zhaoge Qi, Wei Shi, and Zhaohui Wu. Software Architecture Design on Large-scale Network Traffic Signal Controllers System[C]. Beijing:Proceedings of the 11th International IEEE Conference on Intelligent Transportation Systems,2008.