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