WSAEventSelect模型例子

==> 学习汇总(持续更新)
==> 从零搭建后端基础设施系列(一)-- 背景介绍


WSAEventSelect模型和WSAAsyncSelect模型
相同点:
1.都是由系统通知应用程序处理网络事件
2.都是异步的
不同点:通知机制不一样,WSAAsyncSelect模型是以windows的消息机制来通知应用程序的。WSAEventSelect模型是以事件形式通知的。所以一个需要窗口,一个则不需要。

建立WSAEventSelect模型步骤:

##1.为server socket创建网络事件对象和注册网络事件

用到的函数及原型如下:

创建网络事件对象

WSAEVENT  WSACreateEvent (void);

注册网络事件

int WSAEventSelect(     //成功返回0
  SOCKET s,             //要注册的socket 
  WSAEVENT hEventObject,//网络事件对象
  long lNetworkEvents   //网络事件,FD_开头的
);

例如:

g_hServerEvent = WSACreateEvent();   //创建网络事件对象
WSAEventSelect(g_sServer, g_hServerEvent, FD_ACCEPT);//为server socket注册网络事件 

如图:

##2.接受client请求线程

用到的结构、函数及原型如下:

网络事件结构体

typedef struct _WSANETWORKEVENTS {
  long lNetworkEvents;           //网络事件,FD_开头的
  int iErrorCode[FD_MAX_EVENTS]; //错误代码
} WSANETWORKEVENTS, *LPWSANETWORKEVENTS;

查找给定socket上发生的网络事件

int WSAEnumNetworkEvents(    //成功返回0
  SOCKET s,                
  WSAEVENT hEventObject,     //该socket的网络事件对象
  LPWSANETWORKEVENTS lpNetworkEvents //网络结构体指针
);

判断发生了哪个网络事件

networkEvents.lNetworkEvents & FD_ACCEPT 如果等于1 则表示发生了FD_ACCEPT事件

判断有无网络错误

networkEvents.iErrorCode[FD_ACCEPT_BIT]  等于0表示无错误

若无错误,则接受client请求,为新的client创建网络事件对象和注册网络事件。

如图:

##3.接收数据线程函数

用到的结构、函数及原型如下:

等待网络事件的到来

DWORD WSAAPI WSAWaitForMultipleEvents(
  DWORD cEvents,                //等待的事件数
  const WSAEVENT FAR* lphEvents,//事件对象数组
  BOOL fWaitAll,                //是否等待全部socket都有事件才返回
  DWORD dwTimeout,              //等待时间,毫秒为单位,WSA_INFINE表示无限期等待
  BOOL fAlertable               //先置为FALSE
);

这个函数会返回一个index,表示是第几个网络事件对象,根据这个index也可以索引到socket,然后下面调用WSAEnumNetworkEvents来判断该socket上发生了什么事件。

如图:

示例代码:

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

SOCKET g_sClient[WSA_MAXIMUM_WAIT_EVENTS] = {INVALID_SOCKET};  //client socket数组
WSAEVENT g_event[WSA_MAXIMUM_WAIT_EVENTS];                     //网络事件对象数组
SOCKET g_sServer = INVALID_SOCKET;                             //server socket 
WSAEVENT g_hServerEvent;                                       //server 网络事件对象
int iTotal = 0;                                                //client个数
/*
@function OpenTCPServer             打开TCP服务器
@param _In_ unsigned short Port     服务器端口
@param  _Out_ DWORD* dwError        错误代码
@return  成功返回TRUE 失败返回FALSE
*/
BOOL OpenTCPServer( _In_ unsigned short Port, _Out_ DWORD* dwError)
{
	BOOL bRet = FALSE;
	WSADATA wsaData = { 0 };
	SOCKADDR_IN ServerAddr = { 0 };
	ServerAddr.sin_family = AF_INET;
	ServerAddr.sin_port = htons(Port);
	ServerAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	do
	{
		if (!WSAStartup(MAKEWORD(2, 2), &wsaData))
		{
			if (LOBYTE(wsaData.wVersion) == 2 || HIBYTE(wsaData.wVersion) == 2)
			{
				g_sServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
				g_hServerEvent = WSACreateEvent();                    //创建网络事件对象
				WSAEventSelect(g_sServer, g_hServerEvent, FD_ACCEPT);//为server socket注册网络事件 
				if (g_sServer != INVALID_SOCKET)
				{
					if (SOCKET_ERROR != bind(g_sServer, (SOCKADDR*)&ServerAddr, sizeof(ServerAddr)))
					{
						if (SOCKET_ERROR != listen(g_sServer, SOMAXCONN))
						{
							bRet = TRUE;
							break;
						}
						*dwError = WSAGetLastError();
						closesocket(g_sServer);
					}
					*dwError = WSAGetLastError();
					closesocket(g_sServer);
				}
				*dwError = WSAGetLastError();
			}
			*dwError = WSAGetLastError();

		}
		*dwError = WSAGetLastError();
	} while (FALSE);
	return bRet;
}

//接受client请求线程
unsigned int __stdcall ThreadAccept(void* lparam)
{
	WSANETWORKEVENTS networkEvents; //网络事件结构
	while (iTotal < WSA_MAXIMUM_WAIT_EVENTS)  //这个值是64
	{
		if (0 == WSAEnumNetworkEvents(g_sServer, g_hServerEvent, &networkEvents))
		{
			if (networkEvents.lNetworkEvents & FD_ACCEPT) //如果等于FD_ACCEPT,相与就为1
			{
				if (0 == networkEvents.iErrorCode[FD_ACCEPT_BIT])  //检查有无网络错误
				{
					//接受请求
					SOCKADDR_IN addrServer = { 0 };
					int iaddrLen = sizeof(addrServer);
					g_sClient[iTotal] = accept(g_sServer, (SOCKADDR*)&addrServer, &iaddrLen);
					if (g_sClient[iTotal] == INVALID_SOCKET)
					{
						printf("accept failed with error code: %d\n", WSAGetLastError());
						return 1;
					}
					//为新的client注册网络事件
					g_event[iTotal] = WSACreateEvent();
					WSAEventSelect(g_sClient[iTotal], g_event[iTotal], FD_READ | FD_WRITE | FD_CLOSE);
					iTotal++;
					printf("accept a connection from IP: %s,Port: %d\n", inet_ntoa(addrServer.sin_addr), htons(addrServer.sin_port));
				}
				else  //错误处理
				{
					int iError = networkEvents.iErrorCode[FD_ACCEPT_BIT];
					printf("WSAEnumNetworkEvents failed with error code: %d\n", iError);
					return 1;
				}
			}
		}
		Sleep(100);
	}
	return 0;
}

//接收数据
unsigned int __stdcall ThreadRecv(void* lparam)
{
	char* buf = (char*)malloc(sizeof(char) * 128);
	while (1)
	{
		if (iTotal == 0)
		{
			Sleep(100);
			continue;
		}
		//等待网络事件
		DWORD dwIndex = WSAWaitForMultipleEvents(iTotal, g_event, FALSE, 1000, FALSE); 
		//当前的事件对象
		WSAEVENT curEvent = g_event[dwIndex];
		//当前的套接字
		SOCKET sCur = g_sClient[dwIndex];
		//网络事件结构
		WSANETWORKEVENTS networkEvents;
		if (0 == WSAEnumNetworkEvents(sCur, curEvent, &networkEvents))
		{
			if (networkEvents.lNetworkEvents & FD_READ)  //有数据可读
			{
				if (0 == networkEvents.iErrorCode[FD_READ_BIT])
				{
					memset(buf, 0, sizeof(buf));
					int iRet = recv(sCur, buf, sizeof(buf), 0);  //接收数据
					if (iRet != SOCKET_ERROR)
					{
						if (strlen(buf) != 0)
							printf("Recv: %s\n", buf);
					}
				}
				else //错误处理
				{
					int iError = networkEvents.iErrorCode[FD_ACCEPT_BIT];
					printf("WSAEnumNetworkEvents failed with error code: %d\n", iError);
					break;
				}
			}
			else if (networkEvents.lNetworkEvents & FD_CLOSE)  //client关闭
				printf("%d downline\n", sCur);
		}
		Sleep(100);
	}
	if (buf)
		free(buf);
	return 0;
}

int main()
{
	DWORD dwError = 0;
	if (OpenTCPServer(18000, &dwError))
	{
		_beginthreadex(NULL, 0, ThreadAccept, NULL, 0, NULL);
		_beginthreadex(NULL, 0, ThreadRecv, NULL, 0, NULL);
	}
	Sleep(100000000);
	closesocket(g_sServer);
	WSACleanup();
	return 0;
}

这里有个问题要说一下,就是WSAWaitForMultipleEvents可以无限期阻塞,但是最后不要这样,因为如果它阻塞在一个状态后,有新的client加入,它不知道,得不到更新,就算新的client发送消息,也不会返回,要等原来的client发送消息让它返回后才会处理新的client消息。所以我建议timeout时间设置为1s或几s都行,这样轮询消耗的CPU可以忽略不计。这个问题在Select模型的时候也有,Select也会阻塞在那里.

_acme_ CSDN认证博客专家 JAVA
已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: Age of Ai 设计师:meimeiellie 返回首页