网络编程 事件选择模型

您所在的位置:网站首页 close和closesocket 网络编程 事件选择模型

网络编程 事件选择模型

2022-12-27 19:53| 来源: 网络整理| 查看: 265

目录

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 #include #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 #include #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; }



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3