和WSAAsyncSelect类似,它也允许应用程序在一个或多个套接字上,接收以事件为基础的网络事件通知。
该模型最主要的区别是在于网络事件是由对象句柄完成的,而不是通过窗口例程完成。事件通知
事件通知模型要求应用程序针对打算使用的每一个套接字,首先创建一个事件对象。创建方法是调用WSACreateEvent函数:
WSAEVENT WSACreateEvent(void);
WSACreateEvent的返回值很简单,就是一个人工重设的事件对象句柄,一旦得到了事件对象句柄之后,必须将它与某个套接字关联起来,同时注册感兴趣的网络事件类型。要做到这一点,方法是调用WSAEventSelect函数:
int WSAEventSelect( SOCKET s,//程序感兴趣的套接字 WSAEVENT hEventObject,//指定要与套接字关联在一起的事件对象 long lNetworkEvents//位掩码,用于指定应用程序感兴趣的各种网络事件类型组合
);
为WSAEventSelect创建的事件有两种工作状态和两种工作模式,其中,两种工作状态是已传信(signaled)和为传信(non-signaled)。工作模式则包括人工重设和自动重设。WSAEventSelect最初是在一种为传信的工作状态,并用一种人工重设模式,来创建事件句柄。若网络事件触发了与一个套接字关联在一起的事件对象,工作状态便会从为传信变为已传信。由于事件对象是在一种人工重设模式下创建的,所有完成了一个I/O请求处理之后,应用程序需要负责将工作模式从已传信更改为未传信,要做到这一点,可调用WSAResetEvent函数:
BOOL WSAResetEvent(WSAEVENT hEvent);
完成了对某个事件对象的处理之后,便应调用WSACloseEvent函数释放由事件句柄使用的系统资源。
BOOL WSACloseEvent(WSAEVENT hEvent);
套接字同一个事件对象句柄关联在一起后,应用程序便可开始I/O处理,这就需要应用程序等待网络事件触发事件对象句柄的工作状态,WSAWaitForMultipleEvents函数的设计宗旨就是用来等待一个或多个事件对象句柄,并在事先指定的一个或所有句柄进入已传信状态后,或在超过了一个规定的时间周期后,立即返回
DWORD WSAWaitForMultipleEvents(  DWORD cEvents,  const WSAEVENT FAR* lphEvents,  BOOL fWaitAll,  DWORD dwTimeout,  BOOL fAlertable
);
cEvents和lphEvents定义了由WSAEVENT对象构成的一个数组,cEvents指定的是这个数组中事件对象的数量,而lphEvents是一个指针,用于直接引用该数组。要注意的是WSAWaitForMultipleEvents只能支持由WSA_MAXIMUM_WAIT_EVENTS对象规定的一个最大值,在此这个值为64。因此对于发出 WSAWaitForMultipleEvents调用的每一个线程,该I/O模型一次最多接收64个套接字。假如想让这个套接字一次管理多于64个套接字,必须创建额外的工作线程,以便等待更多的事件对象。fWaitAll指定 WSAWaitForMultipleEvents如何等待在事件数组中的对象。若将该参数设置为TRUE,那么只有等lphEvents数组内包含的所有事件对象都已进入已传信状态,函数才会返回,若设为FALSE,则任何一个事件对象进入已传信状态时,函数就返回。通常应用程序会将该参数设为FALSE,一次只为一个套接字事件提供服务。dwTimeout规定了 WSAWaitForMultipleEvents等待一个网络事件发生时,最多可等待多长时间,以毫秒为单位,超过规定时间,函数就返回。如果超时值为0,函数会检测指定的事件对象状态,并立即返回。这样,应用程序可以实现对事件对象的轮询。如果没有可处理事件, WSAWaitForMultipleEvents便会返回WSA_WAIT_TIMEOUT,如果dwTimeout被设为WSA_INFINITE,那么只有在网络事件传信了一个事件对象后,函数才会返回。fAlertable可被忽略,设为FALSE。
应该注意到一次只服务一个已传信事件(fWaitAll设为FALSE),就可能让套接字一直“挨饿”,且可能持续到事件数组的末尾。若WSAWaitForMultipleEvents收到一个事件对象的网络通知,便会返回一个值,指出造成函数返回的事件对象。这样,应用程序便可引用事件数组中已传信的事件,并检索与那个事件对应的套接字,并判断到底是那个套接字上,发生了什么样的网络事件。对事件数组中的事件进行引用时,应该用WSAWaitForMultipleEvents的返回值,减去预定义的WSA_WAIT_EVENT_0,从而得到具体的引用值。
Index = WSAWaitForMultipleEvents(...);
MyEvent = EventArray[Index - WSA_WAIT_0];
指定了造成网络事件的套接字后, 接下来可调用WSAEnumNetworkEvents函数,调查发生了那些网络事件,该函数定义如下:
int WSAEnumNetworkEvents( SOCKET s,//造成网络事件的套接字 WSAEVENT hEventObject,//可选参数,指定了一个事件句柄,对应于打算重设的那个事件对象 LPWSANETWORKEVENTS lpNetworkEvents//指向WSANETWORKEVENTS的指针,用于检测套接字上发生的网络事件类型以及可能出现的任何错误代码
);
WSANETWORKEVENTS结构如下:
typedef struct _WSANETWORKEVNETS
{ long lNetworkEvents; inbt iErrorCode[FD_MAX_EVENTS];
}WSANETWORKEVENTS, FAR* LPWSANETWORKEVENTS;
lNetworkEvents指定一个值,对应于该套接字上发生的所有网络事件类型
iErrorCode指定了一个错误代码数组,这个数组同lNetworkEvents中的事件关联在一起,针对每个网络事件类型,都存在着一个特殊的事件索引,它与事件类型的名称类似,只是事件类型名称后面添加一个"_BIT"作为后缀字符串。例如,对FD_READ事件类型来说,iErrorCode数组的索引标识便是FD_READ_BIT。
//处理FD_READ通知
if(NetworkEvents.lNetworkEvents & FD_READ)
{ if(NetworkEvents.iErrorCode[FD_READ_BIT]!=0) { printf("FD_READ failed with error %d \n", NetworkEvents.iErrorCode[FD_READ_BIT]); }
}
演示用WSAEventSelect模型的创建步骤:
SOCKET SocketArray[WSA_MAXIMUM_WAIT_EVENTS];
WSAEVENT EventArray[WSA_MAXIMUM_WAIT_EVENTS];
WSAEVENT NetEvent;
SOCKADDR_IN addr;
SOCKET Accept,Listen;
DWORD EventTotal = 0;
DWORD Index;
DOWRD i;
//创建一个TCP套接字在5050端口上的监听
Listen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
addr.sin_family = AF_INET;
addr.sin_port = htons(5050);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(Listen, (SOCKADDR*)&addr, sizeof(SOCKADDR_IN)); NetEvent = WSACreateEvent(); WSAEventSelect(Listen, NetEvent, FD_ACCEPT|FD_CLOSE); listen(Listen, 5); SocketArray[EventTotal] = Listen;
EventArray[EventTotal] = NetEvent;
EventTotal++; while(TRUE)
{ //等候所有套接字上的网络事件 Index = WSAWaitForMultipleEvents(EventTotal, EventArray, FALSE, WSA_INFINITE, FALSE); Index = Index - WSA_WAIT_EVENT_0; //遍历所有事件,查看被传信的事件是否多于一个 for(i = Index; i<EventTotal; i++) { Index = WSAWaitForMultipleEvents(1, &EventArray[i], TRUE, 1000, FALSE); if((Index==WSA_WAIT_FAILED)||(Index==WSA_WAIT_TIMEOUT)) { continue; } else { Index = i; WSAEnumNetworkEvents(SocketArray[Index], EventArray[Index], &NetworkEvents); //检测FD_ACCEPT消息 if(NetworkEvents.lNetworkEvents&FD_ACCEPT) { if(NetworkEvents.iErrorCode[FD_ACCEPT_BIT]!=0) { printf("FD_ACCEPT failed with error %d\n", NetworkEvents.iErrorCode[FD_ACCEPT_BIT]); break; } //接收一个新连接,并将它添加到套接字及事件列表中 Accept = accept(SocketArray[Index], NULL, NULL); //无法处理多于WSA_MAXIMUM_WAIT_EVENTS数量套接字,故关闭接收套接字 if(EventTotal>WSA_MAXIMUM_WAIT_EVENTS) { printf("Too Many Connections"); closesocket(Accept); break; } NetEvent = WSACreateEvent(); WSAEventSelect(Accept, NetEvent, FD_READ|FD_WRITE|FD_CLOSE); EventArray[EventTotal] = NetEvent; SocketArray[EventTotal] = Accept; EventTotal++; printf("Socket %d connected \n", Accept); } //处理FD_READ通知 if(NetworkEvents.lNetworkEvents&FD_READ) { if(NetworkEvents.iErrorCode[FD_READ_BIT]!=0) { printf("FD_READ failed with error %d\n", NetworkEvents.iErrorCode[FD_READ_BIT]); break; } //从套接字读取数据 recv(SocketArray[Index-WSA_WAIT_EVENT_0], buffer, sizeof(buffer), 0); } //处理FD_WRITE通知 if(NetworkEvents.lNetworkEvents&FD_WRITE) { if(NetworkEvents.iErrorCode[FD_WRITE_BIT]!=0) { printf("FD_WRITE failed with error %d\n", NetworkEvents.iErrorCode[FD_WRITE_BIT]); break; } send(SocketArray[Index-WSA_WAIT_EVENT_0], buffer, sizeof(buffer), 0); } //处理FD_CLOSE通知 if(NetworkEvents.lNetworkEvents&FD_CLOSE) { if(NetworkEvents.iErrorCode[FD_CLOSE_BIT]!=0) { printf("FD_CLOSE failed with error %d\n", NetworkEvents.iErrorCode[FD_CLOSE_BIT]); break; } closesocket(SocketArray[Index]); //从Socket和Event数组中删除套接字及与其关联的事件,并递减EventTotal CompressArrays(EventArray, SocketArray, &EventTotal); }       } }
}
优势,概念简单,不需要窗口环境。
缺点,它每次只等待64个事件,处理多个套接字时,有必要使用一个线程池=======================================================================
#include<stdio.h>
#include<winsock2.h>
#pragma comment(lib, "ws2_32.lib"); #define PORT 5050
#define MSGSIZE 1024 int g_iTotalConn = 0;
SOCKET g_CliSocketArr[MAXIMUM_WAIT_OBJECTS];
WSAEVENT g_CliEventArr[MAXIMUM_WAIT_OBJECTS]; DWORD WINAPI WorkerThread(LPVOID lpParam);
void Cleanup(int index); int main()
{ WSADATA wsaData; SOCKET sListen, sClient; SOCKADDR_IN local, client; DWORD dwThreadId; int iAddrSize = sizeof(SOCKADDR_IN); WSAStartup(MAKEWORD(2,2), &wsaData); sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); memset(&local, 0, sizeof(SOCKADDR_IN)); local.sin_family = AF_INET; local.sin_port = htons(PORT); local.sin_addr.s_addr = htonl(INADDR_ANY); bind(sListen, (SOCKADDR*)&local, sizeof(SOCKADDR_IN)); listen(sListen, 3); CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId); while(TRUE) { // Accept a connection sClient = accept(sListen, (SOCKADDR*)&client, &iAddrSize); printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port)); // Associate socket with network event g_CliSocketArr[g_iTotalConn] = sClient; g_CliEventArr[g_iTotalConn] = WSACreateEvent(); WSAEventSelect(g_CliSocketArr[g_iTotalConn], g_CliEventArr[g_iTotalConn], FD_READ|FD_CLOSE); g_iTotalConn++; } return 0;
} DWORD WINAPI WorkerThread(LPVOID lpParam)
{ int ret, index;   WSANETWORKEVENTS NetworkEvents;   char szMessage[MSGSIZE]; while (TRUE)   { ret = WSAWaitForMultipleEvents(g_iTotalConn, g_CliEventArr, FALSE, 1000, FALSE); if (ret == WSA_WAIT_FAILED || ret == WSA_WAIT_TIMEOUT)   {   continue;   } index = ret - WSA_WAIT_EVENT_0; WSAEnumNetworkEvents(g_CliSocketArr[index], g_CliEventArr[index], &NetworkEvents); if (NetworkEvents.lNetworkEvents & FD_READ)   {   // Receive message from client   ret = recv(g_CliSocketArr[index], szMessage, MSGSIZE, 0);   if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))   {   Cleanup(index);   }   else   {   szMessage[ret] = '\0';   send(g_CliSocketArr[index], szMessage, strlen(szMessage), 0);   }   }   if (NetworkEvents.lNetworkEvents & FD_CLOSE)   {   Cleanup(index);   } } return 0;
} void Cleanup(int index)
{ closesocket(g_CliSocketArr[index]); WSACloseEvent(g_CliEventArr[index]); if (index < g_iTotalConn-1)   {   g_CliSocketArr[index] = g_CliSocketArr[g_iTotalConn-1];   g_CliEventArr[index] = g_CliEventArr[g_iTotalConn-1];   }   g_iTotalConn--;
}
事件选择模型也比较简单,实现起来也不是太复杂,它的基本思想是将每个套接字都和一个WSAEVENT对象对应起来,并且在关联的时候指定需要关注的哪些网络事件。一旦在某个套接字上发生了我们关注的事件(FD_READ和FD_CLOSE),与之相关联的WSAEVENT对象被Signaled。程序定义了两个全局数组,一个套接字数组,一个WSAEVENT对象数组,其大小都是MAXIMUM_WAIT_OBJECTS(64),两个数组中的元素一一对应。 同样的,这里的程序没有考虑两个问题,一是不能无条件的调用accept,因为我们支持的并发连接数有限。解决方法是将套接字按 MAXIMUM_WAIT_OBJECTS分组,每MAXIMUM_WAIT_OBJECTS个套接字一组,每一组分配一个工作者线程;或者采用 WSAAccept代替accept,并回调自己定义的Condition Function。第二个问题是没有对连接数为0的情形做特殊处理,程序在连接数为0的时候CPU占用率为100%。
分享至    

转载于:https://www.cnblogs.com/profession/p/4464411.html

套接字I/O模型-WSAEventSelect(转载)相关推荐

  1. 套接字I/O模型-WSAEventSelect

    和WSAAsyncSelect类似,它也允许应用程序在一个或多个套接字上,接收以事件为基础的网络事件通知. 该模型最主要的区别是在于网络事件是由对象句柄完成的,而不是通过窗口例程完成. 事件通知 事件 ...

  2. 套接字I/O模型之WSAEventSelect

    今天我又学习了一种新的套接字I/O模型------WSAEventSelect,他与WSAAsyncSelect一样也是一种异步事件通知模型,不同的是WSAAsyncSelect是与窗口句柄关联在一起 ...

  3. Windows套接字I/O模型(4) -- WSAEventSelect模型

    一.WSAEventSelect模型介绍 WSAEventSelect模型和WSAAsyncSelect模型类似,它也允许应用程序在一个或多个套接字上面,接收以事件为基础的网络事件通知.该模型和WSA ...

  4. Windsock套接字I/O模型学习 --- 第二章

    1. select模型 select模型主要借助于apiselect来实现,所以先介绍一下select函数 int select( int nfds, // 忽略,仅是为了与 Berkeley 套接字 ...

  5. 套接字 I/O 模型 WSAEvent

    和WSAAsyncSelect类似,它也允许应用程序在一个或多个套接字上,接收以事件为基础的网络事件通知. 该模型最主要的区别是在于网络事件是由对象句柄完成的,而不是通过窗口例程完成. 事件通知 事件 ...

  6. 套接字I/O模型-重叠I/O

    重叠模型的基本设计原理是让应用程序使用重叠的数据结构,一次投递一个或多个WinsockI/O请求.针对那些提交的请求,在它们完成之后,应用程序可为它们提供服务.模型的总体设计以Windows重叠I/O ...

  7. Windows套接字I/O模型(2) -- Select模型

    一.Select模型介绍 套接字I/O Select模型的"中心思想"便是利用select函数,实现对I/O的管理.利用select函数判断套接字(一个或多个)上是否存在数据,或者 ...

  8. 利用套接字实现 CS 模型

    例子:大写转小写. 注意:代码都是运行在Linux内核中. 服务器端: #include <stdio.h> #include <unistd.h> #include < ...

  9. 【转载】Linux下套接字学习

    感觉这个系列还不错,学习一下. 先看的是第三篇: http://blog.csdn.net/gatieme/article/details/46334337 < Linux下套接字详解(三)-- ...

最新文章

  1. java 手编线程池_死磕 java线程系列之自己动手写一个线程池
  2. 如何理解:先减1后取反和先取反后加1得到的结果是一样的,故仍可采用取反加1的方法,即对于机器数为负数,则有[X]原=[[X]补]补。
  3. Russ Miles:被忽略的架构师和混沌工程
  4. 东芝移动硬盘拆解图_华为Mate40系列新技术曝光:海思闪存亮相,性能超三星东芝...
  5. Hive学习之路 (十六)Hive分析窗口函数(四) LAG、LEAD、FIRST_VALUE和LAST_VALUE
  6. [mybatis]逆向工程MGB基本编写
  7. 根据Request获取客户端IP
  8. 记录并练习Markdown的基本语法
  9. 粤港澳大湾区落地首家技术VC,创新工场25亿加持,做投资也做AI研发
  10. c/c++ 基本线程管理 join detach
  11. python 实例化_python中如何实例化一个对象-问答-阿里云开发者社区-阿里云
  12. 单例模式(学习小记)
  13. 00套经典机械结构设计非标自动化SW机构3D图纸solidWorks模型库
  14. 关于Flash Media Server
  15. python陆股通_沪股通、深股通、港股通、陆股通都是什么鬼?傻傻分不清
  16. 每日一滴——更新pycharm_nltk包中模块的安装
  17. 二分算法实例应用(二)
  18. Oracle实验 实验1 Oracle数据库安装与配置
  19. vue.js毕业设计,基于vue.js前后端分离在线教育视频点播系统(H5移动项目) 开题报告
  20. windows 下运行 UglifyJS

热门文章

  1. CCNA思科的一些基础知识
  2. Excel文档安全性设置
  3. redis基本操作与Java连接
  4. linux ls和cd命令详解,Linux基础cd、pwd和ls命令
  5. 提高文章阅读量的最新技巧
  6. 华为起诉美国政府,曝其服务器曾被美国政府入侵
  7. 2021-10-18 WPF调用dll出现异常
  8. Python - openpyxl Excel 操作示例与实践
  9. iPad如何越狱?4.2.1完美越狱教程 二 (cydia 安装)
  10. 浙江省计算机专业专科院校排名,浙江省高职院校“五强”