windiows 下 WSAEventSelect模型
2017-07-31 11:11
381 查看
WSAEventSelect模型是Windows socekts提供的另一个有用异步IO模型。 该模型同样是接收 FD_XXX
之类的网络事件,但是是通过事件对象句柄通知,而非像 WSAAsyncSelect一样依靠Windows的消息驱动机制。 与WSAAsyncSelect模型很相似 ,它们都是异步的,通知应用程序的形式不同,WSAAsyncSelect以消息的形式通知,而WSAEventSelect以事件的形式通知。
与select模型相比较,select模型是主动的,应用程序主动调用select函数来检查是否发生了网络事件。WSAAsyncSelect与WSAEventSelect模型都是被动接受的。网络事件发生时,系统通知应用程序。
与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事件后加入事件对象数组。
[cpp] view
plain copy
int WSAEventSelect(
SOCKET s, //套接字句柄
WSAEVENT hEvent, //为事件对象句柄
Long lNetworkEvents); //应用程序感兴趣的网络事件集合
如果应用程序为套接字注册网络事件成功,函数返回0。否则返回SOCKET_ERROR。可以调用WSAGetLastError来获取具体的错误代码。调用该函数后,套接字自动被设为非阻塞的工作模式。如果应用程序要将套接字设置为阻塞模式,必须将lNetwork参数设为0,再次调用WSAEventSelect函数。
调用WSACreateEvent函数创建一个事件对象
[cpp] view
plain copy
WSAEVENT WSACreateEvent(void);
当网络事件到来时,与套接字关联的事件对象由未触发变为触发态。由于它是手工重置事件,应用程序需要手动将事件的状态设置为未触发态。这可以调用WSAResetEvent函数:
[cpp] view
plain copy
bool WSAResetEvent(WSAEVENT hEvent);
不再使用事件对象时要将其关闭。
[cpp] view
plain copy
bool WSACloseEvent(WSAEVENT hEvent);
WSAWaitForMultipleEvents函数可以等待网络事件的发生。它的目的是等待一个或是所有的事件对象变为已触发状态。
[cpp] view
plain copy
DWORD WSAWaitForMultipleEvents(
DWORD cEvents,
//事件对象句柄个数。至少为1,最多WSA_MAXIMUM_WAIT_EVENTS,64个。
const WSAEVENT *plhEvent,
//为指向对象句柄数组指针
BOOL fWaitAll,
//如果为TRUE,则该函数在所有事件对象都转变为已触发时才返回。如为false,只要有一个对象被触发,函数即返回。
DWORD dwTimeOUT,
//函数阻塞事件。单位为毫秒。在时间用完后函数会返回。如果为WSA_INFINITE则函数会一直等待下去。如果超时返回,函数返回WSA_WAIT_TIMEOUT。
BOOL fAlertable);
//参数说明当完成例程在系统队列中排队等待执行时,该函数是否返回。这主要应用于重叠IO模型,以后还会介绍。 该模型下忽略,应该设置为false
WSAWaitForMultipleEvents返回时,返回值会指出它返回的原因。
当fWaitAll为TRUE时:
如果返回值为WSA_TIMEOUT则表明等待超时。
WSA_EVENT_0表明所有对象都已变成触发态。等待成功。
WAIT_IO_COMPLETION说明一个或多个完成例程已经排队等待执行。
如果fWaitAll为false时:
WSA_WAIT_EVENT_0 到 WSA_WAIT_EVENT_0 + cEvent-1 范围内的值,说明有一个对象变为触发态。它在数组中的下标为: (返回值 - WSA_EVENT_0 )
如果函数调用失败,则返回WSA_WAIT_FAILED。
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]
标明了此时的错误代码。
服务端
客户端
之类的网络事件,但是是通过事件对象句柄通知,而非像 WSAAsyncSelect一样依靠Windows的消息驱动机制。 与WSAAsyncSelect模型很相似 ,它们都是异步的,通知应用程序的形式不同,WSAAsyncSelect以消息的形式通知,而WSAEventSelect以事件的形式通知。
与select模型相比较,select模型是主动的,应用程序主动调用select函数来检查是否发生了网络事件。WSAAsyncSelect与WSAEventSelect模型都是被动接受的。网络事件发生时,系统通知应用程序。
与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事件后加入事件对象数组。
[cpp] view
plain copy
int WSAEventSelect(
SOCKET s, //套接字句柄
WSAEVENT hEvent, //为事件对象句柄
Long lNetworkEvents); //应用程序感兴趣的网络事件集合
如果应用程序为套接字注册网络事件成功,函数返回0。否则返回SOCKET_ERROR。可以调用WSAGetLastError来获取具体的错误代码。调用该函数后,套接字自动被设为非阻塞的工作模式。如果应用程序要将套接字设置为阻塞模式,必须将lNetwork参数设为0,再次调用WSAEventSelect函数。
调用WSACreateEvent函数创建一个事件对象
[cpp] view
plain copy
WSAEVENT WSACreateEvent(void);
当网络事件到来时,与套接字关联的事件对象由未触发变为触发态。由于它是手工重置事件,应用程序需要手动将事件的状态设置为未触发态。这可以调用WSAResetEvent函数:
[cpp] view
plain copy
bool WSAResetEvent(WSAEVENT hEvent);
不再使用事件对象时要将其关闭。
[cpp] view
plain copy
bool WSACloseEvent(WSAEVENT hEvent);
WSAWaitForMultipleEvents函数可以等待网络事件的发生。它的目的是等待一个或是所有的事件对象变为已触发状态。
[cpp] view
plain copy
DWORD WSAWaitForMultipleEvents(
DWORD cEvents,
//事件对象句柄个数。至少为1,最多WSA_MAXIMUM_WAIT_EVENTS,64个。
const WSAEVENT *plhEvent,
//为指向对象句柄数组指针
BOOL fWaitAll,
//如果为TRUE,则该函数在所有事件对象都转变为已触发时才返回。如为false,只要有一个对象被触发,函数即返回。
DWORD dwTimeOUT,
//函数阻塞事件。单位为毫秒。在时间用完后函数会返回。如果为WSA_INFINITE则函数会一直等待下去。如果超时返回,函数返回WSA_WAIT_TIMEOUT。
BOOL fAlertable);
//参数说明当完成例程在系统队列中排队等待执行时,该函数是否返回。这主要应用于重叠IO模型,以后还会介绍。 该模型下忽略,应该设置为false
WSAWaitForMultipleEvents返回时,返回值会指出它返回的原因。
当fWaitAll为TRUE时:
如果返回值为WSA_TIMEOUT则表明等待超时。
WSA_EVENT_0表明所有对象都已变成触发态。等待成功。
WAIT_IO_COMPLETION说明一个或多个完成例程已经排队等待执行。
如果fWaitAll为false时:
WSA_WAIT_EVENT_0 到 WSA_WAIT_EVENT_0 + cEvent-1 范围内的值,说明有一个对象变为触发态。它在数组中的下标为: (返回值 - WSA_EVENT_0 )
如果函数调用失败,则返回WSA_WAIT_FAILED。
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]
标明了此时的错误代码。
服务端
#include <WINSOCK2.H> #include <iostream> /*#include <windows.h>*/ #pragma comment(lib,"WS2_32") using namespace std; 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(6000); 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 main(int argc, char * argv[]) { WSADATA wsaData; int error; WORD wVersionRequested; wVersionRequested = WINSOCK_VERSION; error = WSAStartup( wVersionRequested , &wsaData ); if ( error != 0 ) { WSACleanup(); return 0; } WSAEventServerSocket(); WSACleanup(); return 0; }
客户端
#include<stdlib.h> #include<WINSOCK2.H> #include <windows.h> #include <process.h> #include<iostream> #include<string> using namespace std; #define BUF_SIZE 64 #pragma comment(lib,"WS2_32.lib") void recv(PVOID pt) { SOCKET sHost= *((SOCKET *)pt); while(true) { char buf[BUF_SIZE];//清空接收数据的缓冲区 memset(buf,0 , BUF_SIZE); int retVal=recv(sHost,buf,sizeof(buf),0); if(SOCKET_ERROR==retVal) { int err=WSAGetLastError(); //无法立即完成非阻塞Socket上的操作 if(err==WSAEWOULDBLOCK) { Sleep(1000); printf("\nwaiting reply!"); continue; } else if(err==WSAETIMEDOUT||err==WSAENETDOWN|| err==WSAECONNRESET)//已建立连接 { printf("recv failed!"); closesocket(sHost); WSACleanup(); return ; } } Sleep(100); printf("\n%s", buf); //break; } } int main() { WSADATA wsd; SOCKET sHost; SOCKADDR_IN servAddr;//服务器地址 int retVal;//调用Socket函数的返回值 char buf[BUF_SIZE]; //初始化Socket环境 if(WSAStartup(MAKEWORD(2,2),&wsd)!=0) { printf("WSAStartup failed!\n"); return -1; } sHost=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); //设置服务器Socket地址 servAddr.sin_family=AF_INET; servAddr.sin_addr.S_un.S_addr=inet_addr("127.0.0.1"); //在实际应用中,建议将服务器的IP地址和端口号保存在配置文件中 servAddr.sin_port=htons(6000); //计算地址的长度 int sServerAddlen=sizeof(servAddr); //调用ioctlsocket()将其设置为非阻塞模式 int iMode=1; retVal=ioctlsocket(sHost,FIONBIO,(u_long FAR*)&iMode); if(retVal==SOCKET_ERROR) { printf("ioctlsocket failed!"); WSACleanup(); return -1; } //循环等待 while(true) { //连接到服务器 retVal=connect(sHost,(LPSOCKADDR)&servAddr,sizeof(servAddr)); if(SOCKET_ERROR==retVal) { int err=WSAGetLastError(); //无法立即完成非阻塞Socket上的操作 if(err==WSAEWOULDBLOCK||err==WSAEINVAL) { Sleep(1); printf("check connect!\n"); continue; } else if(err==WSAEISCONN)//已建立连接 { break; } else { printf("connection failed!\n"); closesocket(sHost); WSACleanup(); return -1; } } } unsigned long threadId=_beginthread(recv,0,&sHost);//启动一个线程接收数据的线程 while(true) { //向服务器发送字符串,并显示反馈信息 printf("input a string to send:\n"); std::string str; //接收输入的数据 std::cin>>str; //将用户输入的数据复制到buf中 ZeroMemory(buf,BUF_SIZE); strcpy(buf,str.c_str()); if(strcmp(buf,"quit")==0) { printf("quit!\n"); break; } while(true) { retVal=send(sHost,buf,strlen(buf),0); if(SOCKET_ERROR==retVal) { int err=WSAGetLastError(); if(err==WSAEWOULDBLOCK) { //无法立即完成非阻塞Socket上的操作 Sleep(5); continue; } else { printf("send failed!\n"); closesocket(sHost); WSACleanup(); return -1; } } break; } } return 0; }
相关文章推荐
- 套接字IO模型(三) WSAEventSelect模型
- WSAEventSelect-事件通知模型
- WSAEventSelect模型 用法介绍
- Windows平台下 WSAEventSelect模型 服务器
- Windows套接字I/O模型(4) -- WSAEventSelect模型
- WSAEventSelect模型
- WinSock IO模型 -- WSAEventSelect模型事件触发条件说明
- WSAEventSelect(事件选择)模型
- SOCKET编程之WSAEventSelect模型
- WinSock IO模型三: WSAEventSelect 事件机制
- Socket I/O模型之事件选择(WSAEventSelect)
- Winsock异步模式I/O模型WSAEventSelect的使用
- WSAEventSelect模型
- WSAEventSelect模型编程 详解
- IO模型(二)WSAEventSelect--事件选择机制
- WSAEventSelect模型
- (三)Socket I/O模型之事件选择(WSAEventSelect)
- Winsock异步模式I/O模型WSAEventSelect的使用
- WSAEventSelect模型
- WsaEventSelect模型