摘自《Visual C++網(wǎng)絡(luò)游戲建模與實現(xiàn)》(蘇羽、王媛媛編著) Win32重疊I/O(Overloapped I/O)機制允許發(fā)起一個操作,然后在操作完成之后接受
到信息。對于那種需要很長時間才能完成的操作來說,重疊I/O機制尤其有用,因為發(fā)起
重疊操作的線程在重疊請求發(fā)出后就可以自由地做別的事情了。
在Windows NT/2000上,提供真正可擴展的I/O模型就是使用完成端口(Completion
Port)的重疊I/O。
……
可以把完成端口看成系統(tǒng)維護的一個隊列,操作系統(tǒng)把重疊I/O操作完成的事件通知
放到該隊列里,由于是暴露“操作完成”的事件通知,所以命名為“完成端口”(Completion
Ports)。一個Socket被創(chuàng)建后,可以在任何時刻和一個完成端口聯(lián)系起來。
一般來說,一個應(yīng)用程序可以創(chuàng)建多個工作線程來處理完成端口上的通知事件。工作
線程的數(shù)量依賴于程序的具體需要。但是在理想的情況下,應(yīng)該對應(yīng)一個CPU創(chuàng)建一個線
程。因為在完成端口理想模型中,每個線程都可以從系統(tǒng)獲得一個“原子”性的時間片,輪
番運行并檢查完成端口,線程的切換是額外的開銷。在實際開發(fā)的時候,還要考慮這些線
程是否牽涉到其他堵塞操作的情況。如果某線程進行堵塞操作,系統(tǒng)則將其掛起,讓別的
線程獲得運行時間。因此,如果有這樣的情況,可以多創(chuàng)建幾個線程來盡量利用時間。
應(yīng)用完成端口分兩步走:
1. 創(chuàng)建完成端口句柄:
HANDLE hIocp;
hIocp=CreateIoCompletionPort(
INVALID_HANDLE_VALUE,
NULL,
(ULONG_PTR)0,
0);
if(hIocp==NULL) {
//如果錯誤
……
}
注意在第1個參數(shù)(FileHandle)傳入INVALID_FILE_HANDLE,第2個參數(shù)(ExistingCompletionPort)
傳入NULL,系統(tǒng)將創(chuàng)建一個新的完成端口句柄,沒有任何I/O句柄與其關(guān)聯(lián)。
2. 完成端口創(chuàng)建成功后,在Socket和完成端口之間建立關(guān)聯(lián)。再次調(diào)用CreateIoCompletionPort
函數(shù),這一次在第1個參數(shù)FileHandle傳入創(chuàng)建的Socket句柄,參數(shù)ExistingCompletionPort
為已經(jīng)創(chuàng)建的完成端口句柄。
以下代碼創(chuàng)建了一個Socket并把它和完成端口聯(lián)系起來。
SOCKET s;
s=Socket(AF_INET,SOCK_STREAM,0);
if(s==INVALID_SOCKET) {
if(CreateIoCompletionPort((HANDLE)s,
hIocp,
(ULONG_PTR)0,
0)==NULL)
{
//如果創(chuàng)建失敗
……
}
}
到此為止,Socket已經(jīng)成功和完成端口相關(guān)聯(lián)。在此Socket進行的重疊I/O操作結(jié)果均
使用完成端口發(fā)出通知。
注意:CreateIoCompletionPort函數(shù)的第3個參數(shù)允許開發(fā)人員傳入一個類型為ULONG_PTR
的數(shù)據(jù)成員,我們把它稱為完成鍵(Completion Key),此數(shù)據(jù)成員可以設(shè)計為指向包含Socket
信息的一個結(jié)構(gòu)體的一個指針,用來把相關(guān)的環(huán)境信息和Socket聯(lián)系起來,每次完成通知來
到的同時,該環(huán)境信息也隨著通知一起返回給開發(fā)人員。
完成端口創(chuàng)建以及與Socket關(guān)聯(lián)之后,就要創(chuàng)建一個或多個工作線程來處理完成通知,
每個線程都可以循環(huán)地調(diào)用GetQueuedCompletionStatus函數(shù),檢查完成端口上的通知事件。
在舉例說明一個典型的工作線程之前,我們先討論一下重疊I/O的過程。到一個重疊I/O
被發(fā)起,一個Overlapped結(jié)構(gòu)體的指針就要作為參數(shù)傳遞給系統(tǒng)。當操作完成時,
GetQueueCompletionStatus就可以返回指向同一個Overlapped結(jié)構(gòu)的指針。為了辨認和定位
這個已完成的操作,開發(fā)人員最好定義自己的OVERLAPPED結(jié)構(gòu),以包含一些自己定義的關(guān)于
操作本身的額外信息。比如:
typedef struct _OVERLAPPELUS {
OVERLAPPED ol;
SOCKET s, sclient;
int OpCode;
WSABUF wbuf;
DWORD dwBytes, dwFlags;
} OVERLAPPELUS;
此結(jié)構(gòu)的第1個成員為默認的OVERLAPPED結(jié)構(gòu),第2和第3個為本地服務(wù)Socket和與該
操作相關(guān)的客戶socket,第4個成員為操作類型,對于Socket,現(xiàn)在定義的有以下3種:
#define OP_READ 0
#define OP_WRITE 1
#define OP_ACCEPT 2
然后還有應(yīng)用程序的Socket緩沖區(qū),操作數(shù)據(jù)量,標志位以及其他開發(fā)人員認為有用
的信息。
當進行重疊I/O操作,把OVERLAPPELUS結(jié)構(gòu)作為重疊I/O的參數(shù)lpOverlapp傳遞(如
WSASend,WASRecv,等函數(shù)的lpOverlapped參數(shù),要求傳入一個OVERLAPP結(jié)構(gòu)的指針)。
當操作完成后,GetQueuedCompletionStatus函數(shù)返回一個LPOVERLAPPED類型的指針,
這個指針其實是指向開發(fā)人員定義的擴展OVERLAPPELUS結(jié)構(gòu),包含著開發(fā)人員早先傳入的
全部信息。
注意:OVERLAPPED成員不一定要求是OVERLAPPELUS擴展結(jié)構(gòu)的一個成員,在獲得
OVERLAPPED指針之后,可以用CONTAINING_RECORD宏獲得相應(yīng)的擴展結(jié)構(gòu)的指針。
典型的Worker Thread結(jié)構(gòu):
DWORD WINAPI WorkerThread(LPVOID lpParam)
{
ULONG_PTR *PerHandleKey;
OVERLAPPED *Overlap;
OVERLAPPELUS *OverlapPlus, *newolp;
DWORD dwBytesXfered;
while(1)
{
ret=GetQueuedCompletionStatus(
hIocp,
&dwBytesXfered,
(PULONG_PTR)&PerHandleKey,
&Overlap,
INFINITE);
if(ret==0)
{
//如果操作失敗
continue;
}
OverlapPlus=CONTATING_RECORD(Overlap, OVERLAPPELUS, ol);
switch(OverlapPlus->OpCode)
{
case OP_ACCEPT:
CreateIoCompletionPort(
(HANDLE)OverlapPlus->sclient,
hIocp,
(ULONG_PTR)0,
0);
newolp=AllocateOverlappelus();
newolp->s=OverlapPlus->sclient;
newolp->OpCode=OP_READ;
PrepareSendBuffer(&newolp->wbuf);
ret=WSASend(
newolp->s,
&newolp->wbuf,
1,
&newolp->dwBytes,
0,
&newolp.ol,
NULL);
if(ret==SOCKET_ERROR)
{
if(WSAGetLastError()!=WSA_IO_PENDING)
{
//進行錯誤處理
……
}
}
FreeOverlappelus(OverlapPlus);
SetEvent(hAcceptThread);
break;
case OP_READ:
memset(&OverlapPlus->ol,0,sizeof(OVERLAPPED));
ret=WSARecv(
OverlapPlus->s,
&OverlapPlus->wbuf,
1,
&OverlapPlus->dwBytes,
&OverlapPlus->dwFlags,
&OverlapPlus->ol,
NULL);
if(ret==SOCKET_ERROR)
{
if(WSAGetLastError()!=WSA_IO_PENDING)
{
//錯誤處理
……
}
}
break;
case OP_WRITE:
break;
}/*switch結(jié)束*/
}/*while結(jié)束*/
}/*WorkerThread結(jié)束*/
注意:如果Overlapped操作立刻失敗(比如,返回SOCKET_ERROR或其他非
WSA_IO_PENDING的錯誤),則沒有任何完成通知事件會被放到完成端口隊列里。反之,
則一定有相應(yīng)的通知事件被放到端口隊列。
posted on 2007-04-28 17:14
jay 閱讀(679)
評論(0) 編輯 收藏 引用 所屬分類:
socket