1.简介
WSAEventSelect 模型是 Windows 网络编程中的一种异步 I/O 模型,可以通过事件对象实现异步操作和事件通知。与 WSAAsyncSelect 模型相比,WSAEventSelect 模型可以同时监听多种网络事件,例如同一套接字的可读和可写事件可以同时监听,也可以同时监听多个套接字的可读事件,因此在大型程序和复杂的网络应用中更加灵活和可扩展。
使用方法
(1)创建套接字
首先需要创建一个套接字,可以使用 socket 函数创建一个 TCP 或 UDP 套接字:
SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
(2)创建事件对象
然后需要创建一个事件对象,可以使用WSACreateEvent
函数创建一个事件对象:
WSAEVENT event = ::WSACreateEvent();(3)使用 WSAEventSelect 函数
然后使用 WSAEventSelect 函数将套接字与事件对象关联,指定需要监听的网络事件:
WSAEventSelect(sock, hEvent, FD_READ | FD_WRITE | FD_CLOSE | FD_CONNECT);
(4)等待网络事件
接下来使用 WaitForSingleObject 或 WaitForMultipleObjects 函数等待事件对象的信号:
DWORD dwResult = WaitForSingleObject(hEvent, INFINITE);if (dwResult == WAIT_OBJECT_0) {// 处理网络事件}
当有网络事件发生时,事件对象会被信号,WaitForSingleObject 函数会返回 WAIT_OBJECT_0,此时可以调用 WSAGetOverlappedResult 函数获取异步操作的结果,或者直接处理网络事件。
(5)实现回调函数
可以在窗口消息循环中使用 WSAAsyncSelect 函数实现回调函数,也可以使用 WSAWaitForMultipleEvents 函数实现回调函数,具体可以参考 Microsoft 官方文档和示例代码。
3.注意事项
在使用 WSAEventSelect 模型时,需要注意以下几点:
(1)事件对象需要在异步操作完成之前一直保持有效,可以使用 WSACloseEvent 函数关闭事件对象。
(2)需要使用 WSAGetOverlappedResult 函数获取异步操作的结果,可以将套接字和事件对象关联的 WSAOVERLAPPED 结构体作为参数传递。
(3)可以使用 WSAResetEvent 函数重置事件对象,以便重复使用。
优缺点
WSAEventSelect 模型的优点是能够同时监听多种网络事件,适用于大型程序和复杂的网络应用,也可以与 Windows 事件通知机制结合使用,具有更高的灵活性和可扩展性。缺点是相对于 WSAAsyncSelect 模型更为复杂,需要在代码中实现事件循环和状态机,也需要更多的资源,例如事件对象和 WSAOVERLAPPED 结构体等。此外,WSAEventSelect 模型也是 Windows 平台特定的异步 I/O 模型,不适用于跨平台的网络应用。
5.总结
WSAEventSelect 模型是 Windows 网络编程中的一种异步 I/O 模型,可以通过事件对象实现异步操作和事件通知。与 WSAAsyncSelect 模型相比,WSAEventSelect 模型可以同时监听多种网络事件,更加灵活和可扩展。使用 WSAEventSelect 模型需要创建套接字、事件对象,使用 WSAEventSelect 函数关联套接字和事件对象,等待网络事件并处理,以及实现回调函数等步骤。需要注意事件对象需要在异步操作完成之前一直保持有效,需要使用 WSAGetOverlappedResult 函数获取异步操作的结果,可以使用 WSAResetEvent 函数重置事件对象,也需要在代码中实现事件循环和状态机等逻辑。WSAEventSelect 模型适用于大型程序和复杂的网络应用,具有更高的灵活性和可扩展性。
6.案例
#include <stdio.h>#include <iostream.h>#include <windows.h>// 初始化Winsock库CInitSock theSock;int main(){// 事件句柄和套节字句柄表WSAEVENT eventArray[WSA_MAXIMUM_WAIT_EVENTS];SOCKET sockArray[WSA_MAXIMUM_WAIT_EVENTS];int nEventTotal = 0;USHORT nPort = 4567; // 此服务器监听的端口号// 创建监听套节字SOCKET sListen = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);sockaddr_in sin;sin.sin_family = AF_INET;sin.sin_port = htons(nPort);sin.sin_addr.S_un.S_addr = INADDR_ANY;if(::bind(sListen, (sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR){printf(" Failed bind() \n");return -1;}::listen(sListen, 5);// 创建事件对象,并关联到新的套节字WSAEVENT event = ::WSACreateEvent();::WSAEventSelect(sListen, event, FD_ACCEPT|FD_CLOSE);// 添加到表中eventArray[nEventTotal] = event;sockArray[nEventTotal] = sListen;nEventTotal++;// 处理网络事件while(TRUE){// 在所有事件对象上等待int nIndex = ::WSAWaitForMultipleEvents(nEventTotal, eventArray, FALSE, WSA_INFINITE, FALSE);// 对每个事件调用WSAWaitForMultipleEvents函数,以便确定它的状态nIndex = nIndex - WSA_WAIT_EVENT_0;for(int i=nIndex; i<nEventTotal; i++){nIndex = ::WSAWaitForMultipleEvents(1, &eventArray[i], TRUE, 1000, FALSE);if(nIndex == WSA_WAIT_FAILED || nIndex == WSA_WAIT_TIMEOUT){continue;}else{// 获取到来的通知消息,WSAEnumNetworkEvents函数会自动重置受信事件WSANETWORKEVENTS event;::WSAEnumNetworkEvents(sockArray[i], eventArray[i], &event);if(event.lNetworkEvents & FD_ACCEPT) // 处理FD_ACCEPT通知消息{if(event.iErrorCode[FD_ACCEPT_BIT] == 0){if(nEventTotal > WSA_MAXIMUM_WAIT_EVENTS){printf(" Too many connections! \n");continue;}SOCKET sNew = ::accept(sockArray[i], NULL, NULL);WSAEVENT event = ::WSACreateEvent();::WSAEventSelect(sNew, event, FD_READ|FD_CLOSE|FD_WRITE);// 添加到表中eventArray[nEventTotal] = event;sockArray[nEventTotal] = sNew;nEventTotal++;}}else if(event.lNetworkEvents & FD_READ) // 处理FD_READ通知消息{if(event.iErrorCode[FD_READ_BIT] == 0){char szText[256];int nRecv = ::recv(sockArray[i], szText, strlen(szText), 0);if(nRecv > 0){szText[nRecv] = '\0';printf("接收到数据:%s \n", szText);}}}else if(event.lNetworkEvents & FD_CLOSE) // 处理FD_CLOSE通知消息{if(event.iErrorCode[FD_CLOSE_BIT] == 0){::closesocket(sockArray[i]);for(int j=i; j<nEventTotal-1; j++){sockArray[j] = sockArray[j+1];sockArray[j] = sockArray[j+1];}nEventTotal--;}}else if(event.lNetworkEvents & FD_WRITE) // 处理FD_WRITE通知消息{}}}}return 0;}
公众号: 安全狗的自我修养
抖音: haidragon
bibi: haidragonx