重疊模型的基本設計原理是讓應用程序使用重疊的數據結構,一次投遞一個或多個WinsockI/O請求。針對那些提交的請求,在它們完成之后,應用程序可為它們提供服務。模型的總體設計以Windows重疊I/O機制為基礎。這個機制可通過ReadFile和WriteFile兩個函數,在設備上執行I/O操作。
要想在一個套接字上使用重疊I/O模型,首先必須創建一個設置了重疊標志的套接字。
主要有兩種方法來管理重疊I/O的請求。1.事件對象通知 2.完成實例。
事件通知:
重疊I/O的事件通知方法要求將Windows事件對象與WSAOVERLAPPED結構關聯在一起。若使用一個WSAOVERLAPPED結構,發出像WSASend和WSARecv這樣的I/O調用,它們會立即返回。
WSAOVERLAPPED結構為重疊I/O請求的初始化及其后續的完成之間提供了一種通信媒介。結構的定義如下:
typedef?struct?WSAOVERLAPPED?{???DWORD?Internal;???DWORD?InternalHigh;???DWORD?Offset;???DWORD?OffsetHigh;???WSAEVENT?hEvent;?}WSAOVERLAPPED,?FAR*?LPWSAOVERLAPPED;?
Internal,InternalHigh,Offset,OffsetHigh字段均由系統在內部使用,不能有應用程序直接進行處理或使用。hEvent字段則允許應用程序將事件對象的句柄同操作關聯起來。
一個重疊I/O完成以后,應用程序要負責獲取重疊I/O操作的結果。一個重疊請求操作最終完成之后,在事件通知方法中,Winsock會更改與WSAOVERLAPPED結構關聯的事件對象的事件傳信狀態,將未傳信變成已傳信。由于已經有一個事件對象分配給WSAOVERLAPPED結構,所有只需簡單的調用WSAWaitForMultipleEvents函數,便可判斷出重疊I/O調用將在什么時候完成。WSAWaitForMultipleEvents會等待一段指定時間,等待一個或多個事件進入已傳信狀態。 WSAWaitForMultipleEvents一次只能等待64個事件對象。確定某個重疊事件完成以后,接著需要調用WSAGetOverlappedResult函數,判斷這個重疊調用是否成功。
BOOL?WSAGetOverlappedResult(???SOCKET?s,?//重疊操作開始的時候,被指定的套接字??LPWSAOVERLAPPED?lpOverlapped,?//重疊操作開始的時候,被指定的WSAOVERLAPPED結構??LPDWORD?lpcbTransfer,//負責接收一次重疊發送或接收操作實際傳輸的字節數???BOOL?fWait,//用于決定函數是否應該等待掛起的重疊操作完成???LPWORD?lpdwFlags?//負責接收結果標志);?
若WSAGetOverlappedResult函數調用成功,返回值就是TRUE,意味著重疊操作完成成功,而且lpcbTransfer參數所指向的值已進行了更新,若返回FALSE,那么可能是由以下原因造成的:
1.重疊I/O操作仍處于掛起狀態
2.重疊操作已經完成,但含有錯誤
3.因為在提供給WSAGetOverlappedResult函數的一個或多個參數中存在錯誤,所有無法判斷重疊操作的完成狀態
失敗后,lpcbTransfer所指向的值不會被更新,而且應用程序應調用WSAGetLastError函數查看錯誤原因。
?
利用事件通知機制設計一個簡單的服務器應用程序,令其在一個套接字上對重疊I/O操作進行管理:
#define?DATA_BUFSIZE?2046??void?main(void)?{??????WSABUF?DataBuf;??????char?buffer[DATA_BUFSIZE];??????DWORD?EventTotal?=?0;??????DWORD?RecvBytes?=?0;??????DWORD?Flags?=?0;??????WSAEVENT?EventArray[WSA_MAXIMUM_WAIT_EVENTS];??????WSAOVERLAPPED?AcceptOverlapped;??????SOCKET?ListenSocket,?AcceptSocket;????????????????????????...??????????????????AcceptSocket?=?accept(ListenSocket,NULL,NULL);????????????????????????EventArray[EventTotal]?=?WSACreateEvent();????????????ZeroMemory(&AcceptOverlapped,?sizeof(WSAOVERLAPPED));??????AcceptOverlapped.hEvent?=?EventArray[EventTotal];????????????DataBuf.len?=?DATA_BUFSIZE;??????DataBuf.buf?=?buffer;????????????EventTotal++;????????????????????????if(SOCKET_ERROR?==??????????WSARecv(AcceptSocket,?&DataBuf,?1,?&RecvBytes,?&Flags,?&AcceptOverlapped,?NULL))??????{??????????if(WSA_IO_PENDING?!=?WSAGetLastError())??????????{????????????????????????}??????}?????????????while(TRUE)??????{??????????DWORD?Index;??????????????????????????????Index?=?WSAWaitForMultipleEvents(EventTotal,?EventArray,?FALSE,?WSA_INFINITE,?FALSE);??????????????????????????????????????????????????WSAResetEvent(EventArray[Index-WSA_WAIT_EVENT_0]);??????????????????????????????WSAGetOverlappedResult(AcceptSocket,&AcceptOverlapped,&BytesTransferred,FALSE,&Flags);??????????????????????????????if(BytesTransferred==0)??????????{??????????????printf("Closing?socket?%d\n",?AcceptSocket);??????????????closesocket(AcceptSocket);??????????????WSACloseEvent(EventArray[Index-WSA_WAIT_EVENT_0]);??????????????return?;??????????}?????????????????????????????????????????...??????????????????????????????????????????????????Flags?=?0;??????????ZeroMemory(&AccpetOverlapped,?sizeof(WSAOVERLAPPED));????????????????????AcceptOverlapped.hEvent?=?EventArray[Index-WSA_WAIT_EVENT_0];??????????DataBuf.len?=?DATA_BUFSIZE;??????????DataBuf.buf?=?buffer;??????????if(SOCKET_ERROR?==???????????WSARecv(AcceptSocket,?&DataBuf,?1,?&RecvBytes,?&Flags,?&AcceptOverlapped,?NULL))??????????{??????????????if(WSA_IO_PENDING?!=?WSAGetLastError())??????????????{????????????????????????????????}??????????}??????}??}?
?對該程序采用的編程步驟總結如下:
1.創建一個套接字,開始在指定的端口上監聽連接請求
2.接受一個入站的連接請求
3.為接收的套接字新建一個WSAOVERLAPPED結構,并為該結構分配一個事件對象句柄。也將該事件對象句柄分配給一個事件數組,以便稍后由WSAWaitForMultipleEvents使用。
4.將WSAOVERLAPPED指定為參數,在套接字上投遞一個異步WSARecv請求
5.使用步驟3的事件數組,調用WSAWaitForMultipleEvents函數,并等待與重疊調用關聯在一起的事件進入已傳信狀態
6.使用WSAGetOverlappedResult函數,判斷重疊調用的返回狀態
7.函數完成后,針對重疊數組,調用WSAResetEvent函數,從而重設事件對象,并對完成的重疊請求進行處理
8.在套接字上投遞另一個重疊WSARecv請求
9.重復步驟5~8
這個例子極易擴展,從而提供對多個套接字的支持。方法是將代碼的重疊I/O處理部分移至一個對立的線程中,讓主應用程序線程為額外的連接請求提供服務。
===================================================================
#include<winsock2.h>?#include<stdio.h>?#pragma?comment(lib,"ws2_32.lib");??#define?PORT?5050?#define?MSGSIZE?1024??typedef?struct?{?????WSAOVERLAPPED?overlap;?????WSABUF?Buffer;?????char?szMessage[MSGSIZE];?????DWORD?NumberOfBytesRecvd;?????DWORD?Flags;?}PER_IO_OPERATION_DATA,?*LPPER_IO_OPERATION_DATA;??int?????????????????????g_iTotalConn?=?0;?SOCKET??????????????????g_CliSocketArr[MAXIMUM_WAIT_OBJECTS];?WSAEVENT????????????????g_CliEventArr[MAXIMUM_WAIT_OBJECTS];?LPPER_IO_OPERATION_DATA?g_pPerIoDataArr[MAXIMUM_WAIT_OBJECTS];??DWORD?WINAPI?WorkerThread(LPVOID?lpParam);?void?Cleanup(int?index);??int?main()?{?????WSADATA?wsaData;?????SOCKET?sListen,?sClient;?????SOCKADDR_IN?client,?local;?????DWORD?dwThreadId;?????int?iAddrSize?=?sizeof(SOCKADDR_IN);??????????WSAStartup(MAKEWORD(2,2),?&wsaData);?????sListen?=?socket(AF_INET,?SOCK_STREAM,?IPPROTO_TCP);?????memset(&local,?0,?sizeof(SOCKADDR_IN));?????local.sin_family?=?AF_INET;?????local.sin_port?=?htons(PORT);?????local.sin_addr.s_addr?=?htonl(INADDR_ANY);??????????bind(sListen,?(SOCKADDR*)&local,?sizeof(SOCKADDR_IN));?????listen(sListen,?5);??????????CreateThread(NULL,?0,?WorkerThread,?NULL,?0,?&dwThreadId);??????????while(TRUE)?????{?????????sClient?=?accept(sListen,?(SOCKADDR*)&client,?&iAddrSize);?????????printf("Accepted?Client:%s:%d\n",?inet_ntoa(client.sin_addr),?ntohs(client.sin_port));?????????g_CliSocketArr[g_iTotalConn]?=?sClient;?????????g_pPerIoDataArr[g_iTotalConn]?=?(LPPER_IO_OPERATION_DATA)HeapAlloc(???????????????????????????????????????GetProcessHeap(),???????????????????????????????????????HEAP_ZERO_MEMORY,???????????????????????????????????????sizeof(PER_IO_OPERATION_DATA)???????????????????????????????????????);?????????g_pPerIoDataArr[g_iTotalConn]->Buffer.len?=?MSGSIZE;?????????g_pPerIoDataArr[g_iTotalConn]->Buffer.buf?=?g_pPerIoDataArr[g_iTotalConn]->szMessage;?????????g_pPerIoDataArr[g_iTotalConn]->overlap.hEvent?=?WSACreateEvent();?????????g_CliEventArr[g_iTotalConn]?=?g_pPerIoDataArr[g_iTotalConn]->overlap.hEvent;??????????????????WSARecv(g_CliSocketArr[g_iTotalConn],?????????????????&g_pPerIoDataArr[g_iTotalConn]->Buffer,?????????????????1,?????????????????&g_pPerIoDataArr[g_iTotalConn]->NumberOfBytesRecvd,?????????????????&g_pPerIoDataArr[g_iTotalConn]->Flags,?????????????????&g_pPerIoDataArr[g_iTotalConn]->overlap,?????????????????NULL);?????????g_iTotalConn++;?????}?????closesocket(sListen);?????WSACleanup();??????????return?0;?}??DWORD?WINAPI?WorkerThread(LPVOID?lpParam)?{?????int?ret,?index;?????DWORD?cbTransferred;?????while(TRUE)?????{?????????ret?=?WSAWaitForMultipleEvents(g_iTotalConn,?g_CliEventArr,?FALSE,?1000,?FALSE);?????????if(ret==WSA_WAIT_FAILED?||?ret==WSA_WAIT_TIMEOUT)?????????{?//如果當前沒有客戶端的話,要sleep一下,要不然CUP會占50%以上if(g_iTotalConn==0)Sleep(1000);????????????continue;?????????}?????????index?=?ret?-?WSA_WAIT_EVENT_0;?????????WSAResetEvent(g_CliEventArr[index]);?????????WSAGetOverlappedResult(g_CliSocketArr[index],????????????????????????????????&g_pPerIoDataArr[index]->overlap,????????????????????????????????&cbTransferred,????????????????????????????????TRUE,????????????????????????????????&g_pPerIoDataArr[index]->Flags);?????????if(cbTransferred==0)?????????{?????????????Cleanup(index);?????????}?????????else?????????{?????????????g_pPerIoDataArr[index]->szMessage[cbTransferred]?=?'\0';?????????????send(g_CliSocketArr[index],g_pPerIoDataArr[index]->szMessage,cbTransferred,0);??????????????????????????WSARecv(g_CliSocketArr[index],?????????????????????&g_pPerIoDataArr[index]->Buffer,?????????????????????1,?????????????????????&g_pPerIoDataArr[index]->NumberOfBytesRecvd,?????????????????????&g_pPerIoDataArr[index]->Flags,?????????????????????&g_pPerIoDataArr[index]->overlap,?????????????????????NULL);?????????}?????}?????return?0;?}??void?Cleanup(int?index)?{??????closesocket(g_CliSocketArr[index]);??????WSACloseEvent(g_CliEventArr[index]);??????HeapFree(GetProcessHeap(),?0,?g_pPerIoDataArr[index]);??????if(index<g_iTotalConn-1)??????{??????????g_CliSocketArr[index]?=?g_CliSocketArr[g_iTotalConn-1];??????????g_CliEventArr[index]?=?g_CliEventArr[g_iTotalConn-1];??????????g_pPerIoDataArr[index]?=?g_pPerIoDataArr[g_iTotalConn-1];??????}??????g_pPerIoDataArr[--g_iTotalConn]?=?NULL;?}?
?
?
完成例程:
完成例程是應用程序用來管理完成的重疊I/O請求的另一種方法。完成例程其實就是一些函數,我們將這些函數傳遞給重疊I/O請求,以供重疊I/O請求完成時由系統調用。它們的設計宗旨,是通過調用者的線程,為已完成的I/O請求提供服務。除此以外,應用程序可通過完成例程,繼續進行重疊I/O的處理。
如果希望用完成例程為重疊I/O請求提供服務,應用程序必須為一個綁定I/O的Winsock函數指定一個完成例程,同時指定一個WSAOVERLAPPED結構。一個完成例程必須擁有下述函數原型:
void?CALLBACK?CompletionROUTINE(???DWORD?dwError,???DWORD?cbTransferred,???LPWSAOVERLAPPED?lpOverlapped,???DWORD?dwFlags?);?
1.dwError參數表明一個重疊操作的完成狀態時什么
2.cbTransferred參數指明了在重疊操作期間,實際傳輸的字節量是多大
3.lpOverlapped參數指明傳遞到最初的I/O調用內的一個WSAOVERLAPPED結構
4.dwFlags參數返回操作結束時可能用的標志
在用一個完成例程提交的重疊請求與用一個事件對象提交的重疊請求之間,存在著一個非常重要的區別。WSAOVERLAPPED的hEvent并未被使用,也就是說,不可以將一個事件對象同重疊請求關聯在一起。用完成例程發出重疊I/O調用之后,調用線程一旦完成,最終必須為完成例程提供服務。這樣,便要求我們將調用線程置于一種警覺等待狀態。并在I/O操作完成后,對完成例程加以處理。WSAWaitForMultipleEvents可以將線程置于一種警覺的等待狀態。這樣做的缺點在于,我們必須還有一個事件對象可用于WSAWaitForMultipleEvents函數。假定應用程序用完成例程只對重疊請求進行處理,便不大可能有什么事件對象需要處理。作為一種變通方法,應用程序可用SleepEx函數將線程置于一種警覺的等待狀態。當然,也可創建一個偽事件對象,它不與任何東西關聯在一起。假如調用線程總是處于繁忙狀態,而不是處于一種警覺的等待狀態,那么根本不會有被投遞的完成例程會得到調用。
WSAWaitForMultipleEvents通常會等待同WSAOVERLAPPED關聯在一起的事件對象。該函數也用來將線程置于一種警覺的等待狀態,并可為已經完成的重疊I/O請求進行完成例程的處理(前提是將fAlertable設為TRUE)。用完成例程接收重疊I/O請求之后,返回值是WSA_IO_COMPLETION,而不是事件數組中的一個事件對象的索引。SleepEx實際上和WSAWaitForMultipleEvents差不多,只是它不需要事件對象。
DWORD?SleepEx(???DWORD?dwMilliseconds,???BOOL?bAlertable?);?
dwMilliseconds定義了SleepEx函數的等待時間,以毫秒為單位,如果將dwMilliseconds設為INFINITE,那么SleepEx會無休止的等待下去。
bAlertable指定了完成例程的執行方式。假如將bAlertable設為FALSE,而且進行了一次I/O完成回叫,那么I/O完成函數就不會執行,而且該函數也不會返回。除非超過由dwMilliseconds規定的時間。若設為TRUE,那么完成例程便會得到執行,同時SleepEx函數返回WAIT_IO_COMPLETION。
下面代碼演示了如果構建一個簡單的服務器應用程序,令其采用前述方法,通過完成例程來實現對一個套接字請求管理:
#define?DATA_BUFSIZE?4096??SOCKET?AcceptSocket,?ListenSocket;?WSABUF?DataBuf;?char?buffer[DATA_BUFSIZE];?WSAEVENT?EventArray[MAXIMUM_WAIT_OBJECTS];?DWORD?Flags,?RecvBytes,?Index;??void?main(void)?{??????WSAOVERLAPPED?Overlapped;??????????????????...????????????????????????AcceptSocket?=?accept(ListenSocket,?NULL,?NULL);????????????????????????????????????Flags?=?0;????????????ZeroMemory(&Overlapped,?sizeof(WSAOVERLAPPED));????????????DataBuf.len?=?DATA_BUFSIZE;??????DataBuf.buf?=?buffer;????????????????????????????????????if(SOCKET_ERROR?=?WSARecv(AcceptSocket,?&DataBuf,?1,?&RecvBytes,?&Flags,?&Overlapped,?WorkerRoutine))??????{??????????if(WSA_IO_PENDING?!=?WSAGetLastError())??????????{??????????????printf("WSARecv()?failed?with?error?%d\n",?WSAGetLastError());??????????????return?;??????????}??????}???????????????????????????????EventArray[0]?=?WSACreateEvent();????????????while(TRUE)??????{????????????????????Index?=?WSAWaitForMultipleEvents(1,?EventArray,?FALSE,?WSA_INFINITE,?TRUE);????????????????????if(Index==WSA_IO_COMPLETION)??????????{????????????????????????????continue;???????????}???????????else??????????{??????????????????????????????????????????return?;???????????}??????}??}??void?CALLBACK?WorkerRoutine(DWORD?Error,?DWORD?BytesTransferred,??????????????????????????????LPWSAOVERLAPPED?Overlapped,?DWORD?InFlags)?{?????DWORD?RecvBytes,?SendBytes;?????DWORD?Flags;??????????if(Error!=0||BytesTransferred==0)?????{??????????????????closesocket(AcceptSocket);?????????return?;??????}???????????????????????????????????Flags?=?0;??????????ZeroMemory(&Overlapped,?sizeof(WSAOVERLAPPED));??????????DataBuf.len?=?DATA_BUFSIZE;?????DataBuf.buf?=?buffer;??????????if(SOCKET_ERROR?=?WSARecv(AcceptSocket,?&DataBuf,?1,?&RecvBytes,?&Flags,?&Overlapped,?WorkerRoutine))?????{?????????if(WSA_IO_PENDING?!=?WSAGetLastError())?????????{?????????????printf("WSARecv()?failed?with?error?%d\n",?WSAGetLastError());?????????????return?;?????????}?????}?}?
程序主要的執行步驟:
1.新建一個套接字,開始在指定端口上監聽傳入的連接
2.接收一個入站連接
3.為接收的套接字創建一個WSAOVERLAPPED結構
4.在套接字上投遞一個異步WSARecv請求,需要將WSAOVERLAPPED指定為一個參數,同時提供一個完成例程
5.在將fAlertable設為TRUE的前提下,調用WSAWaitForMultipleEvents,并等待一個重疊I/O請求完成。重疊請求完成后,完成例程會自動執行,而且WSAWaitForMultipeEvents會返回一個WSA_IO_COMPLETION。在完成例程內,可用一個完成例程投遞另一個重疊WSARecv請求。
6.檢查WSAWaitForMultipleEvents是否返回WSA_IO_COMPLETION
7.重復步驟5~6
重疊模型提供高性能的套接字I/O,因為使用這種模型的應用程序通知緩沖區收發系統直接使用的數據,所有跟前面幾種不同。也就是說,如果應用程序投遞了一個10KB大小的緩沖區來接收數據,且數據已到達套接字,則該數據將直接被拷貝到投遞的緩沖區。在前述模型中,數據到達并被拷貝到單套接字接收緩沖區中,此時應用程序會被告知可以讀入的容量。當應用程序調用接收函數之后,數據將從單套接字緩沖區拷貝到應用程序的緩沖區。
在事件中使用重疊I/O的缺點,也就是每次最多只能等待64個事件這一局限性。完成例程是一個不錯的替代方案,但必須注意確保投遞完成操作的線程進入警覺的等待狀態,以便使完成例程能夠圓滿的結束。同時,還要確保完成例程不要做過量的運算,以便在很重的負載之下,這些完成過程能夠盡快開始運行。
=============================================================
#include?<stdio.h>?#include?<Winsock2.h>?#pragma?comment(lib,?"ws2_32.lib")??#define?PORT?5050?#define?MSGSIZE?1024??typedef?struct?{?????WSAOVERLAPPED?overlap;?????WSABUF?Buffer;?????char?szMessage[MSGSIZE];?????DWORD?NumberOfBytesRecvd;?????DWORD?Flags;?????SOCKET?sClient;?}PER_IO_OPERATION_DATA,?*LPPER_IO_OPERATION_DATA;??int?????????????????????g_iTotalConn?=?0;?SOCKET??????????????????g_CliSocketArr[MAXIMUM_WAIT_OBJECTS];?WSAEVENT????????????????g_CliEventArr[MAXIMUM_WAIT_OBJECTS];?LPPER_IO_OPERATION_DATA?g_pPerIoDataArr[MAXIMUM_WAIT_OBJECTS];?SOCKET?g_sNewClientConnection?=?NULL;?BOOL?g_bNewConnectionArrived?=?FALSE;??DWORD?WINAPI?WorkerThread(LPVOID?lpParam);?void?CALLBACK?CompletionRoutine(DWORD?dwError,?DWORD?cbTransferred,??????????????????????????????????LPWSAOVERLAPPED?lpOverlapped,?DWORD?dwFlags);??int?main()?{?????WSADATA?wsaData;?????SOCKET?sListen;?????SOCKADDR_IN?local,?client;?????int?iAddrSize?=?sizeof(SOCKADDR_IN);?????DWORD?dwThreadId;?????WSAStartup(MAKEWORD(2,2),?&wsaData);?????sListen?=?socket(AF_INET,?SOCK_STREAM,?IPPROTO_TCP);?????memset(&local,?0,?sizeof(SOCKADDR_IN));?????local.sin_family?=?AF_INET;?????local.sin_port?=?htons(PORT);?????local.sin_addr.s_addr?=?htonl(INADDR_ANY);?????bind(sListen,?(SOCKADDR*)&local,?sizeof(SOCKADDR_IN));?????listen(sListen,?5);??????????CreateThread(NULL,?0,?WorkerThread,?NULL,?0,?&dwThreadId);??????????while(TRUE)?????{?????????g_sNewClientConnection?=?accept(sListen,?(SOCKADDR*)&client,?&iAddrSize);?????????g_bNewConnectionArrived?=?TRUE;?????????printf("Accepted?Client:%s:%d?\n",?inet_ntoa(client.sin_addr),?ntohs(client.sin_port));?????}?????return?0;?}??DWORD?WINAPI?WorkerThread(LPVOID?lpParam)?{?????LPPER_IO_OPERATION_DATA?lpPerIOData?=?NULL;?????while(TRUE)?????{?????????if(g_bNewConnectionArrived)?????????{?????????????lpPerIOData?=?(LPPER_IO_OPERATION_DATA)HeapAlloc(????????????????????????????GetProcessHeap(),????????????????????????????HEAP_ZERO_MEMORY,????????????????????????????sizeof(PER_IO_OPERATION_DATA));?????????????lpPerIOData->Buffer.len?=?MSGSIZE;?????????????lpPerIOData->Buffer.buf?=?lpPerIOData->szMessage;?????????????lpPerIOData->sClient?=?g_sNewClientConnection;?????????????WSARecv(lpPerIOData->sClient,?????????????????????&lpPerIOData->Buffer,?????????????????????1,?????????????????????&lpPerIOData->NumberOfBytesRecvd,?????????????????????&lpPerIOData->Flags,?????????????????????&lpPerIOData->overlap,?????????????????????CompletionRoutine);?????????????g_bNewConnectionArrived?=?FALSE;?????????}?????????SleepEx(1000,TRUE);?????}?????return?0;?}??void?CALLBACK?CompletionRoutine(DWORD?dwError,?DWORD?cbTransferred,??????????????????????????????????LPWSAOVERLAPPED?lpOverlapped,?DWORD?dwFlags)?{?????LPPER_IO_OPERATION_DATA?lpPerIOData?=?(LPPER_IO_OPERATION_DATA)lpOverlapped;?????if(dwError!=0||cbTransferred==0)?????{?????????closesocket(lpPerIOData->sClient);?????????HeapFree(GetProcessHeap(),0,lpPerIOData);?????}?????else?????{?????????lpPerIOData->szMessage[cbTransferred]?=?'\0';?????????send(lpPerIOData->sClient,?lpPerIOData->szMessage,?cbTransferred,?0);??????????????????memset(&lpPerIOData->overlap,?0,?sizeof(WSAOVERLAPPED));?????????lpPerIOData->Buffer.len?=?MSGSIZE;?????????lpPerIOData->Buffer.buf?=?lpPerIOData->szMessage;?????????WSARecv(lpPerIOData->sClient,?????????????????&lpPerIOData->Buffer,?????????????????1,?????????????????&lpPerIOData->NumberOfBytesRecvd,?????????????????&lpPerIOData->Flags,?????????????????&lpPerIOData->overlap,?????????????????CompletionRoutine);?????}?}?
用完成例程來實現重疊I/O比用事件通知簡單得多。在這個模型中,主線程只用不停的接受連接即可;輔助線程判斷有沒有新的客戶端連接被建立,如果有,就為那個客戶端套接字激活一個異步的WSARecv操作,然后調用SleepEx使線程處于一種可警告的等待狀態,以使得I/O完成后 CompletionROUTINE可以被內核調用。如果輔助線程不調用SleepEx,則內核在完成一次I/O操作后,無法調用完成例程(因為完成例程的運行應該和當初激活WSARecv異步操作的代碼在同一個線程之內)。?
完成例程內的實現代碼比較簡單,它取出接收到的數據,然后將數據原封不動的發送給客戶端,最后重新激活另一個WSARecv異步操作。注意,在這里用到了 “尾隨數據”。我們在調用WSARecv的時候,參數lpOverlapped實際上指向一個比它大得多的結構 PER_IO_OPERATION_DATA,這個結構除了WSAOVERLAPPED以外,還被我們附加了緩沖區的結構信息,另外還包括客戶端套接字等重要的信息。這樣,在完成例程中通過參數lpOverlapped拿到的不僅僅是WSAOVERLAPPED結構,還有后邊尾隨的包含客戶端套接字和接收數據緩沖區等重要信息。這樣的C語言技巧在我后面介紹完成端口的時候還會使用到。
總結
以上是生活随笔為你收集整理的套接字I/O模型-重叠I/O的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。