WinSock I/O 模型 -- OVERLAPPED I/O 模型
簡介
OVERLAPPED I/O 模型也是 WinSock 中常見的異步 I/O 模型,相比于我們之前提到的 Select 模型,WSAAsyncSelect 模型 和 WSAEventSelect 模型有更好的性能.
為了方便描述,下文我們將稱 Overlapped I/O 模型為 “重疊模型”.
重疊模型的基本設計原理便是讓應用程序使用一個
重疊的數據結構(Overlapped),一次投遞一個或多個 Winsock I/O 請求。針對那些提交的請求,在它們完成
之后,應用程序可為它們提供服務
使用這個模型,網絡應用程序通過接收以 Windows 消息為基礎的網絡事件通知來處理網絡請求。
這篇文章我們就來看看如何使用 重疊 I/O 相關的 api 來實現一個簡單的 TCP 服務器.
這里我們介紹基于 Event 的實現.
API 基礎
這里我們不再介紹 WSAEvent 類型相關的API,之前的文章中已經涉及過.
Overlapped 結構體
對于該結構體,官方的描述為:
一個包含異步輸入輸出任務信息的結構體
對于該結構體中的字段,我們這里不詳細描述,因為大部分雖然當前官方文檔中有詳細描述,但是同時也聲明了未來可能會改變,因此我們的應用程序不應該依賴于這些字段的任何特定值. 而是應該通過對應的 API 方法來獲取自己感興趣的信息.
使用是應該總是將所有字段置為 0 或這 NULL, 除了 hEvent 字段.
唯一非常重要的字段是:
hEvent:一個 WSAEvent 事件的 handle. 當與當前 Overlapped 結構體關聯的異步任務完成時,該 hEvent 會被觸發.
WSAGetOverlappedResult
WSAGetOverlappedResult 用于獲取某 SOCKET 異步任務的結果.
BOOL WSAAPI WSAGetOverlappedResult(SOCKET s,LPWSAOVERLAPPED lpOverlapped,LPDWORD lpcbTransfer,BOOL fWait,LPDWORD lpdwFlags );AcceptEx
該 API 也可以在 重疊 I/O 模式下使用,并且該方法的性能高于傳統的 accept 方法,這里我們為了簡單,先不使用 AcceptEx 方法,在 IOCP 模式我們再介紹該方法.
WSARecv
WSARecv 用于從一個已經連接的 SOCKET 接收數據.
int WSAAPI WSARecv(SOCKET s,LPWSABUF lpBuffers,DWORD dwBufferCount,LPDWORD lpNumberOfBytesRecvd,LPDWORD lpFlags,LPWSAOVERLAPPED lpOverlapped,LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine );WSASend 與 WSARecv 類似,我們不再贅述.
實現思路
實例
#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;}// 創建子線程,用來處理異步任務的結果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);// 新連接到來,為該新連接創建的必要的數據結構,維護該SOCKET的信息if ((SocketArray[EventTotal] = (LPSOCKET_CONTEXT) GlobalAlloc(GPTR, sizeof(SOCKET_CONTEXT))) == NULL) {printf("GlobalAlloc() failed with error %d\n", GetLastError());return 1;}// 為該 SOCKE 創建關聯的 OVERLAPPED 結構體,// 初始化 DataBuf 字段,無論我們是接收數據還是發送數據,我們都會使用它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 結構體的 hEvent 字段,我們異步完成時,我們便可以通過該事件得到通知// 這樣我們便不需要輪詢該異步任務的結果,而是直接等到該 Event 被觸發,然后區處理便可.if ((SocketArray[EventTotal]->Overlapped.hEvent = EventArray[EventTotal] = WSACreateEvent()) == WSA_INVALID_EVENT) {printf("WSACreateEvent() failed with error %d\n", WSAGetLastError());return 1;}// 從該連接上讀取數據. 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 表示我們已經成功的提交了異步讀任務,該任務目前還在進行中。// 當它完成時,我們在子線程中處理} // else 說明我們已經成功的讀取到了數據,// 讀取到的數據存儲在 DataBuf 中, // RecvBytes 存儲接收到的數據長度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) {// 等待我們提交的異步任務完成的事件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,以便后邊重用該事件// 獲取當前完成的異步任務的結果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 結構體,我們要重用這個結構體,提交一個新的任務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 !!!
總結
以上是生活随笔為你收集整理的WinSock I/O 模型 -- OVERLAPPED I/O 模型的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: PID控制器开发笔记之八:带死区的PID
- 下一篇: 奇妙的安全旅行之RSA算法