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("接受數據失敗 \n");
		return false;
	}
	printf("收到數據: %s \n", aryBuff);

	char szBuff[] = { "recv OK \r\n" };
	nRet = send(sockClient, szBuff, sizeof(szBuff), 0);
	if (nRet == SOCKET_ERROR)
	{
		printf("數據發送失敗 \n");
		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("端口綁定失敗\n");
		return 0;
	}
	//3.監聽
	nRet = listen(sockServer, SOMAXCONN);//監聽最大值
	if (nRet == SOCKET_ERROR)
	{
		printf("監聽失敗\n");
		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("接受連接失敗 \r\n");
			return 0;
		}
		printf("IP:%s port:%d 連接到服務器. \r\n",
			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;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM