网络编程 事件选择模型

目录

1.概念分析

2.事件选择代码逻辑

 1.WSACreateEvent函数

2.WSACloseEvent函数

3.WSAEventSelect函数

4.WSAWaitForMultipleEvents()函数

5.WSAEnumNetworkEvents函数

        事件分类

3.##模型代码样例


1.概念分析

        本质上是操作系统处理用户行为,详细如下

事件选择模型是select模型的升级版,区别select模型的代码执行时,会在socket数组中阻塞其它的操作,而事件选择模型不会出现这样的情况,在系统处理消息队列时,用户是可以执行其它操作的,这里就是异步。事件选择模型中的事件在事件数组中是无序的。

2.事件选择代码逻辑

基本流程

        事件选择模型步骤前面的代码与select模型代码是一致的

#define _CRT_SECURE_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS

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

int main()
{
	//第一步 打开网络库并校验版本
	WORD wdVersion = MAKEWORD(2, 2);
	WSADATA wdSocketMsg;
	int nRes = WSAStartup(wdVersion, &wdSocketMsg);
	if (nRes != 0)
	{
		printf("打开网络库失败\n");
		return 0;
	}
	if (HIBYTE(wdSocketMsg.wVersion) != 2 || LOBYTE(wdSocketMsg.wVersion) != 2)
	{
		printf("网络库版本出错\n");
		WSACleanup();
		return 0;
	}
	//第二步 创建socket
	SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (socketServer == INVALID_SOCKET)
	{
		printf("创建的socket无效\n");
		WSACleanup();
		return 0;
	}
	//第四步 绑定ip地址和端口号
	struct sockaddr_in si;
	si.sin_family = AF_INET;
	si.sin_port = htons(12332);
	si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	if (SOCKET_ERROR == bind(socketServer, (const struct sockaddr*)&si, sizeof(si)))
	{
		printf("绑定失败\n");
		closesocket(socketServer);
		WSACleanup();
		return 0;
	}
	//第五步 开始监听
	if (SOCKET_ERROR == listen(socketServer, SOMAXCONN))
	{
		printf("监听失败\n");
		closesocket(socketServer);
		WSACleanup();
		return 0;
	}
	
}

核心代码步骤有4步:1.创建事件对象,使用函数 WSACreateEvent

                                   2.为每个事件对象绑定socket

                                   3.查看事件是否有信号,使用函数 WSAWaitForMultipleEvent

                                   4.有信号时对信号进行分类,使用函数 WSAEnumNetworkEvents

 1.WSACreateEvent函数

该函数创建一个事件对象,函数原型

WSAEVENT WSAAPI WSACreateEvent();

        返回值:如果创建成功会返回事件对象的句柄,如果创建失败返回 WSA_INVALID_EVENT,本质上是一个空指针

##代码样例

	//创建事件
	WSAEVENT eventServer = WSACreateEvent();//创建成会返回一个事件
	if (WSA_INVALID_EVENT == eventServer)
	{
		//出错了
		int a = WSAGetLastError();//获取错误码
		closesocket(socketServer);
		WSACleanup();
		return 0;
	}

2.WSACloseEvent函数

该函数关闭创建好的事件,即关闭打开的事件对象句柄,函数原型

BOOL WSAAPI WSACloseEvent(
  [in] WSAEVENT hEvent
);

参数 hEvent:事件对象

        返回值:成功返回TRUE,失败返回False

3.WSAEventSelect函数

该函数指定要与指定的FD_XXX网络事件集关联的事件对象,即绑定并投递事件。函数原型

int WSAAPI WSAEventSelect(
  [in] SOCKET   s,
  [in] WSAEVENT hEventObject,
  [in] long     lNetworkEvents
);

 参数1 s:要被绑定的socket

参数2 hEventObject:要关联的事件对象

参数3 lNetworkEvents:具体事件

        参数3具有的常用具体事件如下:

事件意义用法
FD_ACCEPT处理客户端连接问题与服务器socket绑定
FD_READ处理客户端发送的消息

与客户端socket绑定

可多个属性并列 使用 |

FD_CLOSE处理客户端下线问题

与客户端socket绑定

处理的包含强制下线和正常下线

FD_WRITE处理服务器给客户端发消息

与客户端socket绑定

会在accept后立即主动产生该信号

        返回值:成功返回0,失败返回SOCKET_ERROR

4.WSAWaitForMultipleEvents()函数

该函数返回一个或多个指定事件对象处于信号状态、超时间隔过期或 I/O 完成例程执行的时间。函数原型

DWORD WSAAPI WSAWaitForMultipleEvents(
  [in] DWORD          cEvents,
  [in] const WSAEVENT *lphEvents,
  [in] BOOL           fWaitAll,
  [in] DWORD          dwTimeout,
  [in] BOOL           fAlertable
);

参数1 cEvents:要处理事件的数量

参数2 lphEvents:保存事件的数组

参数3 fWaitAll:事件等待方式,为TRUE时,数组中所有事件产生信号时才返回,为FALSE时,任何一个事件产生信号,立即返回

参数4 dwTimeout:设定等待时间,单位是毫秒;填数字a时,等待a毫秒后,超时返回 WSA_WAIT_TIMEOUT;填0时,检查事件对象的状态并立即返回,不管有没有信号;填 WSA_INFINITE时,等待直到事件发生

参数5 fAlertable:在这个模型中填FALSE,在重叠IO模型中的完成例程中需要改成TRUE

        返回值:

如果成功返回以下值中的一个:

返回值含义
数组下标的运算值参数3为false时,返回值减去WSA_WAIT_EVENT_0==数组中事件的下标
WSA_WAIT_IO_COMPLETION参数5为true才会返回该值
WSA_WAIT_TIMEOUT超时会返回该值

如果失败返回 WSA_WAIT_FAILED,可使用 WSAGetLastError 获取错误码

##代码实例

//创建结构体对象
	struct fd_es_set esAll = { 0, {0}, {NULL} };
	
	//创建事件
	WSAEVENT eventServer = WSACreateEvent();//创建成会返回一个事件
	if (WSA_INVALID_EVENT == eventServer)
	{
		//出错了
		int a = WSAGetLastError();//获取错误码
		closesocket(socketServer);
		WSACleanup();
		return 0;
	}

	//把事件和socket装进结构体中
	esAll.eventAll[esAll.count] = eventServer;
	esAll.socketAll[esAll.count] = socketServer;
	esAll.count++;

	while (1)
	{
		//等待事件
		DWORD wRes = WSAWaitForMultipleEvents(esAll.count, esAll.eventAll, FALSE, WSA_INFINITE, FALSE);
		if (wRes == WSA_WAIT_FAILED)
		{
			//出错了
			int a = WSAGetLastError();//获取错误码
			printf("错误码:%d\n", a);
			break;
		}
		//超时判断,WSAWaitForMultipleEvents函数的参数4需要是具体毫秒
		if (wRes == WSA_WAIT_TIMEOUT)
		{
			continue;
		}

		DWORD nIndex = wRes - WSA_WAIT_EVENT_0;//拿到事件在数组中的下标
	}

	//绑定事件
	if (SOCKET_ERROR == WSAEventSelect(socketServer, eventServer, FD_ACCEPT))//成功返回0
	{
		//绑定失败
		int a = WSAGetLastError();//获取错误码
		WSACloseEvent(eventServer);
		closesocket(socketServer);
		WSACleanup();
		return 0;
	}

	//释放事件 句柄
	WSACloseEvent(eventServer);

5.WSAEnumNetworkEvents函数

该函数可发现指示套接字的网络事件发生、清除内部网络事件记录以及重置事件对象,即获取事件类型,并将事件上的信号重置,函数原型

int WSAAPI WSAEnumNetworkEvents(
  [in]  SOCKET             s,
  [in]  WSAEVENT           hEventObject,
  [out] LPWSANETWORKEVENTS lpNetworkEvents
);

参数1 s:需要处理的对应socket

参数2 hEventObject:需要处理的对应事件

参数3 lpNetworkEvents:这是一个结构体类型,当事件处理发生错误时,在这个变量中可以找到对应socket发生错误的错误码,结构体原型如下

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

内置对应事件的错误数组iErrorCode,

        返回值:处理成功返回0,失败返回SOCKET_ERROR

##代码实例

		DWORD nIndex = wRes - WSA_WAIT_EVENT_0;//拿到事件在数组中的下标
		//得到下标对应的具体操作
		WSANETWORKEVENTS NetWorkEvents;
		if (SOCKET_ERROR == WSAEnumNetworkEvents(esAll.socketAll[nIndex], esAll.eventAll[nIndex], &NetWorkEvents))
		{
			//出错了
			int a = WSAGetLastError();
			printf("事件操作出错,错误码:%d\n", a);
			break;
		}

        事件分类

在事件分类这一部份代码中,每一个信号都可以对应一种事件,在服务器中FD_ACCEPT信号会对应accept事件,FD_WRITE信号会对应send事件,FD_READ会对应recv事件,在这一部分代码中使用的是if语句,尽量不使用switch语句和if else语句

关键判断语句

if (NetWorkEvents.lNetworkEvents & FD_ACCEPT)//使用按位与判断是否是对应事件
		{
			if (0 == NetWorkEvents.iErrorCode[FD_ACCEPT_BIT])//判断如果错误数组中对应下标的位置为0,则表示没有错误,否则该下标位置存放对应错误码
                {

                }
        }

3.##模型代码样例

#define _CRT_SECURE_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS

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

//定义事件和socket数组的结构体
struct fd_es_set
{
	unsigned short count;
	SOCKET socketAll[WSA_MAXIMUM_WAIT_EVENTS];//WSA_MAXIMUM_WAIT_EVENTS 这是一个宏  大小是 64
	WSAEVENT eventAll[WSA_MAXIMUM_WAIT_EVENTS];
};


int main()
{
	//第一步 打开网络库并校验版本
	WORD wdVersion = MAKEWORD(2, 2);
	WSADATA wdSocketMsg;
	int nRes = WSAStartup(wdVersion, &wdSocketMsg);
	if (nRes != 0)
	{
		printf("打开网络库失败\n");
		return 0;
	}
	if (HIBYTE(wdSocketMsg.wVersion) != 2 || LOBYTE(wdSocketMsg.wVersion) != 2)
	{
		printf("网络库版本出错\n");
		WSACleanup();
		return 0;
	}
	//第二步 创建socket
	SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (socketServer == INVALID_SOCKET)
	{
		printf("创建的socket无效\n");
		WSACleanup();
		return 0;
	}
	//第三步 绑定ip地址和端口号
	struct sockaddr_in si;
	si.sin_family = AF_INET;
	si.sin_port = htons(12332);
	si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	if (SOCKET_ERROR == bind(socketServer, (const struct sockaddr*)&si, sizeof(si)))
	{
		printf("绑定失败\n");
		closesocket(socketServer);
		WSACleanup();
		return 0;
	}
	//第四步 开始监听
	if (SOCKET_ERROR == listen(socketServer, SOMAXCONN))
	{
		printf("监听失败\n");
		closesocket(socketServer);
		WSACleanup();
		return 0;
	}

	//创建结构体对象
	struct fd_es_set esAll = { 0, {0}, {NULL} };
	
	//创建事件
	WSAEVENT eventServer = WSACreateEvent();//创建成会返回一个事件
	if (WSA_INVALID_EVENT == eventServer)
	{
		//出错了
		int a = WSAGetLastError();//获取错误码
		closesocket(socketServer);
		WSACleanup();
		return 0;
	}

	//绑定事件
	if (SOCKET_ERROR == WSAEventSelect(socketServer, eventServer, FD_ACCEPT))//成功返回0
	{
		//绑定失败
		int a = WSAGetLastError();//获取错误码
		WSACloseEvent(eventServer);
		closesocket(socketServer);
		WSACleanup();
		return 0;
	}

	//把事件和socket装进结构体中
	esAll.eventAll[esAll.count] = eventServer;
	esAll.socketAll[esAll.count] = socketServer;
	esAll.count++;

	while (1)
	{
		//等待事件
		DWORD wRes = WSAWaitForMultipleEvents(esAll.count, esAll.eventAll, FALSE, WSA_INFINITE, FALSE);
		if (wRes == WSA_WAIT_FAILED)
		{
			//出错了
			int a = WSAGetLastError();//获取错误码
			printf("等待事件,错误码:%d\n", a);
			break;
		}
		//超时判断,WSAWaitForMultipleEvents函数的参数4需要是具体毫秒
		if (wRes == WSA_WAIT_TIMEOUT)
		{
			continue;
		}

		DWORD nIndex = wRes - WSA_WAIT_EVENT_0;	//拿到事件在数组中的下标
		//得到下标对应的具体操作
		WSANETWORKEVENTS NetWorkEvents;
		//如果发生错误会把对应错误码放进NetWorkEvents结构体数组中
		if (SOCKET_ERROR == WSAEnumNetworkEvents(esAll.socketAll[nIndex], esAll.eventAll[nIndex], &NetWorkEvents))
		{
			//出错了
			int a = WSAGetLastError();
			printf("事件操作出错,错误码:%d\n", a);
			break;
		}

		//事件分类
		if (NetWorkEvents.lNetworkEvents & FD_ACCEPT)//使用按位与判断是否是对应事件
		{
			if (0 == NetWorkEvents.iErrorCode[FD_ACCEPT_BIT])//判断如果错误数组中对应下标的位置为0,则表示没有错误
			{
				//接受连接
				SOCKET socketClient = accept(socketServer, NULL, NULL);
				if (INVALID_SOCKET == socketClient)
				{
					//创建客户端socket失败
					continue;
				}
				//创建事件
				WSAEVENT wsaClientEvent = WSACreateEvent();
				if (wsaClientEvent == WSA_INVALID_EVENT)
				{
					closesocket(socketClient);
					continue;
				}
				//投放事件给系统
				if (SOCKET_ERROR == WSAEventSelect(socketClient, wsaClientEvent, FD_CLOSE | FD_READ | FD_WRITE))
				{
					closesocket(socketClient);
					WSACloseEvent(wsaClientEvent);
					continue;
				}
				//放进结构体
				esAll.eventAll[esAll.count] = wsaClientEvent;
				esAll.socketAll[esAll.count] = socketClient;
				esAll.count++;
				printf("accept succee\n");
			}
			else
			{
				//否则继续
				continue;
			}
		}
		if (NetWorkEvents.lNetworkEvents & FD_WRITE)//FD_WRITE信号对应的事件是send
		{
			if (0 == NetWorkEvents.iErrorCode[FD_WRITE_BIT])//在创建的时候会被触发一次 可以用来对数据进行初始化
			{
				//
				if (SOCKET_ERROR == send(esAll.socketAll[nIndex], "connect succee", (int)strlen("connect succee"), 0))
				{
					int a = WSAGetLastError();
					printf("发送出错,错误码:%d\n", a);
					continue;
				}
				printf("write succee\n");
			}
			else
			{
				printf("事件触发错误,错误码:%d\n", NetWorkEvents.iErrorCode[FD_WRITE_BIT]);
				continue;
			}
		}
		if (NetWorkEvents.lNetworkEvents & FD_READ)
		{
			if (0 == NetWorkEvents.iErrorCode[FD_READ_BIT])//FD_READ信号对应的事件是recv
			{
				char strRecv[1500] = { 0 };
				if (SOCKET_ERROR == recv(esAll.socketAll[nIndex], strRecv, 1499, 0))
				{
					int a = WSAGetLastError();
					printf("发送出错,错误码:%d\n", a);
					continue;
				}
				printf("read data:%s\n", strRecv);
			}
			else
			{
				continue;
			}
		}
		if (NetWorkEvents.lNetworkEvents & FD_CLOSE)
		{
			if (0 == NetWorkEvents.iErrorCode[FD_CLOSE_BIT])//处理关闭的客户端以及对应事件
			{
				printf("client close\n");
				//清理下线的客户端 套接字 事件
				//套接字
				closesocket(esAll.socketAll[nIndex]);
				esAll.socketAll[nIndex] = esAll.socketAll[esAll.count - 1];
				//事件
				WSACloseEvent(esAll.eventAll[nIndex]);
				esAll.eventAll[nIndex] = esAll.eventAll[esAll.count - 1];

				//数量减一
				esAll.count--;
			}
			else
			{
				printf("关闭错误码:%d\n", NetWorkEvents.iErrorCode[FD_CLOSE_BIT]);
				//清理下线的客户端 套接字 事件
				//套接字
				closesocket(esAll.socketAll[nIndex]);
				esAll.socketAll[nIndex] = esAll.socketAll[esAll.count - 1];
				//事件
				WSACloseEvent(esAll.eventAll[nIndex]);
				esAll.eventAll[nIndex] = esAll.eventAll[esAll.count - 1];

				//数量减一
				esAll.count--;
			}
		}

	}

	//释放事件 句柄
	WSACloseEvent(eventServer);

	closesocket(socketServer);
	WSACleanup();

	return 0;

	
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱笑的蛐蛐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值