这是我参与11月更文挑战的第二十四天,活动详情查看:2021最后一次更文挑战
事件选择模型
目的和要求
1. 了解事件选择模型的应用场景;
2. 掌握事件选择模型的通讯过程;
3. 掌握事件选择模型的代码实现;
4. 了解事件选择模型的改进方法:有序及增加客户端数量。
服务器端:
1、包含网络头文件网络库
#include <WinSock2.h>
#include <stdio.h>
#pragma comment(lib, "Ws2_32.lib")
#pragma warning(disable:4996);
复制代码
2、打开网络库
int nRes = WSAStartup(wdVersion, &wdScokMsg);
if (0 != nRes)
{
switch (nRes)
{
case WSASYSNOTREADY:
printf("解决方案:重启。。。\n");
break;
case WSAVERNOTSUPPORTED:
break;
case WSAEINPROGRESS:
break;
case WSAEPROCLIM:
break;
case WSAEFAULT:
break;
}
return 0;
}
复制代码
3、校验版本
if (2 != HIBYTE(wdScokMsg.wVersion) || 2 != LOBYTE(wdScokMsg.wVersion))
{
printf("版本有问题!\n");
WSACleanup();
return 0;
}
复制代码
4、创建SOCKET
SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (INVALID_SOCKET == socketServer)
{
int err = WSAGetLastError();
//清理网络库,不关闭句柄
WSACleanup();
return 0;
}
struct sockaddr_in si;
si.sin_family = AF_INET;
si.sin_port = htons(12345);//用htons宏将整型转为端口号的无符号整型
si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
复制代码
5、绑定地址与端口
if (SOCKET_ERROR == bind(socketServer, (const struct sockaddr*)&si, sizeof(si)))
{
int err = WSAGetLastError();//取错误码
printf("服务器bind失败错误码为:%d\n", err);
closesocket(socketServer);//释放
WSACleanup();//清理网络库
return 0;
}
printf("服务器端bind成功!\n");
复制代码
6、开始监听
if (SOCKET_ERROR == listen(socketServer, SOMAXCONN))
{
int err = WSAGetLastError();//取错误码
printf("服务器监听失败错误码为:%d\n", err);
closesocket(socketServer);//释放
WSACleanup();//清理网络库
return 0;
}
printf("服务器端监听成功!\n");
复制代码
7、事件选择模型
7.1定义fd_sockevent_set结构体
WSAEVENT eventServer = WSACreateEvent();
if (eventServer == WSA_INVALID_EVENT)
{
int createerr = WSAGetLastError();
closesocket(socketServer);
WSACleanup();
return 0;
}
复制代码
7.2为服务器创建事件句柄
if (WSAEventSelect(socketServer, eventServer, FD_ACCEPT) == SOCKET_ERROR)
{
int selecterr = WSAGetLastError();
WSACloseEvent(eventServer);
closesocket(socketServer);
WSACleanup();
return 0;
}
复制代码
7.3为服务器SOCEKT和事件句柄绑定事件码:FD_ACCEPT,并放入
sockevent_set
sockevent_set.evnetall[sockevent_set.count] = eventServer;
sockevent_set.sockall[sockevent_set.count] = socketServer;
sockevent_set.count++;
复制代码
7.4循环查询事件状态是否有信号,没有信号则重新查询
7.4.1使用WSAWaitForMultipleEvents查询有信号的事件对应socket句柄下标
DWORD soindex = retSignal - WSA_WAIT_EVENT_0;
复制代码
7.4.2使用WSAEnumNetworkEvents获取事件操作码
7.4.3如果是FD_ACCEPT操作码
if (NetworkEvents.lNetworkEvents & FD_ACCEPT)
{
//判断FD_ACCEPT错误码对应位是否有值
if (NetworkEvents.iErrorCode[FD_ACCEPT_BIT] == 0)
{
//正常处理,创建客户端
SOCKET socketClient = accept(sockevent_set.sockall[soindex], NULL, NULL);
//创建失败则跳过
if (socketClient == INVALID_SOCKET)
{
continue;
}
//创建成功则为该SOCKET创建事件对象
WSAEVENT wsaClientEvent = WSACreateEvent();
//失败则关闭SOCKET句柄
if (wsaClientEvent == WSA_INVALID_EVENT)
{
closesocket(socketClient);
continue;
}
//绑定,投递客户端事件对象
//客户端事件码通常有三种
if (WSAEventSelect(socketClient, wsaClientEvent, FD_READ | FD_WRITE | FD_CLOSE) == SOCKET_ERROR)
{
//出错关闭句柄,关闭事件对象
closesocket(socketClient);
WSACloseEvent(wsaClientEvent);
//获取错误码略
continue;
}
//绑定投递成功后将客户端事件和SOCKET放到sockevent_set里面
sockevent_set.evnetall[sockevent_set.count] = wsaClientEvent;
sockevent_set.sockall[sockevent_set.count] = socketClient;
sockevent_set.count++;
}
else
{
//出现异常不影响其他处理
continue;
}
}
复制代码
7.4.4如果是FD_WRITE操作码
if (NetworkEvents.lNetworkEvents & FD_WRITE)
{
//判断错误码对应位是否有值,没有说明SOCKET没有错误
if (NetworkEvents.iErrorCode[FD_WRITE_BIT] == 0)
{
if (send(sockevent_set.sockall[soindex], "连接成功~", sizeof("连接成功~"), 0) == SOCKET_ERROR)
{
int FD_WRITEsenderr = WSAGetLastError();
printf("得到FD_WRITE操作send函数执行的错误码为:%d\n", FD_WRITEsenderr);
continue;
}
}
else
{
printf("得到FD_WRITE操作的错误码为:%d\n", NetworkEvents.iErrorCode[FD_WRITE_BIT]);
continue;
}
}
##### 7.4.5如果是FD_READ操作码
if (NetworkEvents.lNetworkEvents & FD_READ)
{
//判断错误码对应位是否有值,没有说明SOCKET没有错误
if (NetworkEvents.iErrorCode[FD_READ_BIT] == 0)
{
char strRecv[1500] = { 0 };
if (recv(sockevent_set.sockall[soindex], strRecv, sizeof(strRecv), 0) == SOCKET_ERROR)
{
int FD_READrecverr = WSAGetLastError();
printf("得到FD_READ操作recv函数执行的错误码为:%d\n", FD_READrecverr);
continue;
}
//打印接收的信息
printf("接收的消息为:%s\n", strRecv);
}
else
{
printf("得到FD_READ操作的错误码为:%d\n", NetworkEvents.iErrorCode[FD_READ_BIT]);
continue;
}
}
复制代码
7.4.5如果是FD_CLOSE操作码
if (NetworkEvents.lNetworkEvents & FD_CLOSE)
{
printf("FD_CLOSE操作\n");
printf("得到FD_CLOSE操作的错误码为:%d\n", NetworkEvents.iErrorCode[FD_CLOSE_BIT]);
//清理下线的客户端套接字
closesocket(sockevent_set.sockall[soindex]);
sockevent_set.sockall[soindex] = sockevent_set.sockall[sockevent_set.count - 1];
//清理下线的客户端事件
WSACloseEvent(sockevent_set.evnetall[soindex]);
sockevent_set.evnetall[soindex] = sockevent_set.evnetall[sockevent_set.count - 1];
sockevent_set.count--;
}
//释放fd_sockevent_set中的事件和SOCKET句柄
for (int i = 0; i < sockevent_set.count; i++)
{
WSACloseEvent(sockevent_set.evnetall[i]);
closesocket(sockevent_set.sockall[i]);
}
WSACleanup();
system("pause");
复制代码
客户端
可以查看以前文章Select(TCP)模型
运行结果
事件选择模型相对于SELECT模型有什么不同?
Select(选择)模型是Winsock中最常见的I/O模型。之所以称其为“Select模型”,是由于它的“中心思想”便是利用select函数,实现对I/O的管理。最初设计该模型时,主要面向的是某些使用UNIX操作系统的计算机,它们采用的是Berkeley套接字方案。Select模型已集成到Winsock 1.1中,它使那些想避免在套接字调用过程中被无辜“锁定”的应用程序,采取一种有序的方式,同时进行对多个套接字的管理。由于Winsock 1.1向后兼容于Berkeley套接字实施方案,所以假如有一个Berkeley套接字应用使用了select函数,那么从理论角度讲,毋需对其进行任何修改,便可正常运行。
事件选择模型是Winsock提供的另一个有用的异步I/O模型。和WSAAsyncSelect模型类似的是,它也允许应用程序在一个或多个套接字上,接收以事件为基础的网络事件通知。由WSAAsyncSelect模型采用的网络事件来说,它们均可原封不动地移植到新模型。在用新模型开发的应用程序中,也能接收和处理所有那些事件。该模型最主要的差别在于网络事件会投递至一个事件对象句柄,而非投递至一个窗口例程。