日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) >

WinSock I/O 模型 -- OVERLAPPED I/O 模型

發(fā)布時(shí)間:2024/7/23 44 豆豆
生活随笔 收集整理的這篇文章主要介紹了 WinSock I/O 模型 -- OVERLAPPED I/O 模型 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

簡(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)體

typedef struct _OVERLAPPED {ULONG_PTR Internal;ULONG_PTR InternalHigh;union {struct {DWORD Offset;DWORD OffsetHigh;} DUMMYSTRUCTNAME;PVOID Pointer;} DUMMYUNIONNAME;HANDLE hEvent; } OVERLAPPED, *LPOVERLAPPED;

對(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 );
  • s: SOCKET s 為當(dāng)通過(guò)特定 API(AcceptEx, ConnectEx, DisconnectEx, TransmitFile, TransmitPackets, WSARecv, WSARecvFrom, LPFN_WSARECVMSG (WSARecvMsg), WSASend, WSASendMsg, WSASendTo, 和 WSAIoctl) 添加這個(gè)異步任務(wù)時(shí),這個(gè)異步任務(wù)所關(guān)聯(lián)的 SOCKET。
  • lpOverlapped: 一個(gè) OVERLAPPED 結(jié)構(gòu)體的指針,為添加該異步任務(wù)時(shí)所使用的 Overlapped 結(jié)構(gòu)體. 該參數(shù)不能為 NULL.
  • lpcbTransfer: 返回當(dāng)前異步任務(wù)上已經(jīng)傳輸?shù)淖止?jié)數(shù)(發(fā)送或者接收)。該參數(shù)不能為 NULL
  • fWait:指定當(dāng)前方法調(diào)用是否等待當(dāng)前異步任務(wù)結(jié)束. 當(dāng)指定為 TRUE時(shí),該方法會(huì)一直阻塞直到當(dāng)前異步任務(wù)完成. 當(dāng)指定為 FALSE 時(shí),如果當(dāng)前異步任務(wù)還未完成,這個(gè)方法會(huì)返回 FALSE, 此時(shí)調(diào)用 WSAGetLastError 將會(huì)返回 WSA_IO_INCOMPLETE。
  • 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 );
  • s: SOCKET handle
  • lpBufffers: 一個(gè) WSABuf 結(jié)構(gòu)體的數(shù)組. 該結(jié)構(gòu)體比較簡(jiǎn)單,我們?cè)趯?shí)例小節(jié)描述其用法.
  • dwBufferCount: lpBuffers 數(shù)組中元素的數(shù)量
  • lpNumberOfBytesRecvd: 當(dāng)此次方法調(diào)用,函數(shù)返回時(shí)已經(jīng)成功的在 SOCKET 上讀取到了數(shù)據(jù),這個(gè)參數(shù)保存讀取到的字節(jié)數(shù). 當(dāng) lpOverlapped 參數(shù)不為空時(shí),該參數(shù)可以為空.
  • lpOverlapped: 與當(dāng)前異步接收任務(wù)關(guān)聯(lián)的 Overlapped 結(jié)構(gòu)體.
  • lpCompletionRoutine: 本文中我們使用基于事件的重疊I/O模型,因此我們不使用這個(gè)字段.
  • 返回值: 如果當(dāng)前讀操作立馬成功,返回值為 0. 否則,返回 SOCKET_ERROR. 具體的錯(cuò)誤碼通過(guò) WSAGetLastError 獲取。 如果具體的錯(cuò)誤碼為 WSA_IO_PENDING 表明當(dāng)前異步任務(wù)已經(jīng)成功提交,在該任務(wù)完成后 lpCompletionRoutine 會(huì)被調(diào)用或者 Overlapped 結(jié)構(gòu)體中的 hEvent 事件會(huì)被觸發(fā)。本文,我們將依賴于 hEvent 參數(shù)來(lái)處理異步完成的任務(wù). 對(duì)于其他的錯(cuò)誤碼,請(qǐng)參考該 API 的官方文檔.
  • WSASend 與 WSARecv 類似,我們不再贅述.

    實(shí)現(xiàn)思路

  • 創(chuàng)建一個(gè) socket 作為監(jiān)聽(tīng) socket
  • 創(chuàng)建子線程用于等待并處理異步 I/O 任務(wù)的結(jié)果。
  • 在主線程中循環(huán)等待新連接的到來(lái)。注意,這里我們?yōu)榱撕?jiǎn)單使用阻塞的 Accept 方法。 使用 AcceptEx 方法可以異步的來(lái)接收新的連接。 但是我們使用較簡(jiǎn)單的 Accept 方法.
  • 在主線程中,當(dāng)新連接到來(lái),接收它,并為他創(chuàng)建對(duì)應(yīng)的 OVERLAPPED 結(jié)構(gòu)體和 WSAEvent 對(duì)象。將 WSAEvent 對(duì)象設(shè)置到 OVERLAPPED 對(duì)象的 hEvent 字段. 然后使用 WSARecv api 來(lái)從該客戶端鏈接上接收數(shù)據(jù). 注意該讀不會(huì)阻塞主線程,它是異步的.
  • 在子線程中,使用 WSAWaitForMultipleEvents 來(lái)等待我們所創(chuàng)建中的所有 Event 中任何一個(gè)被觸發(fā)的事件. 否則阻塞子線程.
  • 當(dāng)有新的 event 被觸發(fā)時(shí),使用 WSAGetOverlappedResult 來(lái)獲取當(dāng)前任務(wù)的完成結(jié)果, 并處理它(一般都會(huì)再次提交新的異步 I/O 任務(wù)).
  • 實(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)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。