WinSock I/O 模型 -- OVERLAPPED I/O 模型
簡(jiǎn)介
OVERLAPPED I/O 模型也是 WinSock 中常見(jiàn)的異步 I/O 模型,相比于我們之前提到的 Select 模型,WSAAsyncSelect 模型 和 WSAEventSelect 模型有更好的性能.
為了方便描述,下文我們將稱 Overlapped I/O 模型為 “重疊模型”.
重疊模型的基本設(shè)計(jì)原理便是讓應(yīng)用程序使用一個(gè)
重疊的數(shù)據(jù)結(jié)構(gòu)(Overlapped),一次投遞一個(gè)或多個(gè) Winsock I/O 請(qǐng)求。針對(duì)那些提交的請(qǐng)求,在它們完成
之后,應(yīng)用程序可為它們提供服務(wù)
使用這個(gè)模型,網(wǎng)絡(luò)應(yīng)用程序通過(guò)接收以 Windows 消息為基礎(chǔ)的網(wǎng)絡(luò)事件通知來(lái)處理網(wǎng)絡(luò)請(qǐng)求。
這篇文章我們就來(lái)看看如何使用 重疊 I/O 相關(guān)的 api 來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的 TCP 服務(wù)器.
這里我們介紹基于 Event 的實(shí)現(xiàn).
API 基礎(chǔ)
這里我們不再介紹 WSAEvent 類型相關(guān)的API,之前的文章中已經(jīng)涉及過(guò).
Overlapped 結(jié)構(gòu)體
對(duì)于該結(jié)構(gòu)體,官方的描述為:
一個(gè)包含異步輸入輸出任務(wù)信息的結(jié)構(gòu)體
對(duì)于該結(jié)構(gòu)體中的字段,我們這里不詳細(xì)描述,因?yàn)榇蟛糠蛛m然當(dāng)前官方文檔中有詳細(xì)描述,但是同時(shí)也聲明了未來(lái)可能會(huì)改變,因此我們的應(yīng)用程序不應(yīng)該依賴于這些字段的任何特定值. 而是應(yīng)該通過(guò)對(duì)應(yīng)的 API 方法來(lái)獲取自己感興趣的信息.
使用是應(yīng)該總是將所有字段置為 0 或這 NULL, 除了 hEvent 字段.
唯一非常重要的字段是:
hEvent:一個(gè) WSAEvent 事件的 handle. 當(dāng)與當(dāng)前 Overlapped 結(jié)構(gòu)體關(guān)聯(lián)的異步任務(wù)完成時(shí),該 hEvent 會(huì)被觸發(fā).
WSAGetOverlappedResult
WSAGetOverlappedResult 用于獲取某 SOCKET 異步任務(wù)的結(jié)果.
BOOL WSAAPI WSAGetOverlappedResult(SOCKET s,LPWSAOVERLAPPED lpOverlapped,LPDWORD lpcbTransfer,BOOL fWait,LPDWORD lpdwFlags );AcceptEx
該 API 也可以在 重疊 I/O 模式下使用,并且該方法的性能高于傳統(tǒng)的 accept 方法,這里我們?yōu)榱撕?jiǎn)單,先不使用 AcceptEx 方法,在 IOCP 模式我們?cè)俳榻B該方法.
WSARecv
WSARecv 用于從一個(gè)已經(jīng)連接的 SOCKET 接收數(shù)據(jù).
int WSAAPI WSARecv(SOCKET s,LPWSABUF lpBuffers,DWORD dwBufferCount,LPDWORD lpNumberOfBytesRecvd,LPDWORD lpFlags,LPWSAOVERLAPPED lpOverlapped,LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine );WSASend 與 WSARecv 類似,我們不再贅述.
實(shí)現(xiàn)思路
實(shí)例
#include <winsock2.h> #include <windows.h> #include <stdio.h>#define _WINSOCK_DEPRECATED_NO_WARNINGS #pragma comment(lib,"ws2_32.lib")#define PORT 8080 #define DATA_BUFSIZE 8192typedef struct _SOCKET_CONTEXT {CHAR Buffer[DATA_BUFSIZE];WSABUF DataBuf;SOCKET Socket;WSAOVERLAPPED Overlapped;DWORD BytesSEND;DWORD BytesRECV; } SOCKET_CONTEXT, * LPSOCKET_CONTEXT;DWORD WINAPI ProcessIO(LPVOID lpParameter);DWORD EventTotal = 0; WSAEVENT EventArray[WSA_MAXIMUM_WAIT_EVENTS]; LPSOCKET_CONTEXT SocketArray[WSA_MAXIMUM_WAIT_EVENTS]; CRITICAL_SECTION CriticalSection;int main() {WSADATA wsaData;SOCKET ListenSocket, AcceptSocket;SOCKADDR_IN Addr;DWORD Flags;DWORD ThreadId;DWORD RecvBytes;// 我們是多線程程序,鎖是必不可少的InitializeCriticalSection(&CriticalSection);if (WSAStartup(0x0202, &wsaData) != 0) {printf("WSAStartup() failed with error %d\n", WSAGetLastError());return 1;}if ((ListenSocket = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {printf("socket() failed with error %d\n", WSAGetLastError());return 1;}Addr.sin_family = AF_INET;Addr.sin_addr.s_addr = htonl(INADDR_ANY);Addr.sin_port = htons(PORT);if (bind(ListenSocket, (PSOCKADDR) &Addr, sizeof(Addr)) == SOCKET_ERROR) {printf("bind() failed with error %d\n", WSAGetLastError());return 1;}if (listen(ListenSocket, 10)) {printf("listen() failed with error %d\n", WSAGetLastError());return 1;}if ((AcceptSocket = WSASocketW(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET) {printf("Failed to get a socket %d\n", WSAGetLastError());return 1;}if ((EventArray[0] = WSACreateEvent()) == WSA_INVALID_EVENT) {printf("WSACreateEvent() failed with error %d\n", WSAGetLastError());return 1;}// 創(chuàng)建子線程,用來(lái)處理異步任務(wù)的結(jié)果if (CreateThread(NULL, 0, ProcessIO, NULL, 0, &ThreadId) == NULL) {printf("CreateThread() failed with error %d\n", GetLastError());return 1;}EventTotal = 1;while(TRUE) {// 阻塞的接收新的客戶端連接if ((AcceptSocket = accept(ListenSocket, NULL, NULL)) == INVALID_SOCKET) {printf("accept() failed with error %d\n", WSAGetLastError());return 1;}EnterCriticalSection(&CriticalSection);// 新連接到來(lái),為該新連接創(chuàng)建的必要的數(shù)據(jù)結(jié)構(gòu),維護(hù)該SOCKET的信息if ((SocketArray[EventTotal] = (LPSOCKET_CONTEXT) GlobalAlloc(GPTR, sizeof(SOCKET_CONTEXT))) == NULL) {printf("GlobalAlloc() failed with error %d\n", GetLastError());return 1;}// 為該 SOCKE 創(chuàng)建關(guān)聯(lián)的 OVERLAPPED 結(jié)構(gòu)體,// 初始化 DataBuf 字段,無(wú)論我們是接收數(shù)據(jù)還是發(fā)送數(shù)據(jù),我們都會(huì)使用它SocketArray[EventTotal]->Socket = AcceptSocket;ZeroMemory(&(SocketArray[EventTotal]->Overlapped), sizeof(OVERLAPPED));SocketArray[EventTotal]->BytesSEND = 0;SocketArray[EventTotal]->BytesRECV = 0;SocketArray[EventTotal]->DataBuf.len = DATA_BUFSIZE;SocketArray[EventTotal]->DataBuf.buf = SocketArray[EventTotal]->Buffer;// 初始化該 Overlapped 結(jié)構(gòu)體的 hEvent 字段,我們異步完成時(shí),我們便可以通過(guò)該事件得到通知// 這樣我們便不需要輪詢?cè)摦惒饺蝿?wù)的結(jié)果,而是直接等到該 Event 被觸發(fā),然后區(qū)處理便可.if ((SocketArray[EventTotal]->Overlapped.hEvent = EventArray[EventTotal] = WSACreateEvent()) == WSA_INVALID_EVENT) {printf("WSACreateEvent() failed with error %d\n", WSAGetLastError());return 1;}// 從該連接上讀取數(shù)據(jù). Flags = 0;if (WSARecv(SocketArray[EventTotal]->Socket, &(SocketArray[EventTotal]->DataBuf), 1, &RecvBytes, &Flags, &(SocketArray[EventTotal]->Overlapped), NULL) == SOCKET_ERROR) {if (WSAGetLastError() != ERROR_IO_PENDING) {printf("WSARecv() failed with error %d\n", WSAGetLastError());return 1;}// else 表示我們已經(jīng)成功的提交了異步讀任務(wù),該任務(wù)目前還在進(jìn)行中。// 當(dāng)它完成時(shí),我們?cè)谧泳€程中處理} // else 說(shuō)明我們已經(jīng)成功的讀取到了數(shù)據(jù),// 讀取到的數(shù)據(jù)存儲(chǔ)在 DataBuf 中, // RecvBytes 存儲(chǔ)接收到的數(shù)據(jù)長(zhǎng)度EventTotal++;LeaveCriticalSection(&CriticalSection);if (WSASetEvent(EventArray[0]) == FALSE) {printf("WSASetEvent() failed with error %d\n", WSAGetLastError());return 1;}} }DWORD WINAPI ProcessIO(LPVOID lpParameter) {DWORD Index;DWORD Flags;LPSOCKET_CONTEXT SocketContext;DWORD BytesTransferred;DWORD i;DWORD RecvBytes, SendBytes;while(TRUE) {// 等待我們提交的異步任務(wù)完成的事件if ((Index = WSAWaitForMultipleEvents(EventTotal, EventArray, FALSE, WSA_INFINITE, FALSE)) == WSA_WAIT_FAILED) {printf("WSAWaitForMultipleEvents() failed %d\n", WSAGetLastError());return 0;}if ((Index - WSA_WAIT_EVENT_0) == 0) {WSAResetEvent(EventArray[0]);continue;}SocketContext = SocketArray[Index - WSA_WAIT_EVENT_0];WSAResetEvent(EventArray[Index - WSA_WAIT_EVENT_0]); // ResetEvent,以便后邊重用該事件// 獲取當(dāng)前完成的異步任務(wù)的結(jié)果if (WSAGetOverlappedResult(SocketContext->Socket, &(SocketContext->Overlapped), &BytesTransferred, FALSE, &Flags) == FALSE || BytesTransferred == 0) {printf("Closing socket %d\n", SocketContext->Socket);if (closesocket(SocketContext->Socket) == SOCKET_ERROR) {printf("closesocket() failed with error %d\n", WSAGetLastError());} GlobalFree(SocketContext);WSACloseEvent(EventArray[Index - WSA_WAIT_EVENT_0]);// Cleanup SocketArray and EventArray by removing the socket event handle// and socket information structure if they are not at the end of the arrayEnterCriticalSection(&CriticalSection);if ((Index - WSA_WAIT_EVENT_0) + 1 != EventTotal)for (i = Index - WSA_WAIT_EVENT_0; i < EventTotal; i++) {EventArray[i] = EventArray[i + 1];SocketArray[i] = SocketArray[i + 1];}EventTotal--;LeaveCriticalSection(&CriticalSection);continue;}if (SocketContext->BytesRECV == 0) {SocketContext->BytesRECV = BytesTransferred;SocketContext->BytesSEND = 0;} else {SocketContext->BytesSEND += BytesTransferred;}if (SocketContext->BytesRECV > SocketContext->BytesSEND) {// 重置 Overlapped 結(jié)構(gòu)體,我們要重用這個(gè)結(jié)構(gòu)體,提交一個(gè)新的任務(wù)ZeroMemory(&(SocketContext->Overlapped), sizeof(WSAOVERLAPPED));SocketContext->Overlapped.hEvent = EventArray[Index - WSA_WAIT_EVENT_0];SocketContext->DataBuf.buf = SocketContext->Buffer + SocketContext->BytesSEND;SocketContext->DataBuf.len = SocketContext->BytesRECV - SocketContext->BytesSEND;if (WSASend(SocketContext->Socket, &(SocketContext->DataBuf), 1, &SendBytes, 0, &(SocketContext->Overlapped), NULL) == SOCKET_ERROR) {if (WSAGetLastError() != ERROR_IO_PENDING) {printf("WSASend() failed with error %d\n", WSAGetLastError());return 0;}}} else {SocketContext->BytesRECV = 0;// Now that there are no more bytes to send post another WSARecv() requestFlags = 0;ZeroMemory(&(SocketContext->Overlapped), sizeof(WSAOVERLAPPED));SocketContext->Overlapped.hEvent = EventArray[Index - WSA_WAIT_EVENT_0];SocketContext->DataBuf.len = DATA_BUFSIZE;SocketContext->DataBuf.buf = SocketContext->Buffer;if (WSARecv(SocketContext->Socket, &(SocketContext->DataBuf), 1, &RecvBytes, &Flags, &(SocketContext->Overlapped), NULL) == SOCKET_ERROR) {if (WSAGetLastError() != ERROR_IO_PENDING) {printf("WSARecv() failed with error %d\n", WSAGetLastError());return 0;}}}} }END !!!
總結(jié)
以上是生活随笔為你收集整理的WinSock I/O 模型 -- OVERLAPPED I/O 模型的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: PID控制器开发笔记之八:带死区的PID
- 下一篇: 奇妙的安全旅行之RSA算法