WSAEventSelect模型分析
发布时间:2021-12-10 20:52:37 所属栏目:PHP教程 来源:互联网
导读:WSAEventSelect 是 WinSock 提供的一种异步事件通知I/O模型,与 WSAAsyncSelect模型有些类似。该模型同样是接收 FD_XXX 之类的网络事件,但是是通过事件对象句柄通知,而非像 WSAAsyncSelect一样依靠Windows的消息驱动机制。 与WSAAsyncSelect模型相同,WSAE
WSAEventSelect 是 WinSock 提供的一种异步事件通知I/O模型,与 WSAAsyncSelect模型有些类似。该模型同样是接收 FD_XXX 之类的网络事件,但是是通过事件对象句柄通知,而非像 WSAAsyncSelect一样依靠Windows的消息驱动机制。 与WSAAsyncSelect模型相同,WSAEventSelect将所有的SOCKET事件分为如下类型:(共十种) FD_READ , FD_WRITE , FD_OOB , FD_ACCEPT, FD_CONNECT , FD_CLOSE, FD_QOS , FD_GROUP_QOS , FD_ROUTING_INTERFACE_CHANGE , FD_ADDRESS_LIST_CHANGE 还有一个 FD_ALL_EVENTS 代表所有的事件 其中 FD_READ 的定义如下: #define FD_READ_BIT 0 #define FD_READ (1 << FD_READ_BIT) // = 1 其他的定义也都类似,比如: FD_ACCEPT_BIT = 3 但是并不是每一种SOCKET都能发生所有的事件,比如监听SOCKET只能发生 FD_ACCEPT 和 FD_CLOSE 事件。 在WSAEventSelect模型中,基本流程如下: 1. 创建一个事件对象数组,用于存放所有的事件对象; 2. 创建一个事件对象(WSACreateEvent); 3. 将一组你感兴趣的SOCKET事件与事件对象关联(WSAEventSelect),然后加入事件对象数组; 4. 等待事件对象数组上发生一个你感兴趣的网络事件(WSAWaitForMultipleEvents); 5. 对发生事件的事件对象查询具体发生的事件类型(WSAEnumNetworkEvents); 6. 针对不同的事件类型进行不同的处理; 7. 循环进行 .4 对于TCP服务端程序而言,在创建一个监听SOCKET,绑定至某个端口然后监听后,可以创建一个事件对象然后与 FD_ACCEPT 和 FD_CLOSE 事件 关联。在第6步时对于 FD_ACCEPT 事件可以将accept得到的SOCKET关联 FD_WRITE,FD_READ,FD_CLOSE事件后加入事件对象数组。 WSAEVENT WSACreateEvent(void); 创建一个 事件对象,实际上 WSAEVENT就是一个 HANDLE int WSAEventSelect( _In_ SOCKET s, // 需要关联的SOCKET _In_ WSAEVENT hEventObject, // 需要关联的事件对象 _In_ long lNetworkEvents // 感兴趣的网络事件,不同的事件可以用 | 合并, FD_ALL_EVENTS 代表所有的事件 ); 函数执行成功将返回 0 ,否则返回 SOCKET_ERROR, 可调用WSAGetLastError() 查看具体的错误代码 DWORD WSAWaitForMultipleEvents( _In_ DWORD cEvents, // 事件对象数组的数量 _In_ const WSAEVENT *lphEvents, // 事件对象数组 _In_ BOOL fWaitAll, // 是否等待所有的事件对象受信,显然一般情况下是false _In_ DWORD dwTimeout, // 超时时限,单位是毫秒,WSA_INFINITE 为无穷大 _In_ BOOL fAlertable // 该模型下忽略,应该设置为false ); 如果执行失败返回 WSA_WAIT_IO_COMPLETION ; 如果是超时,则返回 WSA_WAIT_TIMEOUT 如果 函数执行成功将会返回一个值,分布在 区间 [ WSA_WAIT_EVENT_0 ,(WSA_WAIT_EVENT_0+cEvents-1) ] 内 也就是说返回值 nRet-WSA_WAIT_EVENT_0 将是发生事件的对象在事件对象数组中的下标。 int WSAEnumNetworkEvents( _In_ SOCKET s, // 发生事件的SOCKET _In_ WSAEVENT hEventObject, // 发生事件的事件对象 _Out_ LPWSANETWORKEVENTS lpNetworkEvents // 发生的网络事件 ); 如果该函数执行成功将会返回0,然后可以通过查询网络事件判断到底发生了什么事件。 WSANETWORKEVENTS的定义如下: typedef struct _WSANETWORKEVENTS { long lNetworkEvents; // 发生的网络事件类型 int iErrorCode[FD_MAX_EVENTS]; // 网络事件对应的错误代码 } WSANETWORKEVENTS, FAR * LPWSANETWORKEVENTS; 比如当发生 FD_READ 事件时, 那么 networkEvent.lNetworkEvents&FD_READ 将为真,同时 networkEvent.iErrorCode[FD_READ_BIT] 标明了此时的错误代码。 代码示例:(你还需要一个 client程序,自己写或者找吧) #include <Windows.h> #include <iostream> #pragma comment(lib,"ws2_32.lib") using std::cout; using std::cin; using std::endl; using std::ends; void WSAEventServerSocket() { SOCKET server = ::socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); if(server == INVALID_SOCKET){ cout<<"创建SOCKET失败!,错误代码:"<<WSAGetLastError()<<endl; return ; } int error = 0; sockaddr_in addr_in; addr_in.sin_family = AF_INET; addr_in.sin_port = htons(15000); addr_in.sin_addr.s_addr = INADDR_ANY; error= ::bind(server,(sockaddr*)&addr_in,sizeof(sockaddr_in)); if(error == SOCKET_ERROR){ cout<<"绑定端口失败!,错误代码:"<<WSAGetLastError()<<endl; return ; } listen(server,5); if(error == SOCKET_ERROR){ cout<<"监听失败!,错误代码:"<<WSAGetLastError()<<endl; return ; } cout<<"成功监听端口 :"<<ntohs(addr_in.sin_port)<<endl; WSAEVENT eventArray[WSA_MAXIMUM_WAIT_EVENTS]; // 事件对象数组 SOCKET sockArray[WSA_MAXIMUM_WAIT_EVENTS]; // 事件对象数组对应的SOCKET句柄 int nEvent = 0; // 事件对象数组的数量 WSAEVENT event0 = ::WSACreateEvent(); ::WSAEventSelect(server,event0,FD_ACCEPT|FD_CLOSE); eventArray[nEvent]=event0; sockArray[nEvent]=server; nEvent++; while(true){ int nIndex = ::WSAWaitForMultipleEvents(nEvent,eventArray,false,WSA_INFINITE,false); if( nIndex == WSA_WAIT_IO_COMPLETION || nIndex == WSA_WAIT_TIMEOUT ){ cout<<"等待时发生错误!错误代码:"<<WSAGetLastError()<<endl; break; } nIndex = nIndex - WSA_WAIT_EVENT_0; WSANETWORKEVENTS event; SOCKET sock = sockArray[nIndex]; ::WSAEnumNetworkEvents(sock,eventArray[nIndex],&event); if(event.lNetworkEvents & FD_ACCEPT){ if(event.iErrorCode[FD_ACCEPT_BIT]==0){ if(nEvent >= WSA_MAXIMUM_WAIT_EVENTS){ cout<<"事件对象太多,拒绝连接"<<endl; continue; } sockaddr_in addr; int len = sizeof(sockaddr_in); SOCKET client = ::accept(sock,(sockaddr*)&addr,&len); if(client!= INVALID_SOCKET){ cout<<"接受了一个客户端连接 "<<inet_ntoa(addr.sin_addr)<<":"<<ntohs(addr.sin_port)<<endl; WSAEVENT eventNew = ::WSACreateEvent(); ::WSAEventSelect(client,eventNew,FD_READ|FD_CLOSE|FD_WRITE); eventArray[nEvent]=eventNew; sockArray[nEvent]=client; nEvent++; } } }else if(event.lNetworkEvents & FD_READ){ if(event.iErrorCode[FD_READ_BIT]==0){ char buf[2500]; ZeroMemory(buf,2500); int nRecv = ::recv( sock,buf,2500,0); if(nRecv>0){ cout<<"收到一个消息 :"<<buf<<endl; char strSend[] = "I recvived your message."; ::send(sock,strSend,strlen(strSend),0); } } }else if(event.lNetworkEvents & FD_CLOSE){ ::WSACloseEvent(eventArray[nIndex]); ::closesocket(sockArray[nIndex]); cout<<"一个客户端连接已经断开了连接"<<endl; for(int j=nIndex;j<nEvent-1;j++){ eventArray[j]=eventArray[j+1]; sockArray[j]=sockArray[j+1]; } nEvent--; } else if(event.lNetworkEvents & FD_WRITE ){ cout<<"一个客户端连接允许写入数据"<<endl; } } // end while ::closesocket(server); } int _tmain(int argc, _TCHAR* argv[]) { WSADATA wsaData; int error; WORD wVersionRequested; wVersionRequested = WINSOCK_VERSION; error = WSAStartup( wVersionRequested , &wsaData ); if ( error != 0 ) { WSACleanup(); return 0; } WSAEventServerSocket(); WSACleanup(); return 0; } // 解释一下,为什么我在 socket函数前面加上 :: 因为我前面写的时候本来用了thread库准备开一个线程运行Server,另一个运行Client。 结果 用了 using namespace std; 后,正好引入了bind函数(std的那个模板)把 socket的bind给覆盖了, 然后就一直是 错误了,查下错误代码是 10022(无效参数),检查时才发现的。 (编辑:应用网_丽江站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
站长推荐
热点阅读