一、WSAEventSelect网络事件模型介绍:
事件选择(WSAEventSelect)模型是另一个有用的I/O模型,和WSAAsyncSelect模型类似的是,他也允许应用程序在一个或多个套接字上,接收以事件为基础的网络事件通知,最主要的差别在于网络事件会投递至一个事件对象句柄,而非投递到一个窗口句柄。
二、使用具体步骤:
1.事件通知模型要求我们的应用程序针对使用的每一个套接字,首先创建一个事件对象。创建方法是调用WSACreateEvent函数,定义如下:
WSAEVENT WSACreateEvent(void)
WSACreateEvent函数的返回值很简单,就是一个创建好的事件对象句柄。
WSACreateEvent创建的事件有两种工作状态,以及两种工作模式。
工作状态分别是:已传信和未传信。
工作模式分别是:人工重设和自动重设
WSACreateEvent开始是以一种未传信的工作状态,并用一种人工重设模式来创建事件对象句柄。随着网络事件触发了与一个套接字管理子啊一起的事件对象,工作状态便会从未传信转变为
已传信,由于事件对象是在一种人工重设模式中创建的,所以在完成了一个I/O请求的处理之后,我们的应用程序需要负责将工作状态从已传信更改为未传信,就得调用WSAResetEvent函数
定义如下:
BOOL WSAAPI WSAResetEvent(
_In_ WSAEVENT hEvent
);
hEvent:一个事件对象句柄,基于调用是成功还是失败,会分别返回TRUE或FALSE。
2.接下来必须将其与某个套接字关联在一起,同事注册自己感兴趣的网络事件类型(FD_READ、FD_WRITE、FD_ACCETP、FD_CONNECT、FD_CLOSE),方法是调用WSAEventSelect函数,其定义如下:
int WSAAPI WSAEventSelect( _In_ SOCKET s, _In_opt_ WSAEVENT hEventObject, _In_ long lNetworkEvents );
s:代表感兴趣的套接字
hEventObject:指定要与套接字管理在一起的事件对象---用WSACreateEvent取得的那一个。
lNetworkEvents:则对应一个“位掩码”,用于指定应用程序感兴趣的各种网络事件类型的一个组合。
3.对一个事件对象处理完或者某个socket关闭之后,应关闭对应的事件对象,调用WSACloseEvent函数
BOOL WSAAPI WSACloseEvent(
_In_ WSAEVENT hEvent
);
4.一个套接字同一个事件对象句柄管理在一起后,应用程序便可开始I/O处理:方法是等待网络事件触发对象句柄的工作状态,WSAWaitForMultipleEvents函数的设计
宗旨便是用来等待一个或多个事件对象句柄,并在事先指定的一个或所有句柄进入已传信状态后,或者超过了一个规定的事件周期后,立即返回。函数定义如下:
DWORD WSAAPI WSAWaitForMultipleEvents( _In_ DWORD cEvents, _In_reads_(cEvents) const WSAEVENT FAR * 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:若为TRUE,必须等到lphEvents数组包含所以事件对象都进入已传信状态,函数才能返回,若为FALSE,则任何一个事件对象进入已传信状态,函数返回。
dwTimeout:超时设定,单位毫秒,设定为WSAINFINITE时,永远等待。
fAlertable:一般设置为FALSE.
若WSAWaitForMultipleEvents收到一个事件对象通知,便会返回一个值,指出造成函数返回的事件对象。此时应该用WSAWaitForMultipleEvents的返回值,减去预定义的值WSA_WAIT_EVENT_0,
得到具体的引用值(即索引位置),如下:
Index = WSAWaitForMultipleEvents(...);
MyEvent = EventArray[Index - WSA_WAIT_EVENT_0];
5.知道了造成网络事件的套接字后,接下来调用WSAEnumNetworkEvents,定义如下:
int WSAAPI 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, FAR * LPWSANETWORKEVENTS;
lNetworkEvents:对应套接字上发生的所以网络事件类型(FD_READ、FD_WRITE等)。
iErrorCode:指定一个错误代码数组,同lNetworkEvents中的事件关联在一起。
6.针对每个网络事件类型,都存在一个特殊的事件索引,名字与事件类型的名字类似,只要在事件名字后面加一个“_BIT”后缀字符串即可,例如对FD_READ事件类型来说,iErrorCode
数组的索引标识便是FD_READ_BIT,
if (lpNetworkEvents.lNetworkEvents & FD_READ) { if (lpNetworkEvents.iErrorCode[FD_READ_BIT != 0]) { printf("FD_READ falied with error %d ",lpNetworkEvents.iErrorCode[FD_READ_BIT]); } }
三、具体实现代码:
void CMFCApplication1Dlg::OnBnClickedButton1() { // 服务器启动网络I/O线程 AfxBeginThread(ThreadProc, this, THREAD_PRIORITY_NORMAL, 4 * 1024 * 1024, 0); } UINT CMFCApplication1Dlg::ThreadProc(LPVOID pParam) { ASSERT(pParam); sockaddr_in clientAddr = { 0 }; ///////////////////////////////// TCHAR szBuf[MAX_BUF_SIZE] = { 0 }; SOCKET ArrSocket[WSA_MAXIMUM_WAIT_EVENTS] = { 0 }; WSAEVENT ArrEvent[WSA_MAXIMUM_WAIT_EVENTS] = { 0 }; DWORD dwTotal = 0, dwIndex = 0, index = 0, ret = 0; WSANETWORKEVENTS m_NetWorkEvents = { 0 }; ///////////////////////////////// CMFCApplication1Dlg *pThis = (CMFCApplication1Dlg *)pParam; pThis->WinSockInit(); pThis->m_SockListen = INVALID_SOCKET; pThis->m_SockListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (pThis->m_SockListen == INVALID_SOCKET) return FALSE; sockaddr_in service; service.sin_family = AF_INET; service.sin_addr.s_addr = INADDR_ANY; service.sin_port = htons(9527); if (bind(pThis->m_SockListen, (sockaddr *)&service, sizeof(sockaddr_in)) == SOCKET_ERROR) return FALSE; WSAEVENT m_ListenEvent = WSACreateEvent(); //事件对象与socket绑定 WSAEventSelect(pThis->m_SockListen, m_ListenEvent, FD_ACCEPT | FD_CLOSE); if (listen(pThis->m_SockListen, SOMAXCONN) == SOCKET_ERROR) return FALSE; ArrSocket[dwIndex] = pThis->m_SockListen; ArrEvent[dwIndex] = m_ListenEvent; dwTotal++; while (TRUE) { //等待网络事件 dwIndex = WSAWaitForMultipleEvents(dwTotal, ArrEvent, FALSE, INFINITE, FALSE); if (dwIndex == WSA_WAIT_FAILED || dwIndex == WSA_WAIT_TIMEOUT) continue; index = dwIndex - WSA_WAIT_EVENT_0; WSAEnumNetworkEvents(ArrSocket[index], ArrEvent[index], &m_NetWorkEvents); //底下为对应网络事件的处理 //accept the client if (m_NetWorkEvents.lNetworkEvents & FD_ACCEPT) { if (m_NetWorkEvents.iErrorCode[FD_ACCEPT_BIT] != 0) continue; pThis->m_SockClient = accept(ArrSocket[index], NULL, NULL); if (pThis->m_SockClient == INVALID_SOCKET) continue; WSAEVENT newEvent = WSACreateEvent(); WSAEventSelect(pThis->m_SockClient, newEvent, FD_READ | FD_WRITE | FD_CLOSE); ArrSocket[dwTotal] = pThis->m_SockClient; ArrEvent[dwTotal] = newEvent; dwTotal++; } //recv the data if (m_NetWorkEvents.lNetworkEvents & FD_READ) { if (m_NetWorkEvents.iErrorCode[FD_READ_BIT] != 0) continue; ret = recv(ArrSocket[index], (char *)szBuf, MAX_BUF_SIZE, 0); //更新界面接收数据 if (ret > 0) pThis->ShowMsg(szBuf); else { pThis->ShowMsg(TEXT("客户端已下线,请重新开启服务器等待客户端连接")); continue; } } //send the data if (m_NetWorkEvents.lNetworkEvents & FD_WRITE) { if (m_NetWorkEvents.iErrorCode[FD_WRITE_BIT] != 0) continue; //发送数据 } //close the socket if (m_NetWorkEvents.lNetworkEvents & FD_CLOSE) { if (m_NetWorkEvents.iErrorCode[FD_CLOSE_BIT] != 0) continue; //关闭对应的socket closesocket(ArrSocket[index]); //关闭socket对应的事件对象 WSACloseEvent(ArrEvent[index]); //去掉2个数组中对应的东西,并且总数-1 dwTotal--; pThis->DelValue(ArrSocket, WSA_MAXIMUM_WAIT_EVENTS, index); pThis->DelValue(ArrEvent, WSA_MAXIMUM_WAIT_EVENTS, index); } } if (pThis->m_SockListen != INVALID_SOCKET) closesocket(pThis->m_SockListen); WSACleanup(); return TRUE; } //根据索引删除数组中的值 template <typename T> BOOL CMFCApplication1Dlg::DelValue(T arr[], int arr_size, int index) { if (arr || index >= 0) { for (int i = index; i<arr_size - 1; ++i) arr[i] = arr[i + 1]; } return TRUE; } BOOL CMFCApplication1Dlg::WinSockInit() { WSADATA data = { 0 }; if (WSAStartup(MAKEWORD(2, 2), &data)) return FALSE; if (LOBYTE(data.wVersion) != 2 || HIBYTE(data.wVersion) != 2) { WSACleanup(); return FALSE; } return TRUE; }