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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

WinSock I/O 模型 -- WSAEventSelect 模型

發(fā)布時間:2024/7/23 编程问答 46 豆豆
生活随笔 收集整理的這篇文章主要介紹了 WinSock I/O 模型 -- WSAEventSelect 模型 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

簡介

WSAEventSelect 模型也是 WinSock 中最常見的異步 I/O 模型。

這篇文章我們就來看看如何使用 WSAEventSelect api 來實現(xiàn)一個簡單的 TCP 服務(wù)器.

API 基礎(chǔ)

WSAEventSelect

WSAEventSelect 用來把一個 SOCKET 對象和一個 WSAEVENT 對象關(guān)聯(lián)起來。 lNetworkEvents 表示我們關(guān)心的 FD_XXX 網(wǎng)絡(luò)事件. 如果關(guān)心多個 SOCKET 事件,可以使用 OR 的方式指定多個 FD_XXX 標(biāo)志。

int WSAAPI WSAEventSelect(SOCKET s,WSAEVENT hEventObject,long lNetworkEvents );

當(dāng)特定的事件發(fā)生在相應(yīng)的 SOCKET 上,該 SOCKET 上接下來的事件將會被阻塞,直到當(dāng)前的事件被應(yīng)用處理. 該事件被處理之后,接下來的事件將可以被進(jìn)一步觸發(fā).

事件處理函數(shù)
FD_READrecv, recvFrom, WSARecv, WSARecvEx, WSARecvFrom
FD_WRITEsend, sendTo, WSASend, WSASentTo
FD_OOBrecv, recvFrom, WSARecv, WSARecvEx, WSARecvFrom
FD_ACCEPTaccept, AcceptEx, WSAAccept
FD_CONNECTNone
FD_CLOSENone
FD_QOSWSAIoctl (with SIO_GET_QOS)
FD_GROUP_QOSReserved
FD_ROUTINE_INTERFACE_CHANGEWSAIoctl (with SIO_ROUTINE_INTERFACE_CHANGE)
FD_ADDRESS_LIST_CHANGEWSAIoctl (with SIO_ADDRESS_LIST_CHANGE)
WSAEvent

WSACreateEvent 方法用來創(chuàng)建一個 WSAEvent 對象

WSAEVENT WSAAPI WSACreateEvent();

WSAWaitForMultipleEvents 用于等待一組事件中的一個或全部被觸發(fā)。

DWORD WSAAPI WSAWaitForMultipleEvents(DWORD cEvents,const WSAEVENT *lphEvents,BOOL fWaitAll,DWORD dwTimeout,BOOL fAlertable );
  • cEvents:指定 lphEvents 數(shù)組中事件對象的數(shù)量。 該參數(shù)的最大值是 WSA_MAXIMUM_WAIT_EVENTS (64)
  • lphEvents:事件對象的集合
  • fWaitAll: 指定等待 lphEvents 中所有事件被觸發(fā)或者其中之一被觸發(fā)。 如果指定為 TRUE, 那么該函數(shù)只有在所有事件對象都被觸發(fā)之后才會返回。 如果指定為 FALSE, 當(dāng)事件集合中任何一個事件被觸發(fā)之后,該方法就會返回。如果在這種情況下有多個事件對象被觸發(fā),那個返回值將會返回該事件集合中索引值最小的索引值. 索引值減去 WSA_WAIT_EVENT_0 便是指向 lphEvents 中被觸發(fā)的事件的索引值.
  • dwTimeout: 如果在 timeout 事件間隔內(nèi),沒有事件被觸發(fā),函數(shù)不會一直阻塞,而是在等待 timeout 毫秒后返回。 指定該參數(shù)為 WSA_INFINITE, 該函數(shù)會一直等待,直到有事件被觸發(fā). 指定該參數(shù)為 0, 該函數(shù)會立即返回.
  • fAlertable: 略

WSAEnumNetworkEvents 用于查詢當(dāng)前 SOCKET 上觸發(fā)事件對象 hEventObject 的對應(yīng) socket 事件(FD_READ, FD_WRITE 等).

int WSAAPI WSAEnumNetworkEvents(SOCKET s,WSAEVENT hEventObject,LPWSANETWORKEVENTS lpNetworkEvents );

實現(xiàn)思路

  • 創(chuàng)建一個 socket 作為監(jiān)聽 socket
  • 使用 WSAEventSelect 監(jiān)聽該 SOCKET 上的網(wǎng)絡(luò)事件
  • 使用 WSAWaitForMultipleEvents 等待 SOCKET 事件
  • 當(dāng) SOCKET 上有事件被觸發(fā),使用 WSAEnumNetworkEvents 查詢具體的 SOCKET 事件,并使用相應(yīng)的 API 處理事件.
  • 當(dāng)有新的 SOCKET 連接到來,接收該連接,重復(fù) 2-4 步驟.
  • 實例

    接下來我們通過一個實例來看看如何實現(xiàn).

    #include <winsock2.h> #include <windows.h> #include <stdio.h>#pragma comment(lib,"ws2_32.lib")#define PORT 8080 #define DATA_BUFSIZE 8192typedef struct _SOCKET_CONTEXT {CHAR Buffer[DATA_BUFSIZE];WSABUF DataBuf;SOCKET Socket;DWORD BytesSEND;DWORD BytesRECV; } SOCKET_CONTEXT, * LPSOCKET_CONTEXT;BOOL CreateSocketInformation(SOCKET s); void FreeSocketInformation(DWORD Event);// 這里我們維護(hù)了如下數(shù)據(jù)結(jié)構(gòu): // EeventArray: 我們?yōu)槊總€ SOCKET 對象創(chuàng)建一個對應(yīng)的事件對象,以便我們能監(jiān)聽該 SOCKET 上的網(wǎng)絡(luò)事件 // SocketArray: 毫無疑問,我們也需要維護(hù)所有SOCKET連接的的數(shù)組。其中包含 Listen SocketDWORD EventTotal = 0; WSAEVENT EventArray[WSA_MAXIMUM_WAIT_EVENTS]; LPSOCKET_CONTEXT SocketArray[WSA_MAXIMUM_WAIT_EVENTS];int main() {SOCKET ListenSocket;SOCKET AcceptSocket;SOCKADDR_IN Addr;LPSOCKET_CONTEXT SocketContext;WSANETWORKEVENTS NetworkEvents;DWORD Event;WSADATA wsaData;DWORD Flags;DWORD RecvBytes;DWORD SendBytes;// 初始化 Listen Socket 對象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;}if (CreateSocketInformation(ListenSocket) == FALSE) {printf("CreateSocketInformation() failed!\n");return 1;}// 在調(diào)用 listen api 之前,我們需要使用 WSAEventSelect 需要將 ListenSocket 與一個 WSAEvent 對象關(guān)聯(lián)起來,這里我們僅僅關(guān)系 FD_ACCEPT 和 FS_CLOSE 事件.// 當(dāng)這兩個事件之一被觸發(fā),我們編譯可以從 EventArray[0] 上查詢到這些實際,以便進(jìn)行處理if (WSAEventSelect(ListenSocket, EventArray[EventTotal - 1], FD_ACCEPT | FD_CLOSE) == SOCKET_ERROR) {printf("WSAEventSelect() 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;}while(TRUE) {// 等待當(dāng)前所有 socket 上的網(wǎng)絡(luò)事件被觸發(fā)//// 這里我們 fWait = FALSE, 也就是說任何一個 SOCKET 上有網(wǎng)絡(luò)之間,// 我們便停止等待,開始處理該事件//// dwTimeout = WSA_INFINITE, 如果沒有網(wǎng)絡(luò)事件發(fā)生,我們就一直等待,// 直到有網(wǎng)絡(luò)事件發(fā)生//// 這里 EventTotal 會隨著客戶端連接的到來增加,同時我們會創(chuàng)建對應(yīng)的 Event對象,// 并放入 EventArrayif ((Event = WSAWaitForMultipleWSAWaitForEvents(EventTotal, EventArray, FALSE, WSA_INFINITE, FALSE)) == WSA_WAIT_FAILED) {printf("WSAWaitForMultipleEvents() failed with error %d\n", WSAGetLastError());return 1;}// 程序運行到這里,已經(jīng)有網(wǎng)絡(luò)事件發(fā)生了,我們使用WSAEnumNetworkEvents 查詢到底是什么網(wǎng)絡(luò)事件, // 查詢結(jié)果保存在 NetworkEvents 對象上// 注意,在 API 章節(jié)我們已經(jīng)說明,WSAWaitForMultipleWSAWaitForEvents 的返回值減去 WSA_WAIT_EVENT_0 才是對應(yīng)的 EventArray 中被觸發(fā)的事件的索引值if (WSAEnumNetworkEvents(SocketArray[Event - WSA_WAIT_EVENT_0]->Socket,EventArray[Event - WSA_WAIT_EVENT_0], &NetworkEvents) == SOCKET_ERROR) {printf("WSAEnumNetworkEvents() failed with error %d\n", WSAGetLastError());return 1;}// 檢查當(dāng)前事件是否是 FD_ACCEPT// 如果是 FD_ACCEPT事件,使用 accept 接收新的連接。if (NetworkEvents.lNetworkEvents & FD_ACCEPT) {if (NetworkEvents.iErrorCode[FD_ACCEPT_BIT] != 0) {printf("FD_ACCEPT failed with error %d\n", NetworkEvents.iErrorCode[FD_ACCEPT_BIT]);break;}if ((AcceptSocket = accept(SocketArray[Event - WSA_WAIT_EVENT_0]->Socket, NULL, NULL)) == INVALID_SOCKET) {printf("accept() failed with error %d\n", WSAGetLastError());break;}if (EventTotal > WSA_MAXIMUM_WAIT_EVENTS) {printf("Too many connections - closing socket...\n");closesocket(AcceptSocket);break;}// 接收新的連接之后,為該 SOCKET 創(chuàng)建 WSAEvent對象(在CreateSocketInformation 實現(xiàn))// 然后監(jiān)聽該 SOCKET 的 FD_READ, FD_WRITE, FD_CLOSE 事件CreateSocketInformation(AcceptSocket);if (WSAEventSelect(AcceptSocket, EventArray[EventTotal - 1], FD_READ|FD_WRITE|FD_CLOSE) == SOCKET_ERROR) {printf("WSAEventSelect() failed with error %d\n", WSAGetLastError());return 1;}printf("Socket %d got connected...\n", AcceptSocket);}// 檢查當(dāng)前事件是否是 FD_READ 或者 FD_WRITEif (NetworkEvents.lNetworkEvents & FD_READ || NetworkEvents.lNetworkEvents & FD_WRITE) {// 檢查是不是發(fā)生了讀錯誤if (NetworkEvents.lNetworkEvents & FD_READ && NetworkEvents.iErrorCode[FD_READ_BIT] != 0) {printf("FD_READ failed with error %d\n", NetworkEvents.iErrorCode[FD_READ_BIT]);break;}// 檢查是不是發(fā)生了寫錯誤if (NetworkEvents.lNetworkEvents & FD_WRITE && NetworkEvents.iErrorCode[FD_WRITE_BIT] != 0) {printf("FD_WRITE failed with error %d\n", NetworkEvents.iErrorCode[FD_WRITE_BIT]);break;}SocketContext = SocketArray[Event - WSA_WAIT_EVENT_0];// Read data only if the receive buffer is emptyif (SocketContext->BytesRECV == 0) {SocketContext->DataBuf.buf = SocketContext->Buffer;SocketContext->DataBuf.len = DATA_BUFSIZE;Flags = 0;if (WSARecv(SocketContext->Socket, &(SocketContext->DataBuf), 1, &RecvBytes, &Flags, NULL, NULL) == SOCKET_ERROR) {if (WSAGetLastError() != WSAEWOULDBLOCK) {printf("WSARecv() failed with error %d\n", WSAGetLastError());FreeSocketInformation(Event - WSA_WAIT_EVENT_0);return 1;}} else {printf("WSARecv() is working!\n");SocketContext->BytesRECV = RecvBytes;}}if (SocketContext->BytesRECV > SocketContext->BytesSEND) {SocketContext->DataBuf.buf = SocketContext->Buffer + SocketContext->BytesSEND;SocketContext->DataBuf.len = SocketContext->BytesRECV - SocketContext->BytesSEND;if (WSASend(SocketContext->Socket, &(SocketContext->DataBuf), 1, &SendBytes, 0, NULL, NULL) == SOCKET_ERROR) {if (WSAGetLastError() != WSAEWOULDBLOCK) {printf("WSASend() failed with error %d\n", WSAGetLastError());FreeSocketInformation(Event - WSA_WAIT_EVENT_0);return 1;}// A WSAEWOULDBLOCK error has occurred. An FD_WRITE event will be posted// when more buffer space becomes available} else {printf("WSASend() is fine! Thank you...\n");SocketContext->BytesSEND += SendBytes;if (SocketContext->BytesSEND == SocketContext->BytesRECV) {SocketContext->BytesSEND = 0;SocketContext->BytesRECV = 0;}}}}// 檢查當(dāng)前事件是否是 FD_CLOSEif (NetworkEvents.lNetworkEvents & FD_CLOSE) {// 檢查是否發(fā)生了錯誤if (NetworkEvents.iErrorCode[FD_CLOSE_BIT] != 0) {printf("FD_CLOSE failed with error %d\n", NetworkEvents.iErrorCode[FD_CLOSE_BIT]);break;} else {// socket 正常關(guān)閉printf("FD_CLOSE is OK!\n");}printf("Closing socket information %d\n", SocketArray[Event - WSA_WAIT_EVENT_0]->Socket);FreeSocketInformation(Event - WSA_WAIT_EVENT_0);}}return 0; }BOOL CreateSocketInformation(SOCKET s) {LPSOCKET_CONTEXT SocketContext;if ((EventArray[EventTotal] = WSACreateEvent()) == WSA_INVALID_EVENT) {printf("WSACreateEvent() failed with error %d\n", WSAGetLastError());return FALSE;}if ((SocketContext = (LPSOCKET_CONTEXT) GlobalAlloc(GPTR, sizeof(SOCKET_CONTEXT))) == NULL) {printf("GlobalAlloc() failed with error %d\n", GetLastError());return FALSE;}// Prepare SocketInfo structure for useSocketContext->Socket = s;SocketContext->BytesSEND = 0;SocketContext->BytesRECV = 0;SocketArray[EventTotal] = SocketContext;EventTotal++;return TRUE; }void FreeSocketInformation(DWORD Event) {LPSOCKET_CONTEXT SocketContext = SocketArray[Event];DWORD i;closesocket(SocketContext->Socket);GlobalFree(SocketContext);if(WSACloseEvent(EventArray[Event]) == TRUE) {printf("WSACloseEvent() is OK!\n\n");}// Squash the socket and event arraysfor (i = Event; i < EventTotal; i++) {EventArray[i] = EventArray[i + 1];SocketArray[i] = SocketArray[i + 1];}EventTotal--; }

    END!!!

    總結(jié)

    以上是生活随笔為你收集整理的WinSock I/O 模型 -- WSAEventSelect 模型的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

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