Windows Sockets网络编程(3)WSAEventSelect模型开发

时间:2022-04-28 00:09:02

摘要:WSAEventSelect模型是非阻塞的,该模型允许在一个或者多个套接字上接收以事件为基础的网络事件通知。Windows Sockets应用程序在创建套接字后,调用WSAEventSelect()函数,将一个事件对象与网络事件集合关联在一起。当网络事件发生时,应用程序以事件的形式接收网络事件通知。使用这个模型的基本思路是为感兴趣的一组网络事件创建一个事件对象,再调用WSAEventSelect()函数将网络事件和事件对象关联起来。当网络事件发生时,Winsock使相应的事件对象受信,在事件对象上的等待函数就会返回。WSAEventSelect模型简单易用,也不需要窗口环境。该模型唯一的缺点是有最多等待64个事件对象的限制,当套接字连接数量增加时,就必须创建多个线程来处理I/O,也就是所谓的线程池。


目录:

-------------------------------

- 创建TCP链接

- WSACreateEvent函数

- WSAWaitForMultipleEvents函数

- WSAEnumNetworkEvents函数

- 实践1:WASEventSelect模型

- 实践2:TCP Client


1.创建TCP链接

这里不再赘述了,能来到本文的,相信基本功已经不用多讲了。实在不明白的可以阅读《Windows Sockets网络编程(0)TCP In Action》一文,该文详细的叙述了TCP创建的整个过程。

SOCKET socket_listener = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(8086);
sin.sin_addr.S_un.S_addr = INADDR_ANY;
if (bind(socket_listener, (sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR){
closesocket(socket_listener);
return;
}
listen(socket_listener, SOMAXCONN);//SOMAXCONN = 5

2.WSACreateEvent函数

在调用WSAEventSelect函数之间,必须要先创建事件,否则没法监听。Windows事件对象有两个状态“已触发”和“未触发”,而事件对象的工作模式也有两种“人工重设”以及“自动重设”。WSACreateEvent创建的事件——原始状态是“未触发态”,当事件到来时系统会将其置为已触发态,工作模式是“人工重设”。

WSAEVENT    eventArray[WSA_MAXIMUM_WAIT_EVENTS];//WSA_MAXIMUM_WAIT_EVENTS = 64
SOCKET sockArray[WSA_MAXIMUM_WAIT_EVENTS];
int nEventTotal = 0;
WSAEVENT event = WSACreateEvent();
WSAEventSelect(socket_listener, event, FD_ACCEPT | FD_CLOSE);
eventArray[nEventTotal] = event;
sockArray[nEventTotal] = socket_listener;
++nEventTotal;

另外,需要注意的是WSAResetEvent是将事件从“已触发”态重置为“未触发”状态。而WSACloseEvent主要用来释放事件对象所占用的资源。


3.WSAWaitForMultipleEvents函数

该函数主要用来等待一个或者所有事件变为“已触发”态。

WSAWaitForMultipleEvents(
    _In_ DWORD cEvents,
    _In_reads_(cEvents) const WSAEVENT FAR * lphEvents,
    _In_ BOOL fWaitAll,
    _In_ DWORD dwTimeout,
    _In_ BOOL fAlertable
    );

cEvents :事件句柄的数量,最多为WSA_MAXIMUM_WAIT_EVENTS = 64个;
lphEvents :事件句柄存放处;
fWaitAll :有两种状态,TRUE时表示等待所有事件被触发、FALSE时表示只要一个事件触发,就继续运行;
dwTimeout :超时时间,WSA_INFINITE表示无限等待;

fAlertable:该参数主要用于重叠I/O,此处总是设置为FALSE即可。


int nIndex = WSAWaitForMultipleEvents(nEventTotal, eventArray,FALSE,WSA_INFINITE,FALSE);


WSAWaitForMultipleEvents函数的返回值会指出——到底哪一个事件被触发了(在设置为单一触发的情况下)。这里牵涉到一个宏WSA_WAIT_EVENT_0(该宏在winnt.h中其实是被定义为0的),一般来说where =nIndex - WSA_WAIT_EVENT_0;使用其返回值减去WSA_WAIT_EVENT_0所得的值,则表示事件数组中,具体被触发的那个事件下标。当然,如果fWaitAll设置为TRUE的话,根本不需要计算返回值,因为该情况下,总是全部被触发。


4.WSAEnumNetworkEvents函数

事件发生了,也知道具体是哪个Socket被触发了。但是,为何被触发了?这就需要该函数来确定了。

WSAEnumNetworkEvents(
    _In_ SOCKET s,
    _In_ WSAEVENT hEventObject,
    _Out_ LPWSANETWORKEVENTS lpNetworkEvents
    );

s :被触发的套接字句柄;

hEventObject :需要被重置的Event(WSACreateEvent是“人工重置”的);

lpNetworkEvents:指向_WSANETWORKEVENTS 结构体指针,包含了到底是发生了何种网络事件,或者网络错误。


typedef struct _WSANETWORKEVENTS {
       long lNetworkEvents;
       int iErrorCode[FD_MAX_EVENTS];
} WSANETWORKEVENTS, FAR * LPWSANETWORKEVENTS;

lNetworkEvents:指示发生的网络事件。当一个事件对象成为“已触发”状态时,可能同时发生了多个网络事件。

iErrorCode:网络事件数组。所以事件需要使用一个数组来存放。


WSANETWORKEVENTSevent;
WSAEnumNetworkEvents(sockArray[nIndex], eventArray[nIndex], &event);


那么,具体要如何判断发生了什么自己关注的事件呢?

if (event.lNetworkEvents &FD_ACCEPT)
{

if (event.iErrorCode[FD_ACCEPT_BIT] == 0)

{

}

}

标识符命名是有规则的,如果需要检查某一事件是否发生了错误,在对应事件末尾追加“_BIT”即可。比如FD_ACCEPT网络事件所对应的错误位置即为FD_ACCEPT_BIT。假若iErrorCode[FD_ACCEPT_BIT] == 0,则表示未发生错误。


5.实践1:WASEventSelect模型

#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib")
#include <stdio.h>

/*
Date |Change
-----------------------------
2017-7-24 |WSAEventSelect模型
*/
WSAEVENT eventArray[WSA_MAXIMUM_WAIT_EVENTS];//WSA_MAXIMUM_WAIT_EVENTS = 64
SOCKET sockArray[WSA_MAXIMUM_WAIT_EVENTS];
int nEventTotal = 0;

void wsa_event_select()
{
SOCKET sAccept = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(8086);
sin.sin_addr.S_un.S_addr = INADDR_ANY;
if (bind(sAccept, (sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR){
closesocket(sAccept);
return;
}
listen(sAccept, SOMAXCONN);//SOMAXCONN = 5

WSAEVENT event = WSACreateEvent();
WSAEventSelect(sAccept, event, FD_ACCEPT | FD_CLOSE);
eventArray[nEventTotal] = event;
sockArray[nEventTotal] = sAccept;
++nEventTotal;

while (true)
{
int nIndex = WSAWaitForMultipleEvents(nEventTotal, eventArray, FALSE, WSA_INFINITE, FALSE);
nIndex = nIndex - WSA_WAIT_EVENT_0;

if (nIndex == WSA_WAIT_FAILED || nIndex == WSA_WAIT_TIMEOUT)
{
continue;
}
else
{
WSANETWORKEVENTS event;
WSAEnumNetworkEvents(sockArray[nIndex], eventArray[nIndex], &event);
if (event.lNetworkEvents & FD_ACCEPT)
{
if (event.iErrorCode[FD_ACCEPT_BIT] == 0)
{
if (nEventTotal > WSA_MAXIMUM_WAIT_EVENTS)
{
continue;
}
SOCKET sClient = accept(sockArray[nIndex], NULL, NULL);
WSAEVENT event = WSACreateEvent();
WSAEventSelect(sClient, event, FD_READ | FD_CLOSE | FD_WRITE);
eventArray[nEventTotal] = event;
sockArray[nEventTotal] = sClient;
++nEventTotal;
printf("accept.\n");
}
}
else if (event.lNetworkEvents & FD_READ) // 处理FD_READ通知消息
{
if (event.iErrorCode[FD_READ_BIT] == 0)
{
char data[1024];
int ret = recv(sockArray[nIndex], data, strlen(data), 0);
if (ret > 0)
{
data[ret] = '\0';
printf("recv: %s.\n",data);
send(sockArray[nIndex], data, strlen(data), 0);
}
}
}
else if (event.lNetworkEvents & FD_CLOSE)
{
if (event.iErrorCode[FD_CLOSE_BIT] == 0)
{
printf("close.\n");
closesocket(sockArray[nIndex]);
for (int j = nIndex; j < nEventTotal - 1; j++)
{
eventArray[j] = eventArray[j + 1];
sockArray[j] = sockArray[j + 1];
}
--nEventTotal;
}
}
else if (event.lNetworkEvents & FD_WRITE)
{
printf("send.\n");
char* data = "qingdujun";
send(sockArray[nIndex], data, sizeof(data)/sizeof(char), 0);
}
}
}

}

int main(int argc, char* argv[])
{
WSADATA wsa;
WSAStartup(MAKEWORD(2, 2), &wsa);
wsa_event_select();
WSACleanup();
return 0;
}

6.实践2:TCP Client

#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib")
#include <stdio.h>

/*
Date |Change
-----------------------------------------
2017-7-24 |WSAEventSelect模型,TCP测试客户端
*/
void tcp_client()
{
SOCKET sClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(8086);
sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
if (connect(sClient, (sockaddr *)&sin, sizeof(sin)) == SOCKET_ERROR){
closesocket(sClient);
return;
}
char buffer[1024] = "abcdef";
send(sClient, buffer, strlen(buffer), 0);
int ret = recv(sClient, buffer, sizeof(buffer), 0);
buffer[ret] = '\0';
printf("%s.\n",buffer);
closesocket(sClient);

}

int main(int argc, char* argv[])
{
WSADATA wsa;
WSAStartup(MAKEWORD(2, 2), &wsa);
int i = 5;
while (i--){
tcp_client();
Sleep(2000);
}
WSACleanup();
return 0;
}


参考文献:

[1] 孙海民. 精通Windows Sockets网络开发——基于Visual C++实现[M]. 北京:人民邮电出版社, 2008. 238-245



@qingdujun

2017-7-25 in Xi'An