Windows Sockets网络编程(3)WSAEventSelect模型开发

摘要:WSAEventSelect模型是非阻塞的,该模型允许在一个或者多个套接字上接收以事件为基础的网络事件通知。Windows Sockets应用程序在创建套接字后,调用WSAEventSelect()函数,将一个事件对象与网络事件集合关联在一起。当网络事件发生时,应用程序以事件的形式接收网络事件通知。使用这个模型的基本思路是为感兴趣的一组网络事件创建一个事件对象,再调用WSAEventSelect()函数将网络事件和事件对象关联起来。当网络事件发生时,Winsock使相应的事件对象受信,在事件对象上的等待函数就会返回。WSAEventSelect模型简单易用,也不需要窗口环境。该模型唯一的缺点是有最多等待64个事件对象的限制,当套接字连接数量增加时,就必须创建多个线程来处理I/O,也就是所谓的线程池。


目录:

-------------------------------

- 创建TCP链接

- WSACreateEvent函数

- WSAWaitForMultipleEvents函数

- WSAEnumNetworkEvents函数

- 实践1:WASEventSelect模型

- 实践2:TCP Client


1.创建TCP链接

这里不再赘述了,能来到本文的,相信基本功已经不用多讲了。实在不明白的可以阅读《Windows Sockets网络编程(0)TCP In Action》一文,该文详细的叙述了TCP创建的整个过程。

SOCKET socket_listener = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(8086);
sin.sin_addr.S_un.S_addr = INADDR_ANY;
if (bind(socket_listener, (sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR){
	closesocket(socket_listener);
	return;
}
listen(socket_listener, SOMAXCONN);//SOMAXCONN = 5

2.WSACreateEvent函数

在调用WSAEventSelect函数之间,必须要先创建事件,否则没法监听。Windows事件对象有两个状态“已触发”和“未触发”,而事件对象的工作模式也有两种“人工重设”以及“自动重设”。WSACreateEvent创建的事件——原始状态是“未触发态”,当事件到来时系统会将其置为已触发态,工作模式是“人工重设”。

WSAEVENT    eventArray[WSA_MAXIMUM_WAIT_EVENTS];//WSA_MAXIMUM_WAIT_EVENTS = 64
SOCKET      sockArray[WSA_MAXIMUM_WAIT_EVENTS];
int nEventTotal = 0;
WSAEVENT event = WSACreateEvent();
WSAEventSelect(socket_listener, event, FD_ACCEPT | FD_CLOSE);
eventArray[nEventTotal] = event;
sockArray[nEventTotal] = socket_listener;
++nEventTotal;

另外,需要注意的是WSAResetEvent是将事件从“已触发”态重置为“未触发”状态。而WSACloseEvent主要用来释放事件对象所占用的资源。


3.WSAWaitForMultipleEvents函数

该函数主要用来等待一个或者所有事件变为“已触发”态。

WSAWaitForMultipleEvents(
    _In_ DWORD cEvents,
    _In_reads_(cEvents) const WSAEVENT FAR * lphEvents,
    _In_ BOOL fWaitAll,
    _In_ DWORD dwTimeout,
    _In_ BOOL fAlertable
    );

cEvents :事件句柄的数量,最多为WSA_MAXIMUM_WAIT_EVENTS = 64个;
lphEvents :事件句柄存放处;
fWaitAll :有两种状态,TRUE时表示等待所有事件被触发、FALSE时表示只要一个事件触发,就继续运行;
dwTimeout :超时时间,WSA_INFINITE表示无限等待;

fAlertable:该参数主要用于重叠I/O,此处总是设置为FALSE即可。


int nIndex = WSAWaitForMultipleEvents(nEventTotal, eventArray,FALSE,WSA_INFINITE,FALSE);


WSAWaitForMultipleEvents函数的返回值会指出——到底哪一个事件被触发了(在设置为单一触发的情况下)。这里牵涉到一个宏WSA_WAIT_EVENT_0(该宏在winnt.h中其实是被定义为0的),一般来说where = nIndex - WSA_WAIT_EVENT_0;使用其返回值减去WSA_WAIT_EVENT_0所得的值,则表示事件数组中,具体被触发的那个事件下标。当然,如果fWaitAll设置为TRUE的话,根本不需要计算返回值,因为该情况下,总是全部被触发。


4.WSAEnumNetworkEvents函数

事件发生了,也知道具体是哪个Socket被触发了。但是,为何被触发了?这就需要该函数来确定了。

WSAEnumNetworkEvents(
    _In_ SOCKET s,
    _In_ WSAEVENT hEventObject,
    _Out_ LPWSANETWORKEVENTS lpNetworkEvents
    );

s :被触发的套接字句柄;

hEventObject :需要被重置的Event(WSACreateEvent是“人工重置”的);

lpNetworkEvents:指向_WSANETWORKEVENTS 结构体指针,包含了到底是发生了何种网络事件,或者网络错误。


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

lNetworkEvents:指示发生的网络事件。当一个事件对象成为“已触发”状态时,可能同时发生了多个网络事件。

iErrorCode:网络事件数组。所以事件需要使用一个数组来存放。


WSANETWORKEVENTSevent;
WSAEnumNetworkEvents(sockArray[nIndex], eventArray[nIndex], &event);


那么,具体要如何判断发生了什么自己关注的事件呢?

if (event.lNetworkEvents &FD_ACCEPT)
{

if (event.iErrorCode[FD_ACCEPT_BIT] == 0)

{

}

}

标识符命名是有规则的,如果需要检查某一事件是否发生了错误,在对应事件末尾追加“_BIT”即可。比如FD_ACCEPT网络事件所对应的错误位置即为FD_ACCEPT_BIT。假若iErrorCode[FD_ACCEPT_BIT] == 0,则表示未发生错误。


5.实践1:WASEventSelect模型

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

/*
Date      |Change
-----------------------------
2017-7-24 |WSAEventSelect模型
*/
WSAEVENT    eventArray[WSA_MAXIMUM_WAIT_EVENTS];//WSA_MAXIMUM_WAIT_EVENTS = 64
SOCKET      sockArray[WSA_MAXIMUM_WAIT_EVENTS];
int nEventTotal = 0;

void wsa_event_select()
{
	SOCKET sAccept = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	sockaddr_in sin;
	sin.sin_family = AF_INET;
	sin.sin_port = htons(8086);
	sin.sin_addr.S_un.S_addr = INADDR_ANY;
	if (bind(sAccept, (sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR){
		closesocket(sAccept);
		return;
	}
	listen(sAccept, SOMAXCONN);//SOMAXCONN = 5

	WSAEVENT event = WSACreateEvent();
	WSAEventSelect(sAccept, event, FD_ACCEPT | FD_CLOSE);
	eventArray[nEventTotal] = event;
	sockArray[nEventTotal] = sAccept;
	++nEventTotal;

	while (true)
	{
		int nIndex = WSAWaitForMultipleEvents(nEventTotal, eventArray, FALSE, WSA_INFINITE, FALSE);
		nIndex = nIndex - WSA_WAIT_EVENT_0;

		if (nIndex == WSA_WAIT_FAILED || nIndex == WSA_WAIT_TIMEOUT)
		{
			continue;
		}
		else
		{
			WSANETWORKEVENTS event;
			WSAEnumNetworkEvents(sockArray[nIndex], eventArray[nIndex], &event);
			if (event.lNetworkEvents & FD_ACCEPT)
			{
				if (event.iErrorCode[FD_ACCEPT_BIT] == 0)
				{
					if (nEventTotal > WSA_MAXIMUM_WAIT_EVENTS)
					{
						continue;
					}
					SOCKET sClient = accept(sockArray[nIndex], NULL, NULL);
					WSAEVENT event = WSACreateEvent();
					WSAEventSelect(sClient, event, FD_READ | FD_CLOSE | FD_WRITE);
					eventArray[nEventTotal] = event;
					sockArray[nEventTotal] = sClient;
					++nEventTotal;
					printf("accept.\n");
				}
			}
			else if (event.lNetworkEvents & FD_READ)         // 处理FD_READ通知消息  
			{
				if (event.iErrorCode[FD_READ_BIT] == 0)
				{
					char data[1024];
					int ret = recv(sockArray[nIndex], data, strlen(data), 0);
					if (ret > 0)
					{
						data[ret] = '\0';
						printf("recv: %s.\n",data);
						send(sockArray[nIndex], data, strlen(data), 0);
					}
				}
			}
			else if (event.lNetworkEvents & FD_CLOSE)
			{
				if (event.iErrorCode[FD_CLOSE_BIT] == 0)
				{
					printf("close.\n");
					closesocket(sockArray[nIndex]);
					for (int j = nIndex; j < nEventTotal - 1; j++)
					{
						eventArray[j] = eventArray[j + 1];
						sockArray[j] = sockArray[j + 1];
					}
					--nEventTotal;
				}
			}
			else if (event.lNetworkEvents & FD_WRITE)
			{
				printf("send.\n");
				char* data = "qingdujun";
				send(sockArray[nIndex], data, sizeof(data)/sizeof(char), 0);
			}
		}
	}
	
}

int main(int argc, char* argv[])
{
	WSADATA wsa;
	WSAStartup(MAKEWORD(2, 2), &wsa);
	wsa_event_select();
	WSACleanup();
	return 0;
}

6.实践2:TCP Client

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

/*
Date      |Change
-----------------------------------------
2017-7-24 |WSAEventSelect模型,TCP测试客户端
*/
void tcp_client()
{
	SOCKET sClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	sockaddr_in sin;
	sin.sin_family = AF_INET;
	sin.sin_port = htons(8086);
	sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	if (connect(sClient, (sockaddr *)&sin, sizeof(sin)) == SOCKET_ERROR){
		closesocket(sClient);
		return;
	}
	char buffer[1024] = "abcdef";
	send(sClient, buffer, strlen(buffer), 0);
	int ret = recv(sClient, buffer, sizeof(buffer), 0);
	buffer[ret] = '\0';
	printf("%s.\n",buffer);
	closesocket(sClient);
	
}

int main(int argc, char* argv[])
{
	WSADATA wsa;
	WSAStartup(MAKEWORD(2, 2), &wsa);
	int i = 5;
	while (i--){
		tcp_client();
		Sleep(2000);
	}
	WSACleanup();
	return 0;
}


参考文献:

[1] 孙海民. 精通Windows Sockets网络开发——基于Visual C++实现[M]. 北京:人民邮电出版社, 2008. 238-245



@qingdujun

2017-7-25 in Xi'An

  • 2
    点赞
  • 1
    评论
  • 6
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
程序员的必经之路! 【限时优惠】 现在下单,还享四重好礼: 1、教学课件免费下载 2、课程案例代码免费下载 3、专属VIP学员群免费答疑 4、下单还送800元编程大礼包 【超实用课程内容】  根据《2019-2020年中国开发者调查报告》显示,超83%的开发者都在使用MySQL数据库。使用量大同时,掌握MySQL早已是运维、DBA的必备技能,甚至部分IT开发岗位也要求对数据库使用和原理有深入的了解和掌握。 学习编程,你可能会犹豫选择 C++ 还是 Java;入门数据科学,你可能会纠结于选择 Python 还是 R;但无论如何, MySQL 都是 IT 从业人员不可或缺的技能!   套餐中一共包含2门MySQL数据库必学的核心课程共98课时   课程1:《MySQL数据库从入门到实战应用》   课程2:《高性能MySQL实战课》   【哪些人适合学习这门课程?】  1平时只接触了语言基础,并未学习任何数据库知识的人;  2对MySQL掌握程度薄弱的人,课程可以让你更好发挥MySQL最佳性能; 3想修炼更好的MySQL内功,工作中遇到高并发场景可以游刃有余; 4被面试官打破沙锅问到底的问题问到怀疑人生的应聘者。 【课程主要讲哪些内容?】 课程一:《MySQL数据库从入门到实战应用》 主要从基础篇,SQL语言篇、MySQL进阶篇三个角度展开讲解,帮助大家更加高效的管理MySQL数据库。 课程二:《高性能MySQL实战课》主要从高可用篇、MySQL8.0新特性篇,性能优化篇,面试篇四个角度展开讲解,帮助大家发挥MySQL的最佳性能的优化方法,掌握如何处理海量业务数据和高并发请求 【你能收获到什么?】  1.基础再提高,针对MySQL核心知识点学透,用对; 2.能力再提高,日常工作中的代码换新貌,不怕问题; 3.面试再加分,巴不得面试官打破沙锅问到底,竞争力MAX。 【课程如何观看?】  1、登录CSDN学院 APP 在我的课程中进行学习; 2、移动端:CSDN 学院APP注意不是CSDN APP哦  本课程为录播课,课程永久有效观看时长 【资料开放】 课件、课程案例代码完全开放给你,你可以根据所学知识,自行修改、优化。  下载方式:电脑登录课程观看页面,点击右侧课件,可进行课程资料的打包下载。
©️2020 CSDN 皮肤主题: 精致技术 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值