【网络编程】事件选择模型

2023-05-12 21:38:52 浏览数 (2)

事件选择模型

windows处理用户行为的两种方式

消息机制

核心:消息队列

处理过程:所有的用户操作,比如点鼠标,按键盘,对软件进行的各种操作…等等,所有操作均依次按顺序被记录,装进一个队列。不同的操作对应着不同的编号。

特点:消息队列由操作系统维护,用户进行操作,然后把消息读取出来,分类处理。有先后顺序。动态的。

异步选择模型就是基于这个消息的

事件机制

核心:事件集合

处理过程:根据需求我们为用户的特定操作绑定一个事件,事件由我们自己调用API创建,需要多少创建多少。

将时间投递给系统,系统就帮我们监视着,所以不能无限创建,太多了系统运行就卡了。

如果操作发生了,比如用户按鼠标了,那么对应的事件就会被置成有信号,也就是类似1变2,用个数来标记。

直接获取到有信号的时间进行处理。

特点:所有时间都是咱们自己定义的,系统只是帮咱们标记有无信号。无序

事件选择模型,就是应用这个。

事件选择

整体类似于select

  1. 创建一个事件对象(变量),WSACreateEvent
  2. 为每一个事件对象绑定socket以及操作accept,read,close… 投递给系统让其监管,WSAEventSelect
  3. 查看事件是否有信号,WSAWaitForMultipleEvents
  4. 有信号就分类处理,WSAEnumNetworkEvents
代码语言:javascript复制
网路头文件 网络库
	打开网络库
    校验版本
    创建SOCKET
    绑定地址与端口
    开始监听 
    
    事件选择

创建一个事件对象

代码语言:javascript复制
WSAEVENT WSAAPI WSACreateEvent();

代码语言:javascript复制
WSAEVENT eventServer = WSACreateEvent();
代码语言:javascript复制
成功-返回一个事件
失败-返回WSA_INVALID_EVENT(无效的事件对象) 
    if (eventServer == WSA_INVALID_EVENT)
	{
		int a = WSAGetLastError();
		closesocket(socketServer);
		WSACleanup();
		return 0;
	}

就是定义了一个事件类型。

HANDLE(void *通用类型指针),句柄,句柄的本质是ID,内核对象,唯一的标识符。

内核对象内核对象是系统提供的用户模式下代码与内核模式下代码进行交互的基本接口(百度百科)。

当调用一个用于创建内核对象的函数时,该函数就返回一个用于标识该对象的**句柄**

代码语言:javascript复制
内核对象
    由系统在内核申请
    由操作系统访问
    我们不能定位其内容,也不能修改 
    	void* 通用类型指针
    	对内核的保护,对规则的保护,从而使操作系统有序的平稳的,
    	有效的运行,而不会随便出问题
    调用函数创建,调用函数释放
    	如果我们没有调用释放,那么他可能就一直存在于内核,
    	造成内核内存泄漏, 这种只能重启电脑
    内核对象有哪些 socket Kernel Objects

关闭/释放事件句柄

不用就要释放

代码语言:javascript复制
BOOL WSAAPI WSACloseEvent
(
  WSAEVENT hEvent
);

代码语言:javascript复制
WSACloseEvent(eventServer);

指定事件主动置成无信号的

代码语言:javascript复制
BOOL WSAAPI WSAResetEvent
(
  WSAEVENT hEvent
);

指定事件主动置成有信号的

代码语言:javascript复制
BOOL WSAAPI WSASetEvent
(
  WSAEVENT hEvent
);

绑定并投递

代码语言:javascript复制
int WSAAPI WSAEventSelect(
	SOCKET s,
	WSAEVENT hEventObject,
	long INetworkEvents
);
功能

给事件绑上socket与操作码,并投递给操作系统。

参数1

被绑定的socket,最终每个socket都会被绑定一个事件

参数2

事件对象,逻辑,就是将参数1和参数2绑定在一起

参数3

具体事件

代码语言:javascript复制
FD_ACCEPT
   有客户端链接,与服务器socket绑定
FD_READ
   有客户端发来消息,与客户端socket绑定,可多个属性并列使用。
FD_CLOSE
   客户端下线,与客户端绑定,包含强制下线
FD_WRITE
   可以给客户端发信,与客户端socket绑定,会在accept之后立即主动产生该信号。可以说明,客户端连接成功。即可随时send
FD_CONNECT
   用在客户端上,给服务器绑定这个。
0
   取消事件绑定。
FD_OOB 
   带外数据,一般不使用。
FD_QOS 
  	套接字服务质量状态发生变化。动态变化。
FD_GROUP_QOS
   保留——还没有对其赋值具体意义。    
代码语言:javascript复制
重叠I/O模型中
    FD_ROUTING_ INTERFACE_CHANGE
    	
  	FD_ADDRESS_ LIST_CHANGE
返回值
代码语言:javascript复制
成功——返回0
失败——返回SOCKET_ERROR
 	if (SOCKET_ERROR == WSAEventSelect(socketServer, SetEvent, FD_ACCEPT);)
	{
		int a = WSAGetLastError();
		//释放事件句柄
		WSACloseEvent(eventServer);
		//释放所有socket
		closesocket(socketServer);
		//关闭网络库
		WSACleanup();
		return 0;
	}   

询问事件

代码语言:javascript复制
DWORD WSAAPI WSAWaitForMultipleEvents(
  DWORD          cEvents,
  const WSAEVENT *lphEvents,
  BOOL           fWaitAll,
  DWORD          dwTimeout,
  BOOL           fAlertable
);
功能

获取发生信号的事件。

参数1

事件个数,定义事件列表(数组)个数。

代码语言:javascript复制
最大64  WSA_MAXIMUM_WAIT_EVENTS

可以变大,不像select模型,直接就能变大,因为select模型本身就是个数组,直接遍历即可, 比较直接,而事件选择是按照异步来投放,由系统进行管理,咱们就不能随便修改了,要按照规则来。

参数2

事件列表。

参数3

事件等待方式。

代码语言:javascript复制
TRUE
    所有事件都产生信号,才返回。
FALSE
    任何一个事件产生信号,立即返回。
   	返回值减去WSA_WAIT_EVENT_0表示事件对象的索引,其状态导致函数返回。
     如果在调用期间发出多个事件对象的信号,则这是信号事件对象的数组索引,其中所有信号事件对象的索引值最小。
参数4

超时间隔,以毫秒为单位。与select参数5意义相同。

代码语言:javascript复制
123 等待123秒,超时返回WSA_WAIT_TIMEOUT
0	检查事件对象的状态并立即返回。不管有没有信号 
WSA_INFINITE 等待,直到事件发生。
参数5
代码语言:javascript复制
TRUE 重叠I/O模型使用
FALSE 
返回值
代码语言:javascript复制
数组下标的运算值,参数3为TRUE 所有时间均有信号
				参数3位FALSE 返回值减去WSA_WAIT_EVENT_0==数												组中事件的下标
WSA_WAIT_IO_COMPLETION 参数5为TRUE,才会返回这个值			WSA_WAIT_TIMEOUT 超时了,continue即可。	
    
    
    
    while (1)
	{
		//询问
		DWORD nRes = WSAWaitForMultipleEvents(esSet.count, esSet.eventall, FALSE,WSA_INFINITE, FALSE);
		if (nRes == WSA_WAIT_FAILED)
		{
			printf("错误码%dn", WSAGetLastError());
			break;
		}


		//超时使用
		/*if (WSA_WAIT_TIMEOUT == nRes)
		{
			continue;
		}*/


		DWORD nIndex = nRes - WSA_WAIT_EVENT_0;

	}

列举事件

代码语言:javascript复制
int WSAAPI WSAEnumNetworkEvents(
  SOCKET             s,
  WSAEVENT           hEventObject,
  LPWSANETWORKEVENTS lpNetworkEvents
);

获取事件类型,并将事件上的信号重置,accept,recv,close等

代码语言:javascript复制
WSANETWORKEVENTS NetworkEvents;
if(SOCKET_ERROR==WSAEnumNetworkEvents(esSet.sockall[nIndex], esSet.eventall[nIndex], &NetworkEvents))
{
			int a = WSAGetLastError();
			printf("错误码:%dn", a);
			break;
}
参数1

对应的socket

参数2

对应的事件

参数3

触发的事件类型在这里装着。是一个结构体指针。

代码语言:javascript复制
struct _WSANETWORKEVENTS
 {
  long lNetworkEvents;
    //具体操作,一个信号可能包含两个信息,以按位或的形式存在
  int  iErrorCode[FD_MAX_EVENTS];
    //错误码数组,FD_ACCEPT事件错误码在FD_ACCEPT_BIT下标里
    //没有错误,对应的就是0
} 
返回值
代码语言:javascript复制
成功——返回0
失败——返回SOCKET_ERROR			

事件分类处理逻辑

代码语言:javascript复制
if (lpNetworkEvents->lNetworkEvents & FD_ACCEPT)
{
    if (lpNetworkEvents->iErrorCode[FD_ACCEPT_BIT] == 0)
    {
        //接受链接
       //创建事件
       //投放事件
       //元素增加
    }
}
代码语言:javascript复制
switch不行有大bug
else if 不太行,有小bug

有序处理

优化

代码语言:javascript复制
for (i = Index; i < EventTotal; i  )
 {
        Index = WSAWaitForMultipleEvents(1, &EventArray[i], TRUE, 1000, FALSE);
        if ((Index != WSA_WAIT_FAILED) && (Index != WSA_WAIT_TIMEOUT)) 
        {
            WSAEnumNetworkEvents(SocketArray[i], EventArray[i], &NetworkEvents);
            //分类处理
        }
}

增加事件数量

当前代码是一组一组投递,一组是64个,由WSAWaitForMultipleEvents这个函数决定。

代码语言:javascript复制
增加事件数量————一个一个投,一个大数组就行了,
    	   ————一组一组投,单线程,一组一组顺序处理就好了。
    			创建多线程,每个线程处理一个事件表,最大是64

完整代码

代码语言:javascript复制
#define _CRT_SECURE_NO_WARNINGS
//#define FD_SETSIZE 128
#define _WINSOCK_DEPRECATED_NO_WARNINGS 
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <Winsock2.h>
#include <string.h>
#pragma comment(lib, "Ws2_32.lib")

struct fd_es_set
{
	unsigned short count;
	SOCKET sockall[WSA_MAXIMUM_WAIT_EVENTS];
	WSAEVENT evnetall[WSA_MAXIMUM_WAIT_EVENTS];
};

struct fd_es_set esSet;

BOOL WINAPI fun(DWORD dwCtrlType)
{
	switch (dwCtrlType)
	{
	case CTRL_CLOSE_EVENT:
		//释放所有socket
		for (int i = 0; i < esSet.count; i  )
		{
			closesocket(esSet.sockall[i]);
			WSACloseEvent(esSet.evnetall[i]);
		}

		break;
	}

	return TRUE;
}

int main(void)
{
	SetConsoleCtrlHandler(fun, TRUE);

	WORD wdVersion = MAKEWORD(2, 2); //2.1  //22
	//int a = *((char*)&wdVersion);
	//int b = *((char*)&wdVersion 1);
	WSADATA wdScokMsg;
	//LPWSADATA lpw = malloc(sizeof(WSADATA));// WSADATA*
	int nRes = WSAStartup(wdVersion, &wdScokMsg);

	if (0 != nRes)
	{
		switch (nRes)
		{
		case WSASYSNOTREADY:
			printf("重启下电脑试试,或者检查网络库");
			break;
		case WSAVERNOTSUPPORTED:
			printf("请更新网络库");
			break;
		case WSAEINPROGRESS:
			printf("请重新启动");
			break;
		case WSAEPROCLIM:
			printf("请尝试关掉不必要的软件,以为当前网络运行提供充足资源");
			break;
		}
		return  0;
	}

	//校验版本
	if (2 != HIBYTE(wdScokMsg.wVersion) || 2 != LOBYTE(wdScokMsg.wVersion))
	{
		//说明版本不对
		//清理网络库
		WSACleanup();
		return 0;
	}

	SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	//int a = WSAGetLastError();
	if (INVALID_SOCKET == socketServer)
	{
		int a = WSAGetLastError();
		//清理网络库
		WSACleanup();
		return 0;
	}

	struct sockaddr_in si;
	si.sin_family = AF_INET;
	si.sin_port = htons(12345);
	si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	//int a = ~0;
	if (SOCKET_ERROR == bind(socketServer, (const struct sockaddr*)&si, sizeof(si)))
	{
		//出错了
		int a = WSAGetLastError();
		//释放
		closesocket(socketServer);
		//清理网络库
		WSACleanup();
		return 0;
	}

	if (SOCKET_ERROR == listen(socketServer, SOMAXCONN))
	{
		//出错了
		int a = WSAGetLastError();
		//释放
		closesocket(socketServer);
		//清理网络库
		WSACleanup();
		return 0;
	}



	//创建事件
	WSAEVENT eventServer = WSACreateEvent();
	if (WSA_INVALID_EVENT == eventServer)
	{
		//出错了
		int a = WSAGetLastError();
		//释放
		closesocket(socketServer);
		//清理网络库
		WSACleanup();
		return 0;
	}

	if (SOCKET_ERROR == WSAEventSelect(socketServer, eventServer, FD_ACCEPT))
	{
		//出错了
		int a = WSAGetLastError();
		//释放事件句柄
		WSACloseEvent(eventServer);
		//释放所有socket
		closesocket(socketServer);
		//清理网络库
		WSACleanup();
		return 0;
	}

	//装进去
	esSet.evnetall[esSet.count] = eventServer;
	esSet.sockall[esSet.count] = socketServer;
	esSet.count  ;

	while (1)
	{

		//询问
		DWORD nRes = WSAWaitForMultipleEvents(esSet.count, esSet.evnetall, FALSE, WSA_INFINITE, FALSE);
		if (WSA_WAIT_FAILED == nRes)
		{
			int a = WSAGetLastError();
			//出错了
			printf("错误码:%dn", a);
			break;
		}
		//超时使用
		//if (WSA_WAIT_TIMEOUT == nRes)
		//{
		//	continue;
		//}
		//
		DWORD nIndex = nRes - WSA_WAIT_EVENT_0;

		//得到下标对应的具体操作
		WSANETWORKEVENTS NetworkEvents;
		if (SOCKET_ERROR == WSAEnumNetworkEvents(esSet.sockall[nIndex], esSet.evnetall[nIndex], &NetworkEvents))
		{
			int a = WSAGetLastError();
			//出错了
			printf("错误码:%dn", a);
			break;
		}

		if (NetworkEvents.lNetworkEvents & FD_ACCEPT)
		{
			if (0 == NetworkEvents.iErrorCode[FD_ACCEPT_BIT])
			{
				//正常处理
				SOCKET socketClient = accept(esSet.sockall[nIndex], NULL, NULL);
				if (INVALID_SOCKET == socketClient)
				{
					continue;
				}

				//创建事件对象
				WSAEVENT wsaClientEvent = WSACreateEvent();
				if (WSA_INVALID_EVENT == wsaClientEvent)
				{
					closesocket(socketClient);
					continue;
				}

				//投递给系统
				if (SOCKET_ERROR == WSAEventSelect(socketClient, wsaClientEvent, FD_READ | FD_CLOSE | FD_WRITE))
				{
					closesocket(socketClient);
					WSACloseEvent(wsaClientEvent);
					continue;
				}

				//装进结构体
				esSet.sockall[esSet.count] = socketClient;
				esSet.evnetall[esSet.count] = wsaClientEvent;
				esSet.count  ;

				printf("accept eventn");
			}
			else
			{
				continue;
			}
		}

		if (NetworkEvents.lNetworkEvents & FD_WRITE)
		{
			if (0 == NetworkEvents.iErrorCode[FD_WRITE_BIT])
			{
				//初始化

				if (SOCKET_ERROR == send(esSet.sockall[nIndex], "connect success", strlen("connect success"), 0))
				{
					int a = WSAGetLastError();
					printf("send faild, error code:%dn", a);
					continue;
				}
				printf("write eventn");
			}
			else
			{
				printf("socket error code:%dn", NetworkEvents.iErrorCode[FD_WRITE_BIT]);
				continue;
			}
		}

		if (NetworkEvents.lNetworkEvents & FD_READ)
		{
			if (0 == NetworkEvents.iErrorCode[FD_READ_BIT])
			{
				char strRecv[1500] = { 0 };
				if (SOCKET_ERROR == recv(esSet.sockall[nIndex], strRecv, 1499, 0))
				{
					int a = WSAGetLastError();
					printf("recv faild, error code:%dn", a);
					continue;
				}
				printf("recv data: %sn", strRecv);
			}
			else
			{
				printf("socket error code:%dn", NetworkEvents.iErrorCode[FD_READ_BIT]);
				continue;
			}
		}

		if (NetworkEvents.lNetworkEvents & FD_CLOSE)
		{
			/*if (0 == NetworkEvents.iErrorCode[FD_CLOSE_BIT])
			{

			}
			else
			{

			}
			WSAECONNABORTED;*/

			//打印
			printf("client closen");
			printf("client force out: %dn", NetworkEvents.iErrorCode[FD_CLOSE_BIT]);
			//清理下线的客户端 套接字  事件
			//套接字
			closesocket(esSet.sockall[nIndex]);
			esSet.sockall[nIndex] = esSet.sockall[esSet.count - 1];
			//事件
			WSACloseEvent(esSet.evnetall[nIndex]);
			esSet.evnetall[nIndex] = esSet.evnetall[esSet.count - 1];

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


	}

	for (int i = 0; i < esSet.count; i  )
	{
		closesocket(esSet.sockall[i]);
		WSACloseEvent(esSet.evnetall[i]);
	}

	//清理网络库
	WSACleanup();

	system("pause");
	return 0;
}

对比select模型

事件选择模型——异步

select模型——同步

0 人点赞