標籤:
一、綜述
Winsock分別提供了“通訊端模式”和“通訊端I / O模型”,可對一個通訊端上的I/O行為加以控制。其中,通訊端模式用於決定在隨一個通訊端調用時,那些Winsock函數的行為。而另一方面,通訊端模型描述了一個應用程式如何對通訊端上進行的I/O進行管理及處理。
Winsock提供了兩種通訊端模式:鎖定和非鎖定。
Winsock提供五中通訊端模型:這些模型包括select(選擇)、WSAAsyncSelect(非同步選擇)、WSAEventSelect(事件選擇)、Overlapped I/O(重疊式I / O)以及Completion port(完成連接埠)等等。
二、通訊端模式
Windows通訊端在兩種模式下執行I/O操作:鎖定和非鎖定。在鎖定模式下,在I / O操作完成前,執行操作的Winsock函數(比如send和recv)會一直等候下去,不會立即返回程式(將控制權交還給程式)。而在非鎖定模式下, Winsock函數無論如何都會立即返回。
2.1 鎖定模式
在一個鎖定通訊端上調用任意一個Winsock api可能都會好肥長或短的時間等待。 下列的程式碼片段就是一個例子。
假如沒有資料處於“待決”狀態,那麼recv函數可能永遠都無法返回。這是由於從語句可以看出:只有從系統的輸入緩衝區中讀回點什麼東西,才允許返回!
為了防止資料的缺乏,導致資料缺乏的原因多種,比如網路故障,客戶機出問題等。 這時候我們可以將應用程式化為一個讀線程,一個計算線程。兩個線程共用資料緩衝區,通過同步對象來實現,比如一個事件或者Mutex。
對鎖定通訊端來說,它的一個缺點在於:應用程式很難同時通過多個建好串連的通訊端通訊。使用前述的辦法,我們可對應用程式進行修改,令其為連好的每個通訊端都分配一個讀線程,以及一個資料處理線程。儘管這仍然會增大一些開銷,但的確是一種可行的方案。唯一的缺點便是擴充性極差,以後想同時處理大量通訊端時,恐怕難以下手。
2.2 非鎖定模式
下面就展示一個通訊端,並將其設定為非鎖定模式
三、通訊端IO模型
Windows作業系統提供了選擇(Select)、非同步選擇(WSAAsyncSelect)、事件選擇(WSAEventSelect)、重疊I/O(Overlapped I/O)和完成連接埠(Completion Port)共五種I/O模型。每一種模型均適用於一種特定的應用情境。程式員應該對自己的應用需求非常明確,而且綜合考慮到程式的擴充性和可移植性等因素,作出自己的選擇。
3.1 select模型
Select(選擇)模型是Winsock中最常見的I/O模型。之所以稱其為“Select模型”,是由於它的“中心思想”便是利用select函數,實現對I/O的管理。利用select函數,我們判斷通訊端上是否存在資料,或者能否向一個通訊端寫入資料。
select的函數原型如下:
第一個參數經常被忽略,另外三個fd_set參數是,
一個用於檢查可讀性(readfds),一個用於檢查可寫性(writefds),另一個用於例外資料(excepfds)。
其中, readfds集合包括符合下述任何一個條件的通訊端:
■ 有資料可以讀入。
■ 串連已經關閉、重設或中止。
■ 假如已調用了l i s t e n,而且一個串連正在建立,那麼a c c e p t函數調用會成功。
writefds集合包括符合下述任何一個條件的通訊端:
■ 有資料可以發出。
■ 如果已完成了對一個非鎖定串連調用的處理,串連就會成功。
excepfds集合包括符合下述任何一個條件的通訊端:
■ 假如已完成了對一個非鎖定串連調用的處理,串連嘗試就會失敗。
■ 有帶外(OOB)資料可供讀取。
最後一個參數timeout對應的是一個指標,它指向一個timeval結構,用於決定select最多等待I/O操作完成多久的時間.
同時winsock提供下列宏操作,對fd_set進行處理與檢查:
3.2 非同步選擇
Winsock提供了一個有用的非同步I/O模型。利用這個模型,應用程式可在一個通訊端上,接收以Windows訊息為基礎的網路事件通知。具體的做法是在建好一個通訊端後,調用WSAAsyncSelect函數。該模型最早出現於Winsock的1.1版本中,用於輔助應用程式開發人員面向一些早期的16位Windows平台(如Windows for Workgroups),適應其“落後”的多任務訊息環境。應用程式仍可從這種模型中得到好處,特別是它們用一個標準的Windows常式(常稱為"WndProc"),對視窗訊息進行管理的時候。該模型亦得到了Microsoft Foundation Class(微軟基本類,MFC)對象CSocket的採納。(節選自《Windows網路編程》第八章)。
在我看來,WSAAsyncSelect是最簡單的一種Winsock I/O模型(之所以說它簡單是因為一個主線程就搞定了)。使用Raw Windows API寫過視窗類別應用程式的人應該都能看得懂。這裡,我們需要做的僅僅是:
1.在WM_CREATE訊息處理函數中,初始化Windows Socket library,建立監聽通訊端,綁定,監聽,並且調用WSAAsyncSelect函數表示我們關心在監聽通訊端上發生的FD_ACCEPT事件;
2.自訂一個訊息WM_SOCKET,一旦在我們所關心的通訊端(監聽通訊端和用戶端通訊端)上發生了某個事件,系統就會調用WndProc並且message參數被設定為WM_SOCKET;
3.在WM_SOCKET的訊息處理函數中,分別對FD_ACCEPT、FD_READ和FD_CLOSE事件進行處理;
4.在視窗銷毀訊息(WM_DESTROY)的處理函數中,我們關閉監聽通訊端,清除Windows Socket library
下面這張用於WSAAsyncSelect函數的網路事件類型表可以讓你對各個網路事件有更清楚的認識:
3.3 事件選擇
Winsock提供了另一個有用的非同步I/O模型。和WSAAsyncSelect模型類似的是,它也允許應用程式在一個或多個通訊端上,接收以事件為基礎的網路事件通知。對於表1總結的、由WSAAsyncSelect模型採用的網路事件來說,它們均可原封不動地移植到新模型。在用新模型開發的應用程式中,也能接收和處理所有那些事件。該模型最主要的差別在於網路事件會投遞至一個事件物件控點,而非投遞至一個視窗常式。(節選自《Windows網路編程》第八章)
還是讓我們先看代碼然後進行分析:
事件選擇模型也比較簡單,實現起來也不是太複雜,它的基本思想是將每個通訊端都和一個WSAEVENT對象對應起來,並且在關聯的時候指定需要關注的哪些網路事件。一旦在某個通訊端上發生了我們關注的事件(FD_READ和FD_CLOSE),與之相關聯的WSAEVENT對象被Signaled。程式定義了兩個全域數組,一個通訊端數組,一個WSAEVENT對象數組,其大小都是MAXIMUM_WAIT_OBJECTS(64),兩個數組中的元素一一對應。
同樣的,這裡的程式沒有考慮兩個問題,一是不能無條件的調用accept,因為我們支援的並發串連數有限。解決方案是將通訊端按MAXIMUM_WAIT_OBJECTS分組,每MAXIMUM_WAIT_OBJECTS個通訊端一組,每一組分配一個工作者線程;或者採用WSAAccept代替accept,並回調自己定義的Condition Function。第二個問題是沒有對串連數為0的情形做特殊處理,程式在串連數為0的時候CPU佔用率為100%。 3.4 重疊I/O模型
Winsock2的發布使得Socket I/O有了和檔案I/O統一的介面。我們可以通過使用Win32檔案操縱函數ReadFile和WriteFile來進行Socket I/O。伴隨而來的,用於普通檔案I/O的重疊I/O模型和完成連接埠模型對Socket I/O也適用了。這些模型的優點是可以達到更佳的系統效能,但是實現較為複雜,裡面涉及較多的C語言技巧。例如我們在完成連接埠模型中會經常用到所謂的“尾隨資料”。 1.用事件通知方式實現的重疊I/O模型
這個模型與上述其他模型不同的是它使用Winsock2提供的非同步I/O函數WSARecv。在調用WSARecv時,指定一個WSAOVERLAPPED結構,這個調用不是阻塞的,也就是說,它會立刻返回。一旦有資料到達的時候,被指定的WSAOVERLAPPED結構中的hEvent被Signaled。由於下面這個語句
g_CliEventArr[g_iTotalConn] = g_pPerIODataArr[g_iTotalConn]->overlap.hEvent;
使得與該通訊端相關聯的WSAEVENT對象也被Signaled,所以WSAWaitForMultipleEvents的叫用作業成功返回。我們現在應該做的就是用與調用WSARecv相同的WSAOVERLAPPED結構為參數調用WSAGetOverlappedResult,從而得到本次I/O傳送的位元組數等相關資訊。在取得接收的資料後,把資料原封不動的發送到用戶端,然後重新啟用一個WSARecv非同步作業。 2.用完成常式方式實現的重疊I/O模型
用完成常式來實現重疊I/O比用事件通知簡單得多。在這個模型中,主線程只用不停的接受串連即可;輔助線程判斷有沒有新的用戶端串連被建立,如果有,就為那個用戶端通訊端啟用一個非同步WSARecv操作,然後調用SleepEx使線程處於一種可警告的等待狀態,以使得I/O完成後CompletionROUTINE可以被核心調用。如果輔助線程不調用SleepEx,則核心在完成一次I/O操作後,無法調用完成常式(因為完成常式的運行應該和當初啟用WSARecv非同步作業的代碼在同一個線程之內)。
完成常式內的實現代碼比較簡單,它取出接收到的資料,然後將資料原封不動的發送給用戶端,最後重新啟用另一個WSARecv非同步作業。注意,在這裡用到了“尾隨資料”。我們在調用WSARecv的時候,參數lpOverlapped實際上指向一個比它大得多的結構PER_IO_OPERATION_DATA,這個結構除了WSAOVERLAPPED以外,還被我們附加了緩衝區的結構資訊,另外還包括用戶端通訊端等重要的資訊。這樣,在完成常式中通過參數lpOverlapped拿到的不僅僅是WSAOVERLAPPED結構,還有後邊尾隨的包含用戶端通訊端和接收資料緩衝區等重要訊息。這樣的C語言技巧在我後面介紹完成連接埠的時候還會使用到。 3.5.完成連接埠模型
“完成連接埠”模型是迄今為止最為複雜的一種I/O模型。然而,假若一個應用程式同時需要管理為數眾多的通訊端,那麼採用這種模型,往往可以達到最佳的系統效能!但不幸的是,該模型只適用於Windows NT和Windows 2000作業系統。因其設計的複雜性,只有在你的應用程式需要同時管理數百乃至上千個通訊端的時候,而且希望隨著系統內安裝的CPU數量的增多,應用程式的效能也可以線性提升,才應考慮採用“完成連接埠”模型。要記住的一個基本準則是,假如要為Windows NT或Windows 2000開發高效能的伺服器應用,同時希望為大量通訊端I/O請求提供服務(Web伺服器便是這方面的典型例子),那麼I/O完成連接埠模型便是最佳選擇!(節選自《Windows網路編程》第八章)
完成連接埠模型是我最喜愛的一種模型。雖然其實現比較複雜(其實我覺得它的實現比用事件通知實現的重疊I/O簡單多了),但其效率是驚人的。我在T公司的時候曾經幫同事寫過一個郵件伺服器的效能測試程式,用的就是完成連接埠模型。結果表明,完成連接埠模型在多串連(成千上萬)的情況下,僅僅依靠一兩個輔助線程,就可以達到非常高的輸送量。下面我還是從代碼說起:
首先,說說主線程:
1.建立完成連接埠對象
2.建立工作者線程(這裡工作者線程的數量是按照CPU的個數來決定的,這樣可以達到最佳效能)
3.建立監聽通訊端,綁定,監聽,然後程式進入迴圈
4.在迴圈中,我做了以下幾件事情:
(1).接受一個用戶端串連
(2).將該用戶端通訊端與完成連接埠綁定到一起(還是調用CreateIoCompletionPort,但這次的作用不同),注意,按道理來講,此時傳遞給CreateIoCompletionPort的第三個參數應該是一個完成鍵,一般來講,程式都是傳遞一個單控制代碼資料結構的地址,該單控制代碼資料包含了和該用戶端串連有關的資訊,由於我們只關心通訊端控制代碼,所以直接將通訊端控制代碼作為完成鍵傳遞;
(3).觸發一個WSARecv非同步呼叫,這次又用到了“尾隨資料”,使接收資料所用的緩衝區緊跟在WSAOVERLAPPED對象之後,此外,還有操作類型等重要訊息。
在工作者線程的迴圈中,我們
1.調用GetQueuedCompletionStatus取得本次I/O的相關資訊(例如通訊端控制代碼、傳送的位元組數、單I/O資料結構的地址等等)
2.通過單I/O資料結構找到接收資料緩衝區,然後將資料原封不動的發送到用戶端
3.再次觸發一個WSARecv非同步作業 四.五種I/O模型的比較
我會從以下幾個方面來進行比較
*有無每線程64串連數限制
如果在選擇模型中沒有重新定義FD_SETSIZE宏,則每個fd_set預設可以裝下64個SOCKET。同樣的,受MAXIMUM_WAIT_OBJECTS宏的影響,事件選擇、用事件通知實現的重疊I/O都有每線程最大64串連數限制。如果串連數成千上萬,則必須對用戶端通訊端進行分組,這樣,勢必增加程式的複雜度。
相反,非同步選擇、用完成常式實現的重疊I/O和完成連接埠不受此限制。 *線程數
除了非同步選擇以外,其他模型至少需要2個線程。一個主線程和一個輔助線程。同樣的,如果串連數大於64,則選擇模型、事件選擇和用事件通知實現的重疊I/O的線程數還要增加。 *實現的複雜度
我的個人看法是,在實現難度上,非同步選擇<選擇<用完成常式實現的重疊I/O<事件選擇<完成連接埠<用事件通知實現的重疊I/O *效能
由於選擇模型中每次都要重設讀集,在select函數返回後還要針對所有通訊端進行逐一測試,我的感覺是效率比較差;完成連接埠和用完成常式實現的重疊I/O基本上不涉及全域資料,效率應該是最高的,而且在多處理器情形下完成連接埠還要高一些;事件選擇和用事件通知實現的重疊I/O在實現機制上都是採用WSAWaitForMultipleEvents,感覺效率差不多;至於非同步選擇,不好比較。
所以我的結論是:選擇<用事件通知實現的重疊I/O<事件選擇<用完成常式實現的重疊I/O<完成連接埠
參考資料:
1.http://blog.csdn.net/yadong728/article/details/42153579
2.《windows》網路編程技術
Windows IO方法