• C++Socket编程—socket网络模型之事件选择模型模型


    一、什么是事件选择模型     

       事件选择(WSAEventSelect)模型是另一个有用的异步 I/O 模型。和 WSAAsyncSelect 模型类似的是,它也允许应用程序在一个或多个套接字上,接收以事件为基础的网络事件通知,最主要的差别在于网络事件会投递至一个事件对象句柄,而非投递到一个窗口例程。
        每一个socket都配备一个event,开发者可以为event注册对应的网络事件,当
    有事件来的时候,对应socket的event就会变成有信号状态。

    二、事件选择模型API函数

        事件通知模型要求我们的应用程序针对使用的每一个套接字,首先创建一个事件对象。创建方法是调用 WSACreateEvent 函数,它的定义如下:
     WSAEVENT WSACreateEvent(void);
        WSACreateEvent 函数的返回值很简单,就是一个创建好的事件对象句柄,接下来必须将其与某个套接字关联在一起,同时注册自己感兴趣的网络事件类型
    (FD_READ、FD_WRITE、FD_ACCEPT、FD_CONNECT、FD_CLOSE等),方法是调用 WSAEventSelect 函数,其定义如下:

       int WSAEventSelect(
    __in SOCKET s,                       //代表感兴趣的套接字
    __in WSAEVENT hEventObject,         //指定要与套接字关联在一起的事件对象,即用 WSACreateEvent 创建的那一个
    __in long lNetworkEvents            //对应一个“位掩码”,用于指定应用程序感兴趣的各种网络事件类型的一个组合。
    );
    


        其中参数 lNetworkEvents可以用以下数值进行OR操作
           FD_READ 应用程序想要接收有关是否可读的通知,以便读入数据
           FD_WRITE 应用程序想要接收有关是否可写的通知,以便写入数据
           FD_ACCEPT 应用程序想接收与进入连接有关的通知
           FD_CONNECT 应用程序想接收与一次连接完成的通知
           FD_CLOSE 应用程序想接收与套接字关闭的通知

        WSACreateEvent 创建的事件有两种工作状态,以及两种工作模式。工作状态分别是“已传信”(signaled)和“未传信”(nonsignaled)。工作模式则包括“人工重设”(manual reset)和“自动重设”(auto reset)。WSACreateEvent 开始是在一种未传信的工作状态,并用一种人工重设模式,来创建事件句柄。随着网络事件触发了与一个套接字关联在一起的事件对象,工作状态便会从“未传信”转变成“已传信”。由于事件对象是在一种人工重设模式中创建的,所以在完成了一个 I/O 请求的处理之后,我们的应用程序需要负责将工作状态从已传信更改为未传信。要做到这一点,可调用 WSAResetEvent 函数,对它的定义如下:
       BOOL WSAResetEvent(
    __in WSAEVENT hEvent   //事件句柄;
    );
        该函数调用是成功还是失败,会分别返回TRUE或FALSE。
        应用程序完成了对一个事件对象的处理后,便应调用WSACloseEvent函数,释放由事件句柄使用的系统资源。对 WSACloseEvent 函数的定义如下:
       BOOL WSACloseEvent(
    __in WSAEVENT hEvent  //事件句柄;
    );
        该函数调用是成功还是失败,会分别返回TRUE或FALSE。
    一个套接字同一个事件对象句柄关联在一起后,应用程序便可开始I/O处理;方法是等待网络事件触发事件对象句柄的工作状态。WSAWaitForMultipleEvents 函数的设
    计宗旨便是用来等待一个或多个事件对象句柄,并在事先指定的一个或所有句柄进入“已传信”状态后,或在超过了一个规定的时间周期后,立即返回。下面是

    WSAWaitForMultipleEvents 函数的定义:

     DWORD WSAWaitForMultipleEvents(
    __in DWORD cEvents,
    __in const WSAEVENT* 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 参数指定了 WSAWaitForMultipleEvents 如何等待在事件数组中的对象。若设为TRUE,那么只有等 lphEvents 数组内包含的所有事件对象都已进入“已
    传信”状态,函数才会返回;但若设为FALSE,任何一个事件对象进入“已传信”状态,函数就会返回。就后一种情况来说,返回值指出了到底是哪个事件对象造成了函数的
    返回。通常,应用程序应将该参数设为 FALSE,一次只为一个套接字事件提供服务。
        dwTimeout参数规定了 WSAWaitForMultipleEvents 最多可等待一个网络事件发生有多长时间,以毫秒为单位,这是一项“超时”设定。超过规定的时间,函数就会
    立即返回,即使由 fWaitAll 参数规定的条件尚未满足也如此。考虑到它对性能造成的影响,应尽量避免将超时值设为0。假如没有等待处理的事件,
    WSAWaitForMultipleEvents 便会返回 WSA_WAIT_TIMEOUT。如 dwTimeout 设为 WSAINFINITE(永远等待),那么只有在一个网络事件传信了一个事件对象
    后,函数才会返回。

    三、代码示例

    客户端服务器收发数据进行通信:

    server:

    #include <Winsock2.h>
    #include <windows.h>
    #include "TcpSocket.h"
    #include "CLock.h"
    #include <vector>
    using namespace std;
    
    CLock g_lock;
    
    bool HandleData(SOCKET sockClient)
    {
    	// 5) 收发数据
    	char aryBuff[MAXWORD] = { 0 };
    	int nRet = recv(sockClient, aryBuff, sizeof(aryBuff), 0);
    	if (nRet == 0 || nRet == SOCKET_ERROR)
    	{
    		printf("接受数据失败 
    ");
    		return false;
    	}
    	printf("收到数据: %s 
    ", aryBuff);
    
    	char szBuff[] = { "recv OK 
    " };
    	nRet = send(sockClient, szBuff, sizeof(szBuff), 0);
    	if (nRet == SOCKET_ERROR)
    	{
    		printf("数据发送失败 
    ");
    		return false;
    	}
    
    	return true;
    }
    
    //线程, 用来处理客户端, 和客户端进行数据的收发
    DWORD WINAPI HandleClientsThread(LPVOID pParam)
    {
    	//vector<SOCKET>& vctClients =*(vector<SOCKET>*)pParam; 
    	vector<pair<SOCKET, WSAEVENT>>& vctClients = *(vector<pair<SOCKET, WSAEVENT>>*)pParam;
    
    	while (TRUE)
    	{
    		/*
    		fd_set fdRead;
    		FD_ZERO(&fdRead); //初始化
    		*/
    		WSAEVENT aryEvents[WSA_MAXIMUM_WAIT_EVENTS];
    		int nCount = 0;
    
    		//把所有客户端加入数组
    
    		g_lock.Lock();
    
    		for (auto& pairSockEvent : vctClients)
    		{
    			//FD_SET(sock, &fdRead);
    			aryEvents[nCount++] = pairSockEvent.second;
    		}
    		g_lock.UnLock();
    
    
    		/*
    		timeval tv = { 1, };
    		int nRet = select(fdRead.fd_count,
    			&fdRead,
    			NULL,
    			NULL,
    			&tv);
    
    		if (nRet == 0 || nRet==SOCKET_ERROR)
    		{
    			continue;
    		}
    		*/
    		//检测指定的socket
    		int nRet = WSAWaitForMultipleEvents(nCount, aryEvents, FALSE, 1000, FALSE);
    		if (nRet == WSA_WAIT_TIMEOUT) //超时继续等待
    		{
    			continue;
    		}
    
    		//处理数据
    
    		g_lock.Lock();
    		for (auto itr = vctClients.begin(); itr != vctClients.end(); itr++)
    		{
    			//判断sock是否可以读数据
    			//if (FD_ISSET(*itr, &fdRead))
    			//判断socket是否是可以读数据了
    			if (itr->second == aryEvents[nRet])
    			{
    				WSANETWORKEVENTS workevent;
    				WSAEnumNetworkEvents(itr->first, itr->second, &workevent);
    				if (workevent.lNetworkEvents & FD_READ)
    				{
    				//if (itr->second == aryEvents[nRet]);
    					if (!HandleData(itr->first))
    						{
    							//连接断开
    							vctClients.erase(itr);
    							break;
    						}
    					}
    					else if (workevent.lNetworkEvents & FD_CLOSE)
    					{
    					vctClients.erase(itr);
    					}
    			}
    		}
    		g_lock.UnLock();
    	}
    
    	return 0;
    }
    

     client

    int main()
    {
    	//1.创建socket
    	SOCKET sockServer = socket(AF_INET,
    		SOCK_STREAM,
    		IPPROTO_TCP
    	);
    	//2.绑定端口
    	sockaddr_in siServer;
    	siServer.sin_family = AF_INET;
    	siServer.sin_port = htons(9527);
    	siServer.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    	int nRet = bind(sockServer, (sockaddr*)&siServer, sizeof(siServer));
    	if (nRet == SOCKET_ERROR)
    	{
    		printf("端口绑定失败
    ");
    		return 0;
    	}
    	//3.监听
    	nRet = listen(sockServer, SOMAXCONN);//监听最大值
    	if (nRet == SOCKET_ERROR)
    	{
    		printf("监听失败
    ");
    		return 0;
    	}
    	//创建线程,检测socket是否有数据可读并处理
    	//vector<SOCKET> vctClients;
    	vector<pair<SOCKET, WSAEVENT>>vctClients;
    	HANDLE hTread = CreateThread(NULL, 0, HandleClientsThread, (LPVOID)&vctClients, 0, NULL);
    	CloseHandle(hTread);
    
    	while (true)
    	{
    		// 4) 接受连接
    		sockaddr_in siClient;
    		int nSize = sizeof(siClient);
    		printf("客户端已就绪,等待连接");
    		SOCKET sockClient = accept(sockServer, (sockaddr*)&siClient, &nSize);
    		if (sockClient == SOCKET_ERROR)
    		{
    			printf("接受连接失败 
    ");
    			return 0;
    		}
    		printf("IP:%s port:%d 连接到服务器. 
    ",
    			inet_ntoa(siClient.sin_addr),
    			ntohs(siClient.sin_port));
    
    		g_lock.Lock();
    
    		WSAEVENT hEvent = WSACreateEvent();
    		WSAEventSelect(sockClient, hEvent, FD_READ | FD_CLOSE);
    		vctClients.push_back(make_pair(sockClient, hEvent));
    		g_lock.UnLock();
    
    	}
    
    	return 0;
    }
    
  • 相关阅读:
    设计模式-观察者模式(Observer Pattern)
    设计模式-策略模式(Strategy Pattern)
    数据结构-红黑树
    数据结构-二叉搜索树(BST binary search tree)
    算法-插入排序(Insertion sorting)
    算法-桶排序(Bucket sort)
    设计模式-单例模式(Singleton Pattern)
    算法-基数排序(radix sort)
    算法-计数排序及其变体
    Pytest框架的使用
  • 原文地址:https://www.cnblogs.com/zhaoyixiang/p/12964774.html
Copyright © 2020-2023  润新知