重疊模型的基本設計原理是讓應用程序使用重疊的數據結構,一次投遞一個或多個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;
- //第一步
- //啓動Winsock,建立監聽套接字
- ...
- //第二步
- //接收一個入站連接
- AcceptSocket = accept(ListenSocket,NULL,NULL);
- //第三步
- //建立一個重疊結構
- EventArray[EventTotal] = WSACreateEvent();
- ZeroMemory(&AcceptOverlapped, sizeof(WSAOVERLAPPED));
- AcceptOverlapped.hEvent = EventArray[EventTotal];
- DataBuf.len = DATA_BUFSIZE;
- DataBuf.buf = buffer;
- EventTotal++;
- //第四步
- //接收一個WSARecv請求,以便在套接字上接收數據
- if(SOCKET_ERROR ==
- WSARecv(AcceptSocket, &DataBuf, 1, &RecvBytes, &Flags, &AcceptOverlapped, NULL))
- {
- if(WSA_IO_PENDING != WSAGetLastError())
- {
- //出錯
- }
- }
- //處理套件子上的重疊接收
- while(TRUE)
- {
- DWORD Index;
- //第五步
- //等候重疊I/O調用結束
- Index = WSAWaitForMultipleEvents(EventTotal, EventArray, FALSE, WSA_INFINITE, FALSE);
- //索引應爲0,因爲EventArray中僅有一個事件
- //第六步
- //重置已傳信事件
- 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 ;
- }
- //對接收到的數據進行某種處理
- //DataBuf包含接收到的數據
- ...
- //第八步
- //在套接字上投遞另一個WSARecv請求
- 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;
- //第一步
- //啓動Winsock,建立監聽套接字
- ...
- //第二步
- //接受一個新連接
- AcceptSocket = accept(ListenSocket, NULL, NULL);
- //第三步
- //已經有一個接收套接字之後,開始使用帶有完成例程的重疊I/O來處理I/O
- //爲了啓動重疊I/O處理,先提交一個重疊WSARecv請求
- Flags = 0;
- ZeroMemory(&Overlapped, sizeof(WSAOVERLAPPED));
- DataBuf.len = DATA_BUFSIZE;
- DataBuf.buf = buffer;
- //第四步
- //將WSAOVERLAPPED結構指定爲一個參數,在套接字上投遞一個異步WSARecv請求並提供下面的
- //作爲完成例程的WorkerRoutine函數
- 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 ;
- }
- }
- //因爲WSAWaitForMultipleEvents()API要求在一個或多個事件對象上等待,
- //因此不得不創建一個僞事件對象。作爲一種可選方案,也可使用SleepEx作爲替代
- 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 ;
- }
- //此刻,一個重疊的WSARecv請求順利完成
- //現在可接收DataBuf變量中包含的已接收的數據了
- //處理完接收到的數據後,需要投遞另外一個重疊的WSARecv或WSASend請求
- //爲簡便起見,這裏投遞另外一個WSARecv請求
- 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語言技巧在我後面介紹完成端口的時候還會使用到。