目录
1.概念分析
2.事件选择代码逻辑
1.WSACreateEvent函数
2.WSACloseEvent函数
3.WSAEventSelect函数
4.WSAWaitForMultipleEvents()函数
5.WSAEnumNetworkEvents函数
事件分类
3.##模型代码样例
1.概念分析
本质上是操作系统处理用户行为,详细如下
事件选择模型是select模型的升级版,区别select模型的代码执行时,会在socket数组中阻塞其它的操作,而事件选择模型不会出现这样的情况,在系统处理消息队列时,用户是可以执行其它操作的,这里就是异步。事件选择模型中的事件在事件数组中是无序的。
2.事件选择代码逻辑
基本流程
事件选择模型步骤前面的代码与select模型代码是一致的
#define _CRT_SECURE_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include<stdio.h>
#include<WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
int main()
{
//第一步 打开网络库并校验版本
WORD wdVersion = MAKEWORD(2, 2);
WSADATA wdSocketMsg;
int nRes = WSAStartup(wdVersion, &wdSocketMsg);
if (nRes != 0)
{
printf("打开网络库失败\n");
return 0;
}
if (HIBYTE(wdSocketMsg.wVersion) != 2 || LOBYTE(wdSocketMsg.wVersion) != 2)
{
printf("网络库版本出错\n");
WSACleanup();
return 0;
}
//第二步 创建socket
SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (socketServer == INVALID_SOCKET)
{
printf("创建的socket无效\n");
WSACleanup();
return 0;
}
//第四步 绑定ip地址和端口号
struct sockaddr_in si;
si.sin_family = AF_INET;
si.sin_port = htons(12332);
si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
if (SOCKET_ERROR == bind(socketServer, (const struct sockaddr*)&si, sizeof(si)))
{
printf("绑定失败\n");
closesocket(socketServer);
WSACleanup();
return 0;
}
//第五步 开始监听
if (SOCKET_ERROR == listen(socketServer, SOMAXCONN))
{
printf("监听失败\n");
closesocket(socketServer);
WSACleanup();
return 0;
}
}
核心代码步骤有4步:1.创建事件对象,使用函数 WSACreateEvent
2.为每个事件对象绑定socket
3.查看事件是否有信号,使用函数 WSAWaitForMultipleEvent
4.有信号时对信号进行分类,使用函数 WSAEnumNetworkEvents
1.WSACreateEvent函数
该函数创建一个事件对象,函数原型
WSAEVENT WSAAPI WSACreateEvent();
返回值:如果创建成功会返回事件对象的句柄,如果创建失败返回 WSA_INVALID_EVENT,本质上是一个空指针
##代码样例
//创建事件
WSAEVENT eventServer = WSACreateEvent();//创建成会返回一个事件
if (WSA_INVALID_EVENT == eventServer)
{
//出错了
int a = WSAGetLastError();//获取错误码
closesocket(socketServer);
WSACleanup();
return 0;
}
2.WSACloseEvent函数
该函数关闭创建好的事件,即关闭打开的事件对象句柄,函数原型
BOOL WSAAPI WSACloseEvent(
[in] WSAEVENT hEvent
);
参数 hEvent:事件对象
返回值:成功返回TRUE,失败返回False
3.WSAEventSelect函数
该函数指定要与指定的FD_XXX网络事件集关联的事件对象,即绑定并投递事件。函数原型
int WSAAPI WSAEventSelect(
[in] SOCKET s,
[in] WSAEVENT hEventObject,
[in] long lNetworkEvents
);
参数1 s:要被绑定的socket
参数2 hEventObject:要关联的事件对象
参数3 lNetworkEvents:具体事件
参数3具有的常用具体事件如下:
事件 | 意义 | 用法 |
---|---|---|
FD_ACCEPT | 处理客户端连接问题 | 与服务器socket绑定 |
FD_READ | 处理客户端发送的消息 | 与客户端socket绑定 可多个属性并列 使用 | |
FD_CLOSE | 处理客户端下线问题 | 与客户端socket绑定 处理的包含强制下线和正常下线 |
FD_WRITE | 处理服务器给客户端发消息 | 与客户端socket绑定 会在accept后立即主动产生该信号 |
返回值:成功返回0,失败返回SOCKET_ERROR
4.WSAWaitForMultipleEvents()函数
该函数返回一个或多个指定事件对象处于信号状态、超时间隔过期或 I/O 完成例程执行的时间。函数原型
DWORD WSAAPI WSAWaitForMultipleEvents(
[in] DWORD cEvents,
[in] const WSAEVENT *lphEvents,
[in] BOOL fWaitAll,
[in] DWORD dwTimeout,
[in] BOOL fAlertable
);
参数1 cEvents:要处理事件的数量
参数2 lphEvents:保存事件的数组
参数3 fWaitAll:事件等待方式,为TRUE时,数组中所有事件产生信号时才返回,为FALSE时,任何一个事件产生信号,立即返回
参数4 dwTimeout:设定等待时间,单位是毫秒;填数字a时,等待a毫秒后,超时返回 WSA_WAIT_TIMEOUT;填0时,检查事件对象的状态并立即返回,不管有没有信号;填 WSA_INFINITE时,等待直到事件发生
参数5 fAlertable:在这个模型中填FALSE
返回值:
如果成功返回以下值中的一个:
返回值 | 含义 |
数组下标的运算值 | 参数3为false时,返回值减去WSA_WAIT_EVENT_0==数组中事件的下标 |
WSA_WAIT_IO_COMPLETION | 参数5为true才会返回该值 |
WSA_WAIT_TIMEOUT | 超时会返回该值 |
如果失败返回 WSA_WAIT_FAILED,可使用 WSAGetLastError 获取错误码
##代码实例
//创建结构体对象
struct fd_es_set esAll = { 0, {0}, {NULL} };
//创建事件
WSAEVENT eventServer = WSACreateEvent();//创建成会返回一个事件
if (WSA_INVALID_EVENT == eventServer)
{
//出错了
int a = WSAGetLastError();//获取错误码
closesocket(socketServer);
WSACleanup();
return 0;
}
//把事件和socket装进结构体中
esAll.eventAll[esAll.count] = eventServer;
esAll.socketAll[esAll.count] = socketServer;
esAll.count++;
while (1)
{
//等待事件
DWORD wRes = WSAWaitForMultipleEvents(esAll.count, esAll.eventAll, FALSE, WSA_INFINITE, FALSE);
if (wRes == WSA_WAIT_FAILED)
{
//出错了
int a = WSAGetLastError();//获取错误码
printf("错误码:%d\n", a);
break;
}
//超时判断,WSAWaitForMultipleEvents函数的参数4需要是具体毫秒
if (wRes == WSA_WAIT_TIMEOUT)
{
continue;
}
DWORD nIndex = wRes - WSA_WAIT_EVENT_0;//拿到事件在数组中的下标
}
//绑定事件
if (SOCKET_ERROR == WSAEventSelect(socketServer, eventServer, FD_ACCEPT))//成功返回0
{
//绑定失败
int a = WSAGetLastError();//获取错误码
WSACloseEvent(eventServer);
closesocket(socketServer);
WSACleanup();
return 0;
}
//释放事件 句柄
WSACloseEvent(eventServer);
5.WSAEnumNetworkEvents函数
该函数可发现指示套接字的网络事件发生、清除内部网络事件记录以及重置事件对象,即获取事件类型,并将事件上的信号重置,函数原型
int WSAAPI WSAEnumNetworkEvents(
[in] SOCKET s,
[in] WSAEVENT hEventObject,
[out] LPWSANETWORKEVENTS lpNetworkEvents
);
参数1 s:需要处理的对应socket
参数2 hEventObject:需要处理的对应事件
参数3 lpNetworkEvents:这是一个结构体类型,当事件处理发生错误时,在这个变量中可以找到对应socket发生错误的错误码,结构体原型如下
typedef struct _WSANETWORKEVENTS {
long lNetworkEvents;
int iErrorCode[FD_MAX_EVENTS];
} WSANETWORKEVENTS, *LPWSANETWORKEVENTS;
内置对应事件的错误数组iErrorCode,
返回值:处理成功返回0,失败返回SOCKET_ERROR
##代码实例
DWORD nIndex = wRes - WSA_WAIT_EVENT_0;//拿到事件在数组中的下标
//得到下标对应的具体操作
WSANETWORKEVENTS NetWorkEvents;
if (SOCKET_ERROR == WSAEnumNetworkEvents(esAll.socketAll[nIndex], esAll.eventAll[nIndex], &NetWorkEvents))
{
//出错了
int a = WSAGetLastError();
printf("事件操作出错,错误码:%d\n", a);
break;
}
事件分类
在事件分类这一部份代码中,每一个信号都可以对应一种事件,在服务器中FD_ACCEPT信号会对应accept事件,FD_WRITE信号会对应send事件,FD_READ会对应recv事件,在这一部分代码中使用的是if语句,尽量不使用switch语句和if else语句
关键判断语句
if (NetWorkEvents.lNetworkEvents & FD_ACCEPT)//使用按位与判断是否是对应事件
{
if (0 == NetWorkEvents.iErrorCode[FD_ACCEPT_BIT])//判断如果错误数组中对应下标的位置为0,则表示没有错误,否则该下标位置存放对应错误码
{
}
}
3.##模型代码样例
#define _CRT_SECURE_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include<stdio.h>
#include<WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
//定义事件和socket数组的结构体
struct fd_es_set
{
unsigned short count;
SOCKET socketAll[WSA_MAXIMUM_WAIT_EVENTS];//WSA_MAXIMUM_WAIT_EVENTS 这是一个宏 大小是 64
WSAEVENT eventAll[WSA_MAXIMUM_WAIT_EVENTS];
};
int main()
{
//第一步 打开网络库并校验版本
WORD wdVersion = MAKEWORD(2, 2);
WSADATA wdSocketMsg;
int nRes = WSAStartup(wdVersion, &wdSocketMsg);
if (nRes != 0)
{
printf("打开网络库失败\n");
return 0;
}
if (HIBYTE(wdSocketMsg.wVersion) != 2 || LOBYTE(wdSocketMsg.wVersion) != 2)
{
printf("网络库版本出错\n");
WSACleanup();
return 0;
}
//第二步 创建socket
SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (socketServer == INVALID_SOCKET)
{
printf("创建的socket无效\n");
WSACleanup();
return 0;
}
//第三步 绑定ip地址和端口号
struct sockaddr_in si;
si.sin_family = AF_INET;
si.sin_port = htons(12332);
si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
if (SOCKET_ERROR == bind(socketServer, (const struct sockaddr*)&si, sizeof(si)))
{
printf("绑定失败\n");
closesocket(socketServer);
WSACleanup();
return 0;
}
//第四步 开始监听
if (SOCKET_ERROR == listen(socketServer, SOMAXCONN))
{
printf("监听失败\n");
closesocket(socketServer);
WSACleanup();
return 0;
}
//创建结构体对象
struct fd_es_set esAll = { 0, {0}, {NULL} };
//创建事件
WSAEVENT eventServer = WSACreateEvent();//创建成会返回一个事件
if (WSA_INVALID_EVENT == eventServer)
{
//出错了
int a = WSAGetLastError();//获取错误码
closesocket(socketServer);
WSACleanup();
return 0;
}
//绑定事件
if (SOCKET_ERROR == WSAEventSelect(socketServer, eventServer, FD_ACCEPT))//成功返回0
{
//绑定失败
int a = WSAGetLastError();//获取错误码
WSACloseEvent(eventServer);
closesocket(socketServer);
WSACleanup();
return 0;
}
//把事件和socket装进结构体中
esAll.eventAll[esAll.count] = eventServer;
esAll.socketAll[esAll.count] = socketServer;
esAll.count++;
while (1)
{
//等待事件
DWORD wRes = WSAWaitForMultipleEvents(esAll.count, esAll.eventAll, FALSE, WSA_INFINITE, FALSE);
if (wRes == WSA_WAIT_FAILED)
{
//出错了
int a = WSAGetLastError();//获取错误码
printf("等待事件,错误码:%d\n", a);
break;
}
//超时判断,WSAWaitForMultipleEvents函数的参数4需要是具体毫秒
if (wRes == WSA_WAIT_TIMEOUT)
{
continue;
}
DWORD nIndex = wRes - WSA_WAIT_EVENT_0; //拿到事件在数组中的下标
//得到下标对应的具体操作
WSANETWORKEVENTS NetWorkEvents;
//如果发生错误会把对应错误码放进NetWorkEvents结构体数组中
if (SOCKET_ERROR == WSAEnumNetworkEvents(esAll.socketAll[nIndex], esAll.eventAll[nIndex], &NetWorkEvents))
{
//出错了
int a = WSAGetLastError();
printf("事件操作出错,错误码:%d\n", a);
break;
}
//事件分类
if (NetWorkEvents.lNetworkEvents & FD_ACCEPT)//使用按位与判断是否是对应事件
{
if (0 == NetWorkEvents.iErrorCode[FD_ACCEPT_BIT])//判断如果错误数组中对应下标的位置为0,则表示没有错误
{
//接受连接
SOCKET socketClient = accept(socketServer, NULL, NULL);
if (INVALID_SOCKET == socketClient)
{
//创建客户端socket失败
continue;
}
//创建事件
WSAEVENT wsaClientEvent = WSACreateEvent();
if (wsaClientEvent == WSA_INVALID_EVENT)
{
closesocket(socketClient);
continue;
}
//投放事件给系统
if (SOCKET_ERROR == WSAEventSelect(socketClient, wsaClientEvent, FD_CLOSE | FD_READ | FD_WRITE))
{
closesocket(socketClient);
WSACloseEvent(wsaClientEvent);
continue;
}
//放进结构体
esAll.eventAll[esAll.count] = wsaClientEvent;
esAll.socketAll[esAll.count] = socketClient;
esAll.count++;
printf("accept succee\n");
}
else
{
//否则继续
continue;
}
}
if (NetWorkEvents.lNetworkEvents & FD_WRITE)//FD_WRITE信号对应的事件是send
{
if (0 == NetWorkEvents.iErrorCode[FD_WRITE_BIT])//在创建的时候会被触发一次 可以用来对数据进行初始化
{
//
if (SOCKET_ERROR == send(esAll.socketAll[nIndex], "connect succee", (int)strlen("connect succee"), 0))
{
int a = WSAGetLastError();
printf("发送出错,错误码:%d\n", a);
continue;
}
printf("write succee\n");
}
else
{
printf("事件触发错误,错误码:%d\n", NetWorkEvents.iErrorCode[FD_WRITE_BIT]);
continue;
}
}
if (NetWorkEvents.lNetworkEvents & FD_READ)
{
if (0 == NetWorkEvents.iErrorCode[FD_READ_BIT])//FD_READ信号对应的事件是recv
{
char strRecv[1500] = { 0 };
if (SOCKET_ERROR == recv(esAll.socketAll[nIndex], strRecv, 1499, 0))
{
int a = WSAGetLastError();
printf("发送出错,错误码:%d\n", a);
continue;
}
printf("read data:%s\n", strRecv);
}
else
{
continue;
}
}
if (NetWorkEvents.lNetworkEvents & FD_CLOSE)
{
if (0 == NetWorkEvents.iErrorCode[FD_CLOSE_BIT])//处理关闭的客户端以及对应事件
{
printf("client close\n");
//清理下线的客户端 套接字 事件
//套接字
closesocket(esAll.socketAll[nIndex]);
esAll.socketAll[nIndex] = esAll.socketAll[esAll.count - 1];
//事件
WSACloseEvent(esAll.eventAll[nIndex]);
esAll.eventAll[nIndex] = esAll.eventAll[esAll.count - 1];
//数量减一
esAll.count--;
}
else
{
printf("关闭错误码:%d\n", NetWorkEvents.iErrorCode[FD_CLOSE_BIT]);
//清理下线的客户端 套接字 事件
//套接字
closesocket(esAll.socketAll[nIndex]);
esAll.socketAll[nIndex] = esAll.socketAll[esAll.count - 1];
//事件
WSACloseEvent(esAll.eventAll[nIndex]);
esAll.eventAll[nIndex] = esAll.eventAll[esAll.count - 1];
//数量减一
esAll.count--;
}
}
}
//释放事件 句柄
WSACloseEvent(eventServer);
closesocket(socketServer);
WSACleanup();
return 0;
}