8.2.4 重疊模型
在Wi n s o c k中,相比我們迄今為止解釋過的其他所有I / O模型,重疊I / O(Overlapped I/O)
模型使應(yīng)用程序能達到更佳的系統(tǒng)性能。重疊模型的基本設(shè)計原理便是讓應(yīng)用程序使用一個重疊的數(shù)據(jù)結(jié)構(gòu),一次投遞一個或多個Winsock I/O請求。針對那些提交的請求,在它們完成之后,應(yīng)用程序可為它們提供服務(wù)。該模型適用于除Windows CE之外的各種Wi n d o w s平臺。
模型的總體設(shè)計以Wi n 3 2重疊I / O機制為基礎(chǔ)。那個機制可通過R e a d F i l e和Wr i t e F i l e兩個函數(shù),針對設(shè)備執(zhí)行I / O操作。
最開始的時候,Wi n s o c k重疊I / O模型只能應(yīng)用于Windows NT操作系統(tǒng)上運行的Wi n s o c k1 . 1應(yīng)用程序。作為應(yīng)用程序,它可針對一個套接字句柄,調(diào)用R e a d F i l e以及Wr i t e F i l e,同時指定一個重疊式結(jié)構(gòu)(稍后詳述),從而利用這個模型。自Winsock 2發(fā)布開始,重疊I / O便已集成到新的Wi n s o c k函數(shù)中,比如W S A S e n d和W S A R e c v。這樣一來,重疊I / O模型便能適用于安裝了Winsock 2的所有Wi n d o w s平臺。
注意在Winsock 2發(fā)布之后,重疊I/O仍可在Windows NT和Windows 2000這兩個操作系統(tǒng)中,隨R e a d F i l e和Wr i t e F i l e兩個函數(shù)使用。但是,Windows 95和Windows 98均不具備這一功能。考慮到應(yīng)用程序的跨平臺兼容問題,同時考慮到性能方面的因素,應(yīng)盡量避免使用Wi n 3 2的R e a d F i l e和Wr i t e F i l e函數(shù),分別換以W S A R e c v和W S A S e n d函數(shù)。本節(jié)只打算講述通過新的Winsock 2函數(shù),來使用重疊I/O模型。
要想在一個套接字上使用重疊I / O模型,首先必須使用W S A _ F L A G _ O V E R L A P P E D這個標志,創(chuàng)建一個套接字。如下所示:
? s = WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,WSA_FLAG_OVERLAPPED);
? 創(chuàng)建套接字的時候,假如使用的是s o c k e t函數(shù),而非W S A S o c k e t函數(shù),那么會默認設(shè)置W S A _ F L A G _ O V E R L A P P E D標志。成功建好一個套接字,同時將其與一個本地接口綁定到一起后,便可開始進行重疊I / O 操作,方法是調(diào)用下述的Wi n s o c k 函數(shù),同時指定一個
W S A O V E R L A P P E D結(jié)構(gòu)(可選):
■ W S A S e n d
■ W S A S e n d To
■ W S A R e c v
■ W S A R e c v F r o m
■ W S A I o c t l
■ A c c e p t E x
■ Tr n a s m i t F i l e
大家現(xiàn)在或許已經(jīng)知道,其中每個函數(shù)都與一個套接字上數(shù)據(jù)的發(fā)送、數(shù)據(jù)接收以及連接的接受有關(guān)。因此,這些活動可能會花極少的時間才能完成。這正是每個函數(shù)都可接受一個W S A O V E R L A P P E D結(jié)構(gòu)作為參數(shù)的原因。若隨一個W S A O V E R L A P P E D結(jié)構(gòu)一起調(diào)用這些函數(shù),函數(shù)會立即完成并返回,無論套接字是否設(shè)為鎖定模式(本章開頭已詳細講過了)。它
們依賴于W S A O V E R L A P P E D結(jié)構(gòu)來返回一個I / O請求的返回。
主要有兩個方法可用來管理一個重疊I / O請求的完成:我們的應(yīng)用程序可等待“事件對象通知”,亦可通過“完成例程”,對已經(jīng)完成的請求加以處理。上面列出的函數(shù)( A c c e p t E x除外)還有另一個常用的參數(shù):
l p C o m p l e t i o n R O U T I N E。該參數(shù)指定的是一個可選的指針,指向一個完成例程函數(shù),在重疊請求完成后調(diào)用。接下去,我們將探討事件通知方法。在本章稍后,還會介紹如何使用可選的完成例程,代替事件,對完成的重疊請求加以處理。
1. 事件通知
重疊I / O的事件通知方法要求將Wi n 3 2事件對象與W S A O V E R L A P P E D結(jié)構(gòu)關(guān)聯(lián)在一起。若使用一個W S A O V E R L A P P E D結(jié)構(gòu),發(fā)出像W S A S e n d和W S A R e c v這樣的I / O調(diào)用,它們會立即返回。
通常,大家會發(fā)現(xiàn)這些I / O 調(diào)用會以失敗告終,返回S O C K E T _ E R R O R 。使用W S A G e t L a s t E r r o r函數(shù),便可獲得與錯誤狀態(tài)有關(guān)的一個報告。這個錯誤狀態(tài)意味著I / O操作正在進行。稍后的某個時間,我們的應(yīng)用程序需要等候與W S A O V E R L A P P E D結(jié)構(gòu)對應(yīng)的事件對象,了解一個重疊I / O請示何時完成。W S A O V E R L A P P E D結(jié)構(gòu)在一個重疊I / O請求的初始化,及其后續(xù)的完成之間,提供了一種溝通或通信機制。下面是這個結(jié)構(gòu)的定義:
?typedef struct WSAOVERLAPPED
?{
??DWORD?Internal;
??DWORD InternalHigh;
??DWORD Offset;
??DWORD OffsetHigh;
??WSAEVENT hEvent;
?}WSAOVERLAPPED,FAR *LPWSAOVERLPPED;
?
其中,I n t e r n a l、I n t e r n a l H i g h、O ff s e t和O ff s e t H i g h字段均由系統(tǒng)在內(nèi)部使用,不應(yīng)由應(yīng)用程序直接進行處理或使用。而另一方面, h E v e n t字段有點兒特殊,它允許應(yīng)用程序?qū)⒁粋€事件對象句柄同一個套接字關(guān)聯(lián)起來。大家可能會覺得奇怪,如何將一個事件對象句柄分配給該字段呢?正如我們早先在W S A E v e n t S e l e c t模型中講述的那樣,可用W S A C r e a t e E v e n t函數(shù)來創(chuàng)建一個事件對象句柄。一旦創(chuàng)建好一個事件句柄,簡單地將重疊結(jié)構(gòu)的h E v e n t字段分配給事件句柄,再使用重疊結(jié)構(gòu),調(diào)用一個Wi n s o c k函數(shù)即可,比如W S A S e n d或W S A R e c v。
一個重疊I / O請求最終完成后,我們的應(yīng)用程序要負責取回重疊I / O操作的結(jié)果。一個重疊請求操作最終完成之后,在事件通知方法中, Wi n s o c k會更改與一個W S A O V E R L A P P E D結(jié)構(gòu)對應(yīng)的一個事件對象的事件傳信狀態(tài),將其從“未傳信”變成“已傳信”。由于一個事件對象已分配給W S A O V E R L A P P E D結(jié)構(gòu),所以只需簡單地調(diào)用W S AWa i t F o r M u l t i p l e E v e n t s函數(shù),從而判斷出一個重疊I / O調(diào)用在什么時候完成。該函數(shù)已在我們前面講述WSAEventSelect I/O模型時介紹過了。W S AWa i t F o r M u l t i p l e E v e n t s函數(shù)會等候一段規(guī)定的時間,等待一個或多個事件對象進入“已傳信”狀態(tài)。在此再次提醒大家注意: W S AWa i t F o r M u l t i p l e E v e n t s函數(shù)一次
最多只能等待6 4個事件對象。發(fā)現(xiàn)一次重疊請求完成之后,接著需要調(diào)用W S A G e t O v e r l a p p e dR e s u l t(取得重疊結(jié)構(gòu))函數(shù),判斷那個重疊調(diào)用到底是成功,還是失敗。該函數(shù)的定義如下:
?BOOL WSAGetOverlappedResult(
????????????????SOCKET s,
????????????????LPWSAOVERLAPPED?lpOverlapped,
????????????????LPWORD?lpcbTransfer,
????????????????BOOL??fWait,
????????????????LPWORD?lpdwFlags
???????????????);
其中, s參數(shù)用于指定在重疊操作開始的時候,與之對應(yīng)的那個套接字。l p O v e r l a p p e d參數(shù)是一個指針,對應(yīng)于在重疊操作開始時,指定的那個W S A O V E R L A P P E D 結(jié)構(gòu)。
l p c b Tr a n s f e r參數(shù)也是一個指針,對應(yīng)一個D W O R D(雙字)變量,負責接收一次重疊發(fā)送或接收操作實際傳輸?shù)淖止?jié)數(shù)。f Wa i t參數(shù)用于決定函數(shù)是否應(yīng)該等待一次待決(未決)的重疊操作完成。若將f Wa i t設(shè)為T R U E,那么除非操作完成,否則函數(shù)不會返回;若設(shè)為FA L S E,而且操作仍然處于“待決”狀態(tài),那么W S A G e t O v e r l a p p e d R e s u l t函數(shù)會返回FA L S E值,同時返回一個W S A _ I O _ I N C O M P L E T E(I / O操作未完成)錯誤。但就我們目前的情況來說,由于需要等候重疊操作的一個已傳信事件完成,所以該參數(shù)無論采用什么設(shè)置,都沒有任何效果。
最后一個參數(shù)是l p d w F l a g s,它對應(yīng)于一個指針,指向一個D W O R D(雙字),負責接收結(jié)果標志(假如原先的重疊調(diào)用是用W S A R e c v或W S A R e c v F r o m函數(shù)發(fā)出的)。
如W S A G e t O v e r l a p p e d R e s u l t函數(shù)調(diào)用成功,返回值就是T R U E。這意味著我們的重疊I / O操作已成功完成,而且由l p c b Tr a n s f e r參數(shù)指向的值已進行了更新。若返回值是FA L S E,那么可能是由下述任何一種原因造成的:
■ 重疊I / O操作仍處在“待決”狀態(tài)。
■ 重疊操作已經(jīng)完成,但含有錯誤。
■ 重疊操作的完成狀態(tài)不可判決,因為在提供給W S A G e t O v e r l a p p e d R e s u l t函數(shù)的一個或
多個參數(shù)中,存在著錯誤。
失敗后,由l p c b Tr a n s f e r參數(shù)指向的值不會進行更新,而且我們的應(yīng)用程序應(yīng)調(diào)用W S A G e t L a s t E r r o r函數(shù),調(diào)查到底是何種原因造成了調(diào)用失敗。
在程序清單8 - 7中,我們向大家闡述了如何編制一個簡單的服務(wù)器應(yīng)用,令其在一個套接字上對重疊I / O操作進行管理,程序完全利用了前述的事件通知機制。對該程序采用的編程步驟總結(jié)如下:
1) 創(chuàng)建一個套接字,開始在指定的端口上監(jiān)聽連接請求。
2) 接受一個進入的連接請求。
3) 為接受的套接字新建一個W S A O V E R L A P P E D結(jié)構(gòu),并為該結(jié)構(gòu)分配一個事件對象句柄。
也將事件對象句柄分配給一個事件數(shù)組,以便稍后由W S AWa i t F o r M u l t i p l e E v e n t s函數(shù)使用。
4) 在套接字上投遞一個異步W S A R e c v請求,指定參數(shù)為W S A O V E R L A P P E D結(jié)構(gòu)。
注意函數(shù)通常會以失敗告終,返回S O C K E T _ E R R O R錯誤狀態(tài)W S A _ I O _ P E N D I N G(I/O操作尚未完成)。
5) 使用步驟3 )的事件數(shù)組,調(diào)用W S AWa i t F o r M u l t i p l e E v e n t s函數(shù),并等待與重疊調(diào)用關(guān)聯(lián)在一起的事件進入“已傳信”狀態(tài)(換言之,等待那個事件的“觸發(fā)”)。
6) WSAWa i t F o r M u l t i p l e E v e n t s函數(shù)完成后,針對事件數(shù)組,調(diào)用W S A R e s e t E v e n t(重設(shè)事件)函數(shù),從而重設(shè)事件對象,并對完成的重疊請求進行處理。
7) 使用W S A G e t O v e r l a p p e d R e s u l t函數(shù),判斷重疊調(diào)用的返回狀態(tài)是什么。
8) 在套接字上投遞另一個重疊W S A R e c v請求。
9) 重復步驟5 ) ~ 8 )。
這個例子極易擴展,提供對多個套接字的支持。方法是將代碼的重疊I / O處理部分移至一個獨立的線程內(nèi),讓主應(yīng)用程序線程為附加的連接請求提供服務(wù)。
程序清單8-7 采用事件機制的簡單重疊I / O處理示例