IO模型之WSAEventSelect

1. WsaAsyncSelect的缺点

WsaAsyncSelect解决了select模型的问题,但是它最大的缺点就是它只能用在windows程序上,因为它需要一个接收系统消息的窗口句柄,那么有没有一个模型既可以不限定只能是windows程序才能用呢?

2. WsaEventSelect模型

WsaEventSelect模型是一个不用主动去轮询所有客户端套接字是否有数据到来的模型,客户端有数据到来时,系统发送事件来通知我们的程序,这就解决了WsaAsyncSelect模型只能用在windows窗口程序的限制。

3. 在WSAEventSelect模型,流程如下:

1.创建一个事件对象数组,用于存放所有的事件对象;

2.创建一个事件对象(WSACreateEvent);

3.将一组你感兴趣的SOCKET事件与事件对象关联(WSAEventSelect),然后加入事件对象数组;

4.等待事件对象数组上发生一个你感兴趣的网络事件(WSAWaitForMultipleEvents);

5.对发生事件的事件对象查询具体发生的事件类型(WSAEnumNetworkEvents);

6.针对不同的事件类型进行不同的处理;

4. WSACreateEvent函数

WSAEVENT WSACreateEvent(void);

调用WSACreateEvent函数创建一个事件对象

返回值:

  • 成功返回事件对象的句柄。
  • 失败返回WSAINVALIDEVENT。通过WSAGetLastError()获取错误信息。

5. WSAEventSelect函数

int WSAEventSelect(
	SOCKET s,//套接字句柄
    WSAEVENT hEvent,//为事件对象句柄
    Long INetworkEvents);//应用程序感兴趣的网络事件集合

//应用程序为套接字注册网络事件成功

返回值:

  • 成功返回0。
  • 失败返回SOCKET_ERROR通过WSAGetLastError()获取错误信息。

6. WSAWaitForMultipleEvents函数

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

cEvents:指出lphEvents所指数组中事件对象句柄的数目。事件对象句柄的最大值为WSA_MAXIMUM_WAIT_EVENTS。

lphEvents:指向一个事件对象句柄数组的指针。

fWaitAll:指定等待类型。若为真TRUE,则当lphEvents数组中的所有事件对象同时有信号时,函数返回。若为假FALSE,则当任意一个事件对象有信号时函数即返回。在后一种情况下,返回值指出是哪一个事件对象造成函数返回。

dwTimeout:指定超时时间间隔(以毫秒计)。当超时间隔到,函数即返回,不论fWaitAll参数所指定的条件是否满足。如果dwTimeout为零,则函数测试指定的时间对象的状态,并立即返回。如果dwTimeout是WSA_INFINITE,则函数的超时间隔永远不会到。

fAlertable:指定当系统将一个输入/输出完成例程放入队列以供执行时,函数是否返回。若为真TRUE,则函数返回且执行完成例程。若为假FALSE,函数不返回,不执行完成例程。请注意在Win16中忽略该参数。

返回值:
如果函数成功,返回值指出造成函数返回的事件对象。(这一句有问题),应该改成:如果事件数组中有某一个事件被传信了,函数会返回这个事件的索引值,但是这个索引值需要减去预定义值 WSA_WAIT_EVENT_0才是这个事件在事件数组中的位置。

如果函数失败,返回值为WSA_WAIT_FAILED。可调用WSAGetLastError()来获取进一步的错误信息。

注意:

WSAWaitForMultipleEvents函数只能支持由WSA MAXIMUM WAIT EVENTS对象定义的一个最大值,是64,就是说wsAWaitForMultipleEvents只能等待64个事件,如果想同时等待多于64个事件,就要创建额外的工作者线程,就不得不去管理一个线程池!

7. WSAEnumNetworkEvents函数

int WSAEnumNetworkEvents(
	_In_ SOCKET s,//发生事件的SOCKET
    _In_ WSAEVENT hEventObject,//发生事件的事件对象
    _Out_ LPWSANETWORKEVENTS IpNetworkEvents//发生的网络事件
);

检测所指定的套接口上网络事件的发生。

返回值:

成功则返回0。

失败回SOCKET_ERROR错误,通过WSAGetLastError()来获取错误代码。

8. WSANETWORKEVENTS的定义

typedef struct _WSANETWORKEVENTS{
    long INetworkEvents; //发生的网络事件类型
    int iErrorCode[FD_MAX_EVENTS];//网络事件错误代码
}WSANETWORKEVENTS,FAR * LPWSANETWORKEVENTS;

比如当发生FDREAD事件时,那么INetworkEvents&FDREAD将为真,同时iErrorCode[FD_READ_BI]标明了此时的错误代码,为0则没有错误。

9. WSAResetEvent函数

bool WSAResetEVent(WSAEVENT hEvent);

当网络事件到来时,与套接字关联的事件对象由未触发变为触发态。由于它是手工重置事件,应用程序需要手动将事件的状态设置为未触发态。

返回值:

成功返回真TRUE。

失败,返回假FALSE。可调用WSAGetLastError()来获取进一步的错误信息。

10. 服务端代码

#include <WinSock2.h>  
#include <iostream>  
using namespace std;

#pragma comment(lib,"Ws2_32.lib")  


int main()
{
	// 初始化Winsock 2.2
	WSAData wsaData;
	if (0 != WSAStartup(MAKEWORD(2, 2), &wsaData))
	{
		cout << "WSAStartup  error:" << WSAGetLastError() << endl;
		return -1;
	}

	//1创建监听套接字
	SOCKET  sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (sListen == INVALID_SOCKET)
	{
		cout << "socket() Error: " << WSAGetLastError() << endl; 
		return  0;
	}

	sockaddr_in sin;
	sin.sin_family = AF_INET;
	sin.sin_port = htons(8000);
	sin.sin_addr.S_un.S_addr = htonl(ADDR_ANY);
	//2绑定到本机任意地址、端口
	if (SOCKET_ERROR == bind(sListen, (sockaddr*)&sin, sizeof(sin)))
	{
		cout << "bind  error:" << WSAGetLastError() << endl;
		return -1;
	}

	//3监听套接字
	if (listen(sListen, 5) == SOCKET_ERROR)
	{
		cout << " listen() Error:" << WSAGetLastError() << endl;
		return  0;
	}


	WSAEVENT eventArray[WSA_MAXIMUM_WAIT_EVENTS];  // 事件对象数组64  
	SOCKET      sockArray[WSA_MAXIMUM_WAIT_EVENTS]; // 事件对应的SOCKET句柄数组  
	int  N = 0;//事件对象的个数,初始化为0


	//创建第一个事件对象,用于监听socket 关联,注册事件
	WSAEVENT event = ::WSACreateEvent();
	::WSAEventSelect(sListen, event, FD_ACCEPT | FD_CLOSE);
	sockArray[N] = sListen;
	eventArray[N++] = event;//放了第一个事件对象


	while (true)
	{
		//等待事件,false只要有一个对象被触发就返回, 无限等待
		int   ret = ::WSAWaitForMultipleEvents(N, eventArray, false, WSA_INFINITE, false);
		if (ret == WSA_WAIT_FAILED) 
		{
			cout << "WSAWaitForMultipleEvents  error:" << WSAGetLastError() << endl;
			continue;
		}

		//它在数组中的下标为:  (返回值  -  WSA_WAIT_EVENT_0 ) 
		int  index = ret - WSA_WAIT_EVENT_0;

	
		SOCKET sock = sockArray[index];//对应关系,通过索引获取相应的socket

		WSANETWORKEVENTS  netEvents;//返回发生的事件
		//检测发生的事件
		::WSAEnumNetworkEvents(sock, eventArray[index], &netEvents); 
		//接受到客户端的请求事件
		if (netEvents.lNetworkEvents & FD_ACCEPT)
		{
			//无错误
			if (netEvents.iErrorCode[FD_ACCEPT_BIT] == 0)
			{
				if (N >= WSA_MAXIMUM_WAIT_EVENTS)
				{
					cout << "事件对象数组已满!" << endl;
					continue;
				}

				SOCKET client = ::accept(sock, NULL, NULL);
				if (client != INVALID_SOCKET)
				{
					cout << client << "	进入聊天室!" << endl;
					char tmp[64];
					sprintf(tmp, "欢迎%d进入聊天室!", client);
					send(client, tmp, strlen(tmp), 0);

					//关联一个客户端套接字,并注册读事件
					WSAEVENT e = ::WSACreateEvent();
					::WSAEventSelect(client, e, FD_READ | FD_WRITE | FD_CLOSE);
					sockArray[N] = client;
					eventArray[N++] = e;//放到事件数组中
				}
			}
		}
		//读事件
		else if (netEvents.lNetworkEvents & FD_READ)
		{
			if (netEvents.iErrorCode[FD_READ_BIT] == 0)
			{
				char buf[64] = {0}; 
				int ret = recv(sock, buf, 64, 0);
				if (ret > 0)
				{
					cout << sock << "	说: " << buf << endl;
				}
			}
		}
		else if (netEvents.lNetworkEvents & FD_CLOSE)
		{
			//关闭事件对象
			::WSACloseEvent(eventArray[index]);
			::closesocket(sock);
			cout << sock << "	离开了!" << endl;

			//减少数组元素,删除关闭的套接字
			for (int j = index; j<N - 1; j++) {
				eventArray[j] = eventArray[j + 1];
				sockArray[j] = sockArray[j + 1];
			}
			--N;//总数量减一
		}

	}



	//关闭套接字
	closesocket(sListen);

	// 最后应该做一些清除工作
	if (WSACleanup() == SOCKET_ERROR)
	{
		cout << "WSACleanup 出错!" << endl;
	}

}

11. 客户端代码

#include<winsock2.h>//winsock2的头文件
#include<iostream>
using  namespace std;

//勿忘,链接dll的lib
#pragma comment(lib, "ws2_32.lib")

int  main()
{

	//加载winsock2的环境
	WSADATA  wd;
	if (WSAStartup(MAKEWORD(2, 2), &wd) != 0)
	{
		cout << "WSAStartup  error:" << GetLastError() << endl;
		return 0;
	}

	//1.创建流式套接字
	SOCKET  s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (s == INVALID_SOCKET)
	{
		cout << "socket  error:" << GetLastError() << endl;
		return 0;
	}

	//2.链接服务器
	sockaddr_in   addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(8000);
	addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");

	int len = sizeof(sockaddr_in);
	if (connect(s, (SOCKADDR*)&addr, len) == SOCKET_ERROR)
	{
		cout << "connect  error:" << GetLastError() << endl;
		return 0;
	}

	//3接收服务端的消息
	char buf[100] = { 0 };
	recv(s, buf, 100, 0);
	cout << buf << endl;

	//3随时给服务端发消息
	int  ret = 0;
	do
	{
		char buf[64] = { 0 };
		cout << "请输入聊天内容:";
		cin >> buf;
		ret = send(s, buf, 64, 0);
	} while (ret != SOCKET_ERROR&& ret != 0);


	//4.关闭监听套接字
	closesocket(s);

	//清理winsock2的环境
	WSACleanup();



	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

超级D洋葱

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

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

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

打赏作者

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

抵扣说明:

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

余额充值