WSAEventSelect模型:
█ 事件选择(WSAEventSelect)模型是另一个有用的异步I/O 模型。和 WSAAsyncSelect 模型类似的是,
它也允许应用程序在一个或多个套接字上,接收以事件为基础的网络事件通知,最主要的差别在于网络事件会投
递至一个事件对象句柄,而非投递到一个窗口例程。
█ 事件通知模型要求我们的应用程序针对使用的每一个套接字,首先创建一个事件对象。
创建方法是调用 WSACreateEvent 函数,它的定义如下:
WSAEVENT WSACreateEvent(void);
█ WSACreateEvent 函数的返回值很简单,就是一个创建好的事件对象句柄,接下来必须将其与某个套接字关联在一起,
同时注册自己感兴趣的网络事件类型(FD_READ、FD_WRITE、FD_ACCEPT、FD_CONNECT、FD_CLOSE等),
方法是调用 WSAEventSelect 函数,其定义如下:
int WSAEventSelect(
__in SOCKET s,
__in WSAEVENT hEventObject,
__in long lNetworkEvents
);
● s 参数代表感兴趣的套接字;
● hEventObject 参数指定要与套接字关联在一起的事件对象—用 WSACreateEvent 取得的那一个;
● lNetworkEvents 参数则对应一个“位掩码”,用于指定应用程序感兴趣的各种网络事件类型的一个组合。(这里又一个好处就是一次可以指定多个感兴趣的网络事件)
█ WSACreateEvent 创建的事件有两种工作状态,以及两种工作模式。
工作状态分别是“已传信”(signaled)和“未传信”(nonsignaled)。
工作模式则包括“人工重设”(manual reset)和“自动重设”(auto reset)。
WSACreateEvent 开始是在一种未传信的工作状态,并用一种人工重设模式,来创建事件句柄。
随着网络事件触发了与一个套接字关联在一起的事件对象,工作状态便会从“未传信”转变成“已传信”。
由于事件对象是在一种人工重设模式中创建的,所以在完成了一个I/O 请求的处理之后,
我们的应用程序需要负责将工作状态从已传信更改为未传信。要做到这一点,可调用WSAResetEvent 函数,对它的定义如下:
BOOL WSAResetEvent(
__in WSAEVENT hEvent
);
● 该函数唯一的参数便是一个事件句柄;基于调用是成功还是失败,会分别返回TRUE或FALSE。
应用程序完成了对一个事件对象的处理后,便应调用WSACloseEvent函数,释放由事件句柄使用的系统资源。
对 WSACloseEvent 函数的定义如下:
BOOL WSACloseEvent(
__in WSAEVENT hEvent
);
● 该函数也要拿一个事件句柄作为自己唯一的参数,并会在成功后返回TRUE,失败后返回FALSE。
█ 一个套接字同一个事件对象句柄关联在一起后,应用程序便可开始I/O处理;
方法是等待网络事件触发事件对象句柄的工作状态。WSAWaitForMultipleEvents函数的设计宗旨便是用来等待一个或多个事件对象句柄,
并在事先指定的一个或所有句柄进入“已传信”状态后,或在超过了一个规定的时间周期后,立即返回。
下面是 WSAWaitForMultipleEvents 函数的定义:
DWORDWSAWaitForMultipleEvents(
__in DWORD cEvents,
__in const WSAEVENT* lphEvents,
__in BOOL fWaitAll,
__in DWORD dwTimeout,
__in BOOL fAlertable
);
● cEvents 和lphEvents 参数定义了由 WSAEVENT 对象构成的一个数组。在这个数组中,
cEvents指定的是事件对象的数量,而lphEvents对应的是一个指针,用于直接引用该数组。
要注意的是,WSAWaitForMultipleEvents只能支持由 WSA_MAXIMUM_WAIT_EVENTS 对象规定的一个最大值,在此定义成64个。
因此,针对发出WSAWaitForMultipleEvents 调用的每个线程,该 I/O 模型一次最多都只能支持64个套接字。
假如想让这个模型同时管理不止64个套接字,必须创建额外的工作者线程,以便等待更多的事件对象。
● fWaitAll 参数指定了WSAWaitForMultipleEvents 如何等待在事件数组中的对象。
若设为TRUE,那么只有等 lphEvents 数组内包含的所有事件对象都已进入“已传信”状态,函数才会返回;
但若设为FALSE,任何一个事件对象进入“已传信”状态,函数就会返回。就后一种情况来说,返回值指出了到底是哪个事件对象造成了函数的返回。
通常,应用程序应将该参数设为 FALSE,一次只为一个套接字事件提供服务。
● dwTimeout参数规定了WSAWaitForMultipleEvents 最多可等待一个网络事件发生有多长时间,以毫秒为单位,这是一项“超时”设定。
超过规定的时间,函数就会立即返回,即使由 fWaitAll 参数规定的条件尚未满足也如此。
考虑到它对性能造成的影响,应尽量避免将超时值设为0。
假如没有等待处理的事件,WSAWaitForMultipleEvents便会返回 WSA_WAIT_TIMEOUT。
如 dwTimeout 设为 WSAINFINITE(永远等待),那么只有在一个网络事件传信了一个事件对象后,函数才会返回。
● fAlertable 参数,在我们使用WSAEventSelect 模型的时候,它是可以忽略的,且应设为 FALSE。
该参数主要用于在重叠式 I/O 模型中,在完成例程的处理过程中使用。
█ 若 WSAWaitForMultipleEvents 收到一个事件对象的网络事件通知,便会返回一个值,指出造成函数返回的事件对象。
这样一来,我们的应用程序便可引用事件数组中已传信的事件,并检索与那个事件对应的套接字,判断到底是在哪个套接字上,发生了什么网络事件类型。
对事件数组中的事件进行引用时,应该用WSAWaitForMultipleEvents 的返回值,减去预定义的值 WSA_WAIT_EVENT_0,得到具体的引用值(即索引位置)。
如下例所示:
Index =WSAWaitForMultipleEvents(...);
MyEvent = EventArray[Index -WSA_WAIT_EVENT_0];
█ 知道了造成网络事件的套接字后,接下来可调用 WSAEnumNetworkEvents函数,调查发生了什么类型的网络事件。
该函数定义如下:
int WSAEnumNetworkEvents(
__in SOCKET s,
__in WSAEVENT hEventObject,
__out LPWSANETWORKEVENTS lpNetworkEvents
);
● s 参数对应于造成了网络事件的套接字。
● hEventObject 参数则是可选的;它指定了一个事件句柄,对应于打算重设的那个事件对象(该函数会自动把事件对象重设)。由于我们的事件对象处在一个“已传信”状态,
所以可将它传入,令其自动成为“未传信”状态。如果不想用 hEventObject 参数来重设事件,那么可使用WSAResetEvent 函数,该函数之前已经讨论过了。
● 参数 lpNetworkEvents,代表一个指针,指向WSANETWORKEVENTS 结构,用于接收套接字上发生的网络事件类型以及可能出现的任何错误代码。(根据该函数来确定发生的网络事件)
WSANETWORKEVENTS 结构的定义如下:
typedef struct_WSANETWORKEVENTS {
long lNetworkEvents;
int iErrorCode[FD_MAX_EVENTS];
} WSANETWORKEVENTS, *LPWSANETWORKEVENTS;
● lNetworkEvents 参数指定了一个值,对应于套接字上发生的所有网络事件类型(FD_READ、FD_WRITE等)。
注意:一个事件进入传信状态时,可能会同时发生多个网络事件类型。例如,一个繁忙的服务器应用可能同时收到FD_READ 和 FD_WRITE 通知。
● iErrorCode 参数指定的是一个错误代码数组,同lNetworkEvents 中的事件关联在一起。
针对每个网络事件类型,都存在着一个特殊的事件索引,名字与事件类型的名字类似,只是要在事件名字后面添加一个“_BIT”后缀字串即可。
例如,对 FD_READ 事件类型来说,iErrorCode数组的索引标识符便是 FD_READ_BIT。下述代码片断对此进行了阐释(针对FD_READ事件):
if(NetwordEvents.lNetworkEvents & FD_READ)
{
if (NetworkEvents.iErrorCode[FD_READ_BIT] != 0)
{
printf("FD_READ failed with error %d\n",NetworkEvents.iErrorCode[FD_READ_BIT]);
}
}
完成了对 WSANETWORKEVENTS 结构中的事件的处理之后,我们的应用程序应在所有可用的套接字上,继续等待更多的网络事件。
- <span style="font-size:14px;">服务器:
- UINT CServerDlg::ThreadPro(LPVOID pParam )
- {
- CServerDlg* pDlg=(CServerDlg*)pParam;
- pDlg->InitSock();
- SOCKADDR_IN serAdd;
- serAdd.sin_family=AF_INET; //TCP协议簇
- serAdd.sin_addr.s_addr=ADDR_ANY;
- serAdd.sin_port=htons(5000);
- pDlg->m_listenSock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
- if (pDlg->m_listenSock==INVALID_SOCKET)
- {
- AfxMessageBox(_T("初始化失败"));
- return 0;
- }
- if (bind(pDlg->m_listenSock,(SOCKADDR*)&serAdd,sizeof(SOCKADDR))==SOCKET_ERROR)
- {
- AfxMessageBox(_T("绑定失败"));
- return 0;
- }
- SOCKET socArray[2]={0}; //句柄数组,有时候会对多个套接字进行监视
- WSAEVENT eveArray[2]={0}; //事件句柄数组
- WSAEVENT wEvent;
- int num=0;
- wEvent=WSACreateEvent(); //创建一个事件对象
- socArray[num]=pDlg->m_listenSock; //加入数组
- eveArray[num]=wEvent;
- num++;
- WSAEventSelect(pDlg->m_listenSock,wEvent,FD_ACCEPT | FD_CLOSE); //将事件,套接字,与感兴趣的事件关联
- if(SOCKET_ERROR==listen(pDlg->m_listenSock,SOMAXCONN))
- {
- AfxMessageBox(_T("监听失败"));
- return 0;
- }
- while (1) //开始监视有没有指定的事件,有就触发
- {
- DWORD dw;
- dw=WSAWaitForMultipleEvents(num,eveArray,FALSE,1000,FALSE); //这里1????只等待一个连接,那么后来通信的。。可能出错
- //WSAWaitForMultipleEvents()等待,类似于多线程的WaitForMultipleObjects,有信号就执行,否则不通过
- if(WSA_WAIT_TIMEOUT == dw) //超时
- {
- continue;
- }
- else if ( WSA_WAIT_FAILED == dw) //失败
- {
- AfxMessageBox(_T("等待失败"));
- continue;
- }
- else //如果有事件触发就找出该套接字,并判断是哪种事件,dw就是返回数组里套接字的索引
- {
- WSANETWORKEVENTS ne;
- WSAEnumNetworkEvents(socArray[dw-WSA_WAIT_EVENT_0],eveArray[dw-WSA_WAIT_EVENT_0],&ne); //这里要判断发生事件的是哪个套接字
- if (ne.lNetworkEvents & FD_ACCEPT)
- {
- if (ne.iErrorCode[FD_ACCEPT_BIT] != 0) //不为0则出错
- {
- continue;
- }
- SOCKADDR_IN cliAdd;
- int len=sizeof(SOCKADDR);
- pDlg->m_cliSock=accept(pDlg->m_listenSock,(SOCKADDR*)&cliAdd,&len);
- if (pDlg->m_cliSock == INVALID_SOCKET)
- {
- AfxMessageBox(_T("接收连接失败"));
- return 0;
- }
- //将通信的套接字加入事件选择
- WSAEVENT cEvent;
- cEvent=WSACreateEvent();
- socArray[num] = pDlg->m_cliSock;
- eveArray[num] = cEvent;
- num++; //必须,如果有多个套接字时...
- WSAEventSelect(pDlg->m_cliSock,cEvent,FD_READ | FD_WRITE | FD_CLOSE);
- }
- else if (ne.lNetworkEvents & FD_READ)
- {
- if (ne.iErrorCode[FD_READ_BIT] != 0)
- {
- continue;
- }
- TCHAR bufData[1024]={0};
- int flag=recv(pDlg->m_cliSock,(char*)bufData,1024,0);
- if(flag == 0)
- {
- AfxMessageBox(_T("客户端已经断开"));
- return 0;
- }
- else if (flag < 0)
- {
- AfxMessageBox(_T("接收数据出错"));
- return 0;
- }
- else
- pDlg->ShowMsg(bufData);
- }
- else if (ne.lNetworkEvents & FD_WRITE) //FD_WRITE只有三种情况下回触发,一般不处理
- {
- continue;
- }
- else if (ne.lNetworkEvents & FD_CLOSE)
- {
- if(ne.iErrorCode[FD_CLOSE_BIT] != 0)
- {
- continue;
- }
- closesocket(pDlg->m_cliSock);
- closesocket(pDlg->m_listenSock);
- WSACleanup();
- return 0;
- }
- }
- }
- }</span>
客户端与服务器类似。