轉(zhuǎn)自http://blog.csdn.net/phunxm/archive/2009/12/27/5085915.aspx
一.重疊I/O模型的概念
當(dāng)調(diào)用ReadFile()和WriteFile()時(shí),如果最后一個(gè)參數(shù)lpOverlapped設(shè)置為NULL,那么線程就阻塞在這里,直到讀寫完指定的數(shù)據(jù)后,它們才返回。這樣在讀寫大文件的時(shí)候,很多時(shí)間都浪費(fèi)在等待ReadFile()和WriteFile()的返回上面。如果ReadFile()和WriteFile()是往管道里讀寫數(shù)據(jù),那么有可能阻塞得更久,導(dǎo)致程序性能下降。
為了解決這個(gè)問題,Windows引進(jìn)了重疊I/O的概念,它能夠同時(shí)以多個(gè)線程處理多個(gè)I/O。其實(shí)你自己開多個(gè)線程也可以處理多個(gè)I/O,但是系統(tǒng)內(nèi)部對(duì)I/O的處理在性能上有很大的優(yōu)化。它是Windows下實(shí)現(xiàn)異步I/O最常用的方式。
Windows為幾乎全部類型的文件提供這個(gè)工具:磁盤文件、通信端口、命名管道和套接字。通常,使用ReadFile()和WriteFile()就可以很好地執(zhí)行重疊I/O。
重疊模型的核心是一個(gè)重疊數(shù)據(jù)結(jié)構(gòu)。若想以重疊方式使用文件,必須用 FILE_FLAG_OVERLAPPED標(biāo)志打開它,例如:
HANDLE hFile = CreateFile(lpFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
如果沒有規(guī)定該標(biāo)志,則針對(duì)這個(gè)文件(句柄),重疊I/O是不可用的。如果設(shè)置了該標(biāo)志,當(dāng)調(diào)用ReadFile()和WriteFile()操作這個(gè)文件(句柄)時(shí),必須為最后一個(gè)參數(shù)提供OVERLAPPED結(jié)構(gòu):
// WINBASE.H
typedef struct _OVERLAPPED{
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
HANDLE hEvent; //關(guān)鍵的一個(gè)參數(shù)
}OVERLAPPED, *LPOVERLAPPED;
頭兩個(gè)32位的結(jié)構(gòu)字Internal和InternalHigh由系統(tǒng)內(nèi)部使用;其次兩個(gè)32位結(jié)構(gòu)字Offset和OffsetHigh使得可以設(shè)置64位的偏移量,該偏移量是要文件中讀或?qū)懙牡胤健?/font>
因?yàn)镮/O異步發(fā)生,就不能確定操作是否按順序完成。因此,這里沒有當(dāng)前位置的概念。對(duì)于文件的操作,總是規(guī)定該偏移量。在數(shù)據(jù)流下(如COM端口或socket),沒有尋找精確偏移量的方法,所以在這些情況中,系統(tǒng)忽略偏移量。這四個(gè)字段不應(yīng)由應(yīng)用程序直接進(jìn)行處理或使用,OVERLAPPED結(jié)構(gòu)的最后一個(gè)參數(shù)是可選的事件句柄,當(dāng)I/O完成時(shí),該事件對(duì)象受信(signaled)。程序通過等待該對(duì)事件對(duì)象受信來做善后處理。
設(shè)置了OVERLAPPED參數(shù)后,ReadFile()/WriteFile()的調(diào)用會(huì)立即返回,這時(shí)候你可以去做其他的事(所謂異步),系統(tǒng)會(huì)自動(dòng)替你完成ReadFile()/WriteFile()相關(guān)的I/O操作。你也可以同時(shí)發(fā)出幾個(gè)ReadFile()/WriteFile()的調(diào)用(所謂重疊)。當(dāng)系統(tǒng)完成I/O操作時(shí),會(huì)將OVERLAPPED.hEvent置信,我們可以通過調(diào)用WaitForSingleObject/WaitForMultipleObjects來等待這個(gè)I/O完成通知,在得到通知信號(hào)后,就可以調(diào)用GetOverlappedResult來查詢I/O操作的結(jié)果,并進(jìn)行相關(guān)處理。由此可以看出,OVERLAPPED結(jié)構(gòu)在一個(gè)重疊I/O請(qǐng)求的初始化及其后續(xù)的完成之間,提供了一種溝通或通信機(jī)制。注意OVERLAPPED結(jié)構(gòu)的生存周期,一般動(dòng)態(tài)分配,待I/O完成后,回收重疊結(jié)構(gòu)。
以Win32重疊I/O機(jī)制為基礎(chǔ),自WinSock 2發(fā)布開始,重疊I/O便已集成到新的WinSock API中,比如WSARecv()/WSASend()。這樣一來,重疊I/O模型便能適用于安裝了WinSock 2的所有Windows平臺(tái)??梢砸淮瓮哆f一個(gè)或多個(gè)WinSock I/O請(qǐng)求。針對(duì)那些提交的請(qǐng)求,在它們完成之后,應(yīng)用程序可為它們提供服務(wù)(對(duì)I/O的數(shù)據(jù)進(jìn)行處理)。
相應(yīng)的,要想在一個(gè)套接字上使用重疊I/O模型來處理網(wǎng)絡(luò)數(shù)據(jù)通信,首先必須使用 WSA_FLAG_OVERLAPPED這個(gè)標(biāo)志來創(chuàng)建一個(gè)套接字。如下所示:
SOCKET s = WSASocket(AF_INET, SOCK_STEAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
創(chuàng)建套接字的時(shí)候,假如使用的是socket()函數(shù),而非WSASocket()函數(shù),那么會(huì)默認(rèn)設(shè)置WSA_FLAG_OVERLAPPED標(biāo)志。成功創(chuàng)建好了一個(gè)套接字,將其與一個(gè)本地接口綁定到一起后,便可開始進(jìn)行這個(gè)套接字上的重疊I/O操作,方法是調(diào)用下述的WinSock 2函數(shù),同時(shí)為它們指定一個(gè)WSAOVERLAPPED結(jié)構(gòu)參數(shù)(#define WSAOVERLAPPED OVERLAPPED// WINSOCK2.H):
(1)WSASend()
(2)WSASendTo()
(3)WSARecv()
(4)WSARecvFrom()
(5)WSAIoctl()
(6)AcceptEx()
(7)TransmitFile()
若隨一個(gè)WSAOVERLAPPED結(jié)構(gòu)一起調(diào)用這些函數(shù),函數(shù)會(huì)立即返回,無論套接字是否設(shè)為鎖定模式。它們依賴于WSAOVERLAPPED結(jié)構(gòu)來返回一個(gè)I/O請(qǐng)求操作的結(jié)果。
比起阻塞、select、WSAAsyncSelect以及WSAEventSelect等模型,WinSock的重疊I/O(Overlapped I/O)模型使應(yīng)用程序能達(dá)到更佳的系統(tǒng)性能。因?yàn)樗瓦@4種模型不同的是,使用重疊模型的應(yīng)用程序通知緩沖區(qū)收發(fā)系統(tǒng)直接使用數(shù)據(jù)。也就是說,如果應(yīng)用程序投遞了一個(gè)10KB大小的緩沖區(qū)來接收數(shù)據(jù),且數(shù)據(jù)已經(jīng)到達(dá)套接字,則該數(shù)據(jù)將直接被拷貝到投遞的緩沖區(qū)。而這4種模型中,數(shù)據(jù)到達(dá)并拷貝到單套接字接收緩沖區(qū)(Per Socket Buffer)中,此時(shí)應(yīng)用程序會(huì)被系統(tǒng)通知可以讀入的字節(jié)數(shù)。當(dāng)應(yīng)用程序調(diào)用接收函數(shù)之后,數(shù)據(jù)才從單套接字緩沖區(qū)拷貝到應(yīng)用程序的緩沖區(qū)。這樣就減少了一次從I/O緩沖區(qū)到應(yīng)用程序緩沖區(qū)的拷貝,差別就在于此。
實(shí)際編程時(shí),可以投遞一個(gè)0字節(jié)緩沖區(qū)的WSARecv/WSASend操作,這樣就沒有用戶緩沖區(qū)與I/O操作相關(guān)聯(lián),避免了用戶緩沖區(qū)的鎖定(過多的鎖定可能導(dǎo)致非分頁(yè)內(nèi)存池耗盡,即WSAENOBUFS),應(yīng)用程序繞開單套接字緩沖區(qū)而直接與TCP Stack進(jìn)行數(shù)據(jù)交互,從而避免了內(nèi)存拷貝。當(dāng)然,只要投遞了足夠多的重疊發(fā)送/接收操作,就能避免額外的內(nèi)存拷貝,這時(shí)將單套接字緩沖區(qū)設(shè)置為0并不能提升性能。因?yàn)閼?yīng)用程序的發(fā)送緩沖區(qū)將始終被鎖定直到可以下傳給TCP,所以停用套接字的發(fā)送緩沖區(qū)對(duì)性能的影響比停用接收緩沖區(qū)小。然而,如果接收緩沖區(qū)被設(shè)置為0,而又未投遞重疊接收操作,則進(jìn)來的數(shù)據(jù)都只能停留在TCP Stack中,而TCP驅(qū)動(dòng)程序的緩沖區(qū)最多只能接收窗口大小。TCP緩沖區(qū)被定位在非分頁(yè)內(nèi)存池中,假如很多連接發(fā)數(shù)據(jù)過來,但我們根本沒有投遞接收操作,則將消耗大量的非分頁(yè)內(nèi)存池。非分頁(yè)內(nèi)存池是一種有限的資源,過多的鎖定可能導(dǎo)致非分頁(yè)內(nèi)存池耗盡,即WSAENOBUFS。
在Windows NT和Windows 2000中,重疊I/O模型也允許應(yīng)用程序以一種重疊方式實(shí)現(xiàn)對(duì)套接字連接的處理。具體的做法是在監(jiān)聽套接字上調(diào)用AcceptEx函數(shù)。AcceptEx是一個(gè)特殊的WinSock擴(kuò)展函數(shù),由mswsock.dll實(shí)現(xiàn),使用時(shí)需包含Mswsock.h頭文件,鏈接Mswsock.lib庫(kù)文件。該函數(shù)最初的設(shè)計(jì)宗旨是在Windows NT與Windows 2000操作系統(tǒng)上使用Win 32的重疊I/O機(jī)制。但事實(shí)上,它也適用于WinSock 2中的重疊I/O。AcceptEx的定義如下:
// MSWSOCK.H
AcceptEx(
IN SOCKET sListenSocket,
IN SOCKET sAcceptSocket,
IN PVOID lpOutputBuffer,
IN DWORD dwReceiveDataLength,
IN DWORD dwLocalAddressLength,
IN DWORD dwRemoteAddressLength,
OUT LPDWORD lpdwBytesReceived,
IN LPOVERLAPPED lpOverlapped);
參數(shù)一sListenSocket參數(shù)指定的是一個(gè)監(jiān)聽套接字。
參數(shù)二sAcceptSocket參數(shù)指定的是另一個(gè)套接字,負(fù)責(zé)對(duì)進(jìn)入連接請(qǐng)求的“接受”。 AcceptEx()函數(shù)和accept()函數(shù)的區(qū)別在于,我們必須提供接受的套接字,而不是讓函數(shù)自動(dòng)為我們創(chuàng)建。正是由于要提供套接字,所以要求我們事先調(diào)用socket()或WSASocket()函數(shù)創(chuàng)建一個(gè)套接字,以便通過sAcceptSocket參數(shù),將其傳遞給AcceptEx()。
參數(shù)三lpOutputBuffer指定的是一個(gè)特殊的緩沖區(qū),因?yàn)樗?fù)責(zé)三種數(shù)據(jù)的接收:服務(wù)器的本地地址,客戶機(jī)的遠(yuǎn)程地址,以及在新建連接上接收的第一個(gè)數(shù)據(jù)塊。存儲(chǔ)順序是:接收到的數(shù)據(jù)塊→本地地址→遠(yuǎn)程地址。
參數(shù)四dwReceiveDataLength以字節(jié)為單位,指定了在lpOutputBuffer緩沖區(qū)開頭保留多大的空間,用于數(shù)據(jù)的接收。如這個(gè)參數(shù)設(shè)為0,那么只接受連接,不伴隨接收數(shù)據(jù)。
參數(shù)五dwLocalAddressLength和參數(shù)六dwRemoteAddressLength也是以字節(jié)為單位,指定在lpOutputBuffer緩沖區(qū)中,保留多大的空間,在一個(gè)套接字被接受的時(shí)候,用于本地和遠(yuǎn)程地址信息的保存。要注意的是,和當(dāng)前采用的傳送協(xié)議允許的最大地址長(zhǎng)度比較起來,這里指定的緩沖區(qū)大小至少應(yīng)多出16字節(jié)。舉個(gè)例子來說,假定正在使用的是TCP/IP協(xié)議,那么這里的大小應(yīng)設(shè)為“SOCKADDR_IN結(jié)構(gòu)的長(zhǎng)度+16字節(jié)”。
參數(shù)七lpdwBytesReceived參數(shù)用于返回接收到的實(shí)際數(shù)據(jù)量,以字節(jié)為單位。只有在操作以同步方式完成的前提下,才會(huì)設(shè)置這個(gè)參數(shù)。假如AcceptEx()函數(shù)返回ERROR_IO_PENDING,那么這個(gè)參數(shù)永遠(yuǎn)都不會(huì)設(shè)置,我們必須利用完成事件通知機(jī)制,獲知實(shí)際讀取的字節(jié)量。
最后一個(gè)參數(shù)是lpOverlapped,它對(duì)應(yīng)的是一個(gè)OVERLAPPED結(jié)構(gòu),允許AcceptEx()以一種異步方式工作。如我們?cè)缦人?,只有在一個(gè)重疊I/O應(yīng)用中,該函數(shù)才需要使用事件對(duì)象通知機(jī)制(hEvent字段),這是由于此時(shí)沒有一個(gè)完成例程參數(shù)可供使用。
二.獲取重疊I/O操作完成結(jié)果
當(dāng)異步I/O請(qǐng)求掛起后,最終要知道I/O操作是否完成。一個(gè)重疊I/O請(qǐng)求最終完成后,應(yīng)用程序要負(fù)責(zé)讀取重疊I/O操作的結(jié)果。對(duì)于讀,直到I/O完成,接收緩沖器才有效(參考IRP緩沖區(qū)管理)。對(duì)于寫,要知道寫是否成功,有幾種方法可以做到這點(diǎn),最直接的方法是調(diào)用(WSA)GetOverlappedResult,其函數(shù)原型如下:
WINBASEAPI BOOL WINAPI
GetOverlappedResult(
HANDLE hFile,
LPOVERLAPPED lpOverlapped,
LPDWORD lpNumberOfBytesTransferred,
BOOL bWait);
BOOL WSAAPI WSAGetOverlappedResult(
SOCKET s,
LPWSAOVERLAPPED lpOverlapped,
LPDWORD lpcbTransfer,
BOOL fWait,
LPDWORD lpdwFlags);
參數(shù)一為的文件/套接字句柄。
參數(shù)二為參數(shù)一關(guān)聯(lián)的(WSA) OVERLAPPED結(jié)構(gòu),在調(diào)用CreateFile()、WSASocket()或AcceptEx()時(shí)指定。
參數(shù)三指向字節(jié)計(jì)數(shù)指針,負(fù)責(zé)接收一次重疊發(fā)送或接收操作實(shí)際傳輸?shù)淖止?jié)數(shù)。
參數(shù)四是確定命令是否等待的標(biāo)志。Wait參數(shù)用于決定函數(shù)是否應(yīng)該等待一次重疊操作完成。若將Wait設(shè)為TRUE,那么直到操作完成函數(shù)才返回;若設(shè)為FALSE,而且操作仍然處于未完成狀態(tài),那么(WSA)GetOverlappedResult()函數(shù)會(huì)返回FALSE值。
如(WSA)GetOverlappedResult()函數(shù)調(diào)用成功,返回值就是TRUE。這意味著我們的重疊I/O操作已成功完成,而且由參數(shù)三BytesTransfered參數(shù)指向的值已進(jìn)行了更新。若返回值是FALSE,那么可能是由下述任何一種原因造成的:
■ 重疊I/O操作仍處在“待決”狀態(tài)。
■ 重疊操作已經(jīng)完成,但含有錯(cuò)誤。
■ 重疊操作的完成狀態(tài)不可判決,因?yàn)樵谔峁┙o WSAGetOverlappedResult函數(shù)的一個(gè)或多個(gè)參數(shù)中,存在著錯(cuò)誤。
失敗后,由BytesTransfered參數(shù)指向的值不會(huì)進(jìn)行更新,而且我們的應(yīng)用程序應(yīng)調(diào)用(WSA)GetLastError()函數(shù),檢查到底是何種原因造成了調(diào)用失敗以使用相應(yīng)容錯(cuò)處理。如果錯(cuò)誤碼為SOCKET_ERROR/WSA_IO_INCOMPLETE(Overlapped I/O event is not in a signaled state)或SOCKET_ERROR/WSA_IO_PENDING(Overlapped I/O operation is in progress),則表明I/O仍在進(jìn)行。當(dāng)然,這不是真正錯(cuò)誤,任何其他錯(cuò)誤碼則真正表明一個(gè)實(shí)際錯(cuò)誤。
下面介紹兩種常用重疊I/O完成通知的方法。
1.使用事件通知
使用(WSA)GetOverlappedResult()是直截了當(dāng)?shù)?,它吻合重疊I/O的概念。畢竟,如果要等待I/O,也許使用常規(guī)I/O命令更好。對(duì)于大多數(shù)程序,反復(fù)檢查I/O是否完成,并非最佳。解決方案之一是使用(WSA)OVERLAPPED結(jié)構(gòu)中的hEvent字段,使應(yīng)用程序?qū)⒁粋€(gè)事件對(duì)象句柄同一個(gè)文件/套接字關(guān)聯(lián)起來。
當(dāng)指定OVERLAPPED參數(shù)給ReadFile()/WriteFile()或WSARecv()/WSASend()后,可以再為(WSA)OVERLAPPED最后一個(gè)參數(shù)提供自定義的事件對(duì)象(通過(WSA)CreateEvent()創(chuàng)建)。
當(dāng)I/O完成時(shí),系統(tǒng)更改(WSA)OVERLAPPED結(jié)構(gòu)對(duì)應(yīng)的事件對(duì)象的傳信狀態(tài),使其從“未傳信”(unsignaled)變成“已傳信”(signaled)。由于我們之前將事件對(duì)象分配給了(WSA)OVERLAPPED結(jié)構(gòu),所以只需簡(jiǎn)單地調(diào)用WaitForSingleObject/WaitForMultipleObjects或WSAWaitForMultipleEvents函數(shù),從而判斷出一個(gè)(一些)重疊I/O在什么時(shí)候完成。通過WaitForSingleObject/WaitForMultipleObjects或WSAWaitForMultipleEvents函數(shù)返回的索引可以知道這個(gè)重疊I/O完成事件是在哪個(gè)HANDLE(File或Socket)上發(fā)生的。
然后調(diào)用(WSA)GetOverlappedResult()函數(shù),將發(fā)生事件的HANDLE(FILE或SOCKET)傳給參數(shù)一,將這個(gè)HANDLE對(duì)應(yīng)的(WSA)OVERLAPPED結(jié)構(gòu)傳給參數(shù)二,這樣判斷重疊調(diào)用到底是成功還是失敗。如果返回FALSE值,則重疊操作已經(jīng)完成但含有錯(cuò)誤。或者重疊操作的完成狀態(tài)不可判決,因?yàn)樵谔峁┙o(WSA)GetOverlappedResult()函數(shù)的一個(gè)或多個(gè)參數(shù)中存在著錯(cuò)誤。失敗后,由BytesTransfered參數(shù)指向的值不會(huì)進(jìn)行更新,應(yīng)用程序應(yīng)調(diào)用(WSA)GetLastError()函數(shù),調(diào)查到底是何種原因造成了調(diào)用失敗。
若(WSA)GetOverlappedResult()函數(shù)返回TRUE,則根據(jù)先前調(diào)用異步I/O函數(shù)時(shí)設(shè)置的緩沖區(qū)(ReadFile/WriteFile或WSARecv/WSASend的lpBuffer字段)和BytesTransfered,使用指針偏移定位就可以準(zhǔn)確操作接受到的數(shù)據(jù)了。
利用事件對(duì)象來完成同步通知的方法比重復(fù)調(diào)用(WSA)GetOverlappedResult()浪費(fèi)處理器時(shí)間的方案要高效得多。但WaitForMultipleObjects/WSAaitForMultipleEvent支持的事件對(duì)象個(gè)數(shù)的上限為MAXIMUM_WAIT_OBJECTS/WSA_MAXIMUM_WAIT_EVENTS=64!
2.使用完成例程
對(duì)于文件重疊I/O操作,等待I/O操作結(jié)束的另外方法是使用ReadFileEx()和WriteFileEx()。這些命令只用于重疊I/O,當(dāng)為它們的最后一個(gè)參數(shù)lpCompletionRoutine傳遞了一個(gè)完成例程指針(回調(diào)函數(shù)地址)時(shí),I/O操作結(jié)束時(shí)將調(diào)用此函數(shù)進(jìn)行處理。
完成例程指針LPOVERLAPPED_COMPLETION_ROUTINE定義如下:
// WINBASE.H
typedef VOID (WINAPI *LPOVERLAPPED_COMPLETION_ROUTINE)(
DWORD dwErrorCode,
DWORD dwNumberOfBytesTransfered,
LPOVERLAPPED lpOverlapped );
相應(yīng)在WinSock 2中,WSARecv()/WSASend()最后一個(gè)參數(shù)lpCompletionROUTINE是一個(gè)可選的指針,它指向一個(gè)完成例程。若指定此參數(shù)(自定義函數(shù)地址),在重疊請(qǐng)求完成后,將調(diào)用完成例程處理。完成例程本質(zhì)上是一種APC(Asynchronous Procedure Calls)。
WinSock 2中完成例程指針LPWSAOVERLAPPED_COMPLETION_ROUTINE定義略有不同:
// WINSOCK2.H
typedef void (CALLBACK * LPWSAOVERLAPPED_COMPLETION_ROUTINE)(
DWORD dwError,
DWORD cbTransferred,
LPWSAOVERLAPPED lpOverlapped,
DWORD dwFlags );
前三個(gè)參數(shù)同LPOVERLAPPED_COMPLETION_ROUTINE,參數(shù)四一般不用,置0。用完成例程完成一個(gè)重疊I/O請(qǐng)求之后,參數(shù)中會(huì)包含下述信息:
參數(shù)一dwError表明了一個(gè)重疊操作(由lpOverlapped指定)的完成狀態(tài)是什么。
參數(shù)二BytesTransferred參數(shù)指定了在重疊操作實(shí)際傳輸?shù)淖止?jié)量是多大。
參數(shù)三lpOverlapped參數(shù)指定的是調(diào)用這個(gè)完成例程的異步I/O操作函數(shù)(ReadFileEx()/WriteFileEx()或WSARecv()/WSASend())的(WSA)OVERLAPPED結(jié)構(gòu)參數(shù)。
提交帶有完成例程的重疊I/O請(qǐng)求時(shí),(WSA)OVERLAPPED結(jié)構(gòu)的事件字段hEvent一般不再使用。使用一個(gè)含有完成例程指針參數(shù)的異步I/O函數(shù)發(fā)出一個(gè)重疊I/O請(qǐng)求之后,一旦重疊I/O操作完成,作為我們的調(diào)用線程,必須能夠通知完成例程指針?biāo)赶虻淖远x函數(shù)開始執(zhí)行,提供數(shù)據(jù)處理服務(wù)。這樣一來,便要求將調(diào)用線程置于一種“可警告的等待狀態(tài)”,在I/O操作完成后,能自動(dòng)調(diào)用完成例程。WSAWaitForMultipleEvents()函數(shù)可用來將線程置于一種可警告的等待狀態(tài)。這樣做的代價(jià)是必須創(chuàng)建一個(gè)事件對(duì)象可用于WSAWaitForMultipleEvents()函數(shù)。假定應(yīng)用程序只用完成例程對(duì)重疊請(qǐng)求進(jìn)行處理,便不需要引入事件對(duì)象。作為一種變通方法,我們的應(yīng)用程序可用Win32的SleepEx()函數(shù)將自己的線程置為一種可警告的等待狀態(tài)。當(dāng)然,亦可創(chuàng)建一個(gè)偽事件對(duì)象,不將它與任何東西關(guān)聯(lián)在一起。假如調(diào)用線程經(jīng)常處于繁忙狀態(tài),而且并不處于一種可警告的等待狀態(tài),那么完成例程根本不會(huì)被通知執(zhí)行。
如前面所述,WSAWaitForMultipleEvents()通常會(huì)等待同(WSA)OVERLAPPED結(jié)構(gòu)關(guān)聯(lián)在一起的事件對(duì)象。該函數(shù)也可用于將我們的線程設(shè)置為一種可警告的等待狀態(tài),為已經(jīng)完成的重疊I/O請(qǐng)求調(diào)用完成例程進(jìn)行處理(前提是將fAlertable參數(shù)設(shè)為TRUE)。使用一個(gè)含有完成例程指針的異步I/O函數(shù)提交了重疊I/O請(qǐng)求之后, WSAWaitForMultipleEvents()的期望返回值是WAIT_IO_COMPLETION(One or more I/O completion routines are queued for execution),而不再是事件對(duì)象索引。從宏WAIT_IO_COMPLETION的注解可知,它的意思是有完成例程需要執(zhí)行。SleepEx()函數(shù)的行為實(shí)際上和WSAWaitForMultipleEvents()差不多,只是它不需要任何事件對(duì)象。對(duì)SleepEx函數(shù)的定義如下:
WINBASEAPI DWORD WINAPI
SleepEx(
DWORD dwMilliseconds,
BOOL bAlertable );
其中,dwMilliseconds參數(shù)定義了SleepEx()函數(shù)的等待時(shí)間,以毫秒為單位。假如將dwMilliseconds設(shè)為INFINITE,那么SleepEx()會(huì)無休止地等待下去。bAlertable參數(shù)規(guī)定了一個(gè)完成例程的執(zhí)行方式,若將它設(shè)置為FALSE,則使用一個(gè)含有完成例程指針的異步I/O函數(shù)提交了重疊I/O請(qǐng)求后,I/O完成例程不會(huì)被通知執(zhí)行,而且SleepEx()函數(shù)不會(huì)返回,除非超過由dwMilliseconds規(guī)定的時(shí)間;若將它設(shè)置為TRUE,則完成例程會(huì)被通知執(zhí)行,同時(shí)SleepEx()函數(shù)返回WAIT_IO_COMPLETION。
在完成例程處理模型中,投遞重疊I/O請(qǐng)求的同時(shí)注冊(cè)完成例程,待I/O完成時(shí)由系統(tǒng)回調(diào),并克服了事件通知模型的個(gè)數(shù)限制。利用完成例程處理重疊I/O的WinSock程序的編寫步驟如下:
(1) 新建一個(gè)監(jiān)聽套接字,在指定端口上監(jiān)聽客戶端的連接請(qǐng)求。
(2) 接受一個(gè)客戶端的連接請(qǐng)求,并返回一個(gè)會(huì)話套接字負(fù)責(zé)與客戶端通信。
(3) 為會(huì)話套接字關(guān)聯(lián)一個(gè)WSAOVERLAPPED結(jié)構(gòu)。
(4) 在套接字上投遞一個(gè)異步WSARecv請(qǐng)求,方法是將WSAOVERLAPPED指定成為參數(shù),同時(shí)提供一個(gè)完成例程。
(5) 在將fAlertable參數(shù)設(shè)為TRUE的前提下,調(diào)用WSAWaitForMultipleEvents,并等待一個(gè)重疊I/O請(qǐng)求完成。重疊請(qǐng)求完成后,完成例程會(huì)自動(dòng)執(zhí)行,而且WSAWaitForMultipleEvents會(huì)返回一個(gè)WAIT_IO_COMPLETION。在完成例程內(nèi),可隨一個(gè)完成例程一道投遞另一個(gè)重疊WSARecv請(qǐng)求。
(6) 檢查WSAWaitForMultipleEvents是否返回WAIT_IO_COMPLETION。
(7) 重復(fù)步驟(5)和(6)。
當(dāng)調(diào)用accept處理連接時(shí),一般創(chuàng)建一個(gè)AcceptEvent偽事件,當(dāng)有客戶連接時(shí),需要手動(dòng)SetEvent(AcceptEvent);當(dāng)調(diào)用AcceptEx處理重疊的連接時(shí),一般為L(zhǎng)istenSocket創(chuàng)建一個(gè)ListenOverlapped結(jié)構(gòu),并為其指定一個(gè)偽事件,當(dāng)有客戶連接時(shí),系統(tǒng)自動(dòng)將其置信。這些偽事件的作用在于,當(dāng)含有完成例程指針的異步I/O操作(如WSARecv)完成時(shí),設(shè)置了fAlertable的WSAWaitForMultipleEvents返回WAIT_IO_COMPLETION,并調(diào)用完成例程指針指向的完成例程對(duì)數(shù)據(jù)進(jìn)行處理。
重疊I/O模型的缺點(diǎn)是它一般要為每一個(gè)I/O請(qǐng)求都開一個(gè)線程,當(dāng)同時(shí)有成千上萬個(gè)請(qǐng)求發(fā)生時(shí),系統(tǒng)處理線程上下文切換是非常耗時(shí)的。所以這也就引出了更為先進(jìn)的完成端口模型IOCP,用線程池來解決這個(gè)問題。
參考:
《Windows 2000 Systems Programming Black Book》 Al Williams
《Network Programming for Microsoft Windows》 Anthony Jones,Jim Ohlund
本文來自CSDN博客,轉(zhuǎn)載請(qǐng)標(biāo)明出處:http://blog.csdn.net/phunxm/archive/2009/12/27/5085915.aspx