和WSAAsyncSelect类似,它也允许应用程序在一个或多个套接字上,接收以事件为基础的网络事件通知。

该模型最主要的区别是在于网络事件是由对象句柄完成的,而不是通过窗口例程完成。

事件通知

事件通知模型要求应用程序针对打算使用的每一个套接字,首先创建一个事件对象。创建方法是调用WSACreateEvent函数:

  1. WSAEVENT WSACreateEvent(void);

WSACreateEvent的返回值很简单,就是一个人工重设的事件对象句柄,一旦得到了事件对象句柄之后,必须将它与某个套接字关联起来,同时注册感兴趣的网络事件类型。要做到这一点,方法是调用WSAEventSelect函数:

  1. int WSAEventSelect(
  2. SOCKET s,//程序感兴趣的套接字
  3. WSAEVENT hEventObject,//指定要与套接字关联在一起的事件对象
  4. long lNetworkEvents//位掩码,用于指定应用程序感兴趣的各种网络事件类型组合
  5. );

为WSAEventSelect创建的事件有两种工作状态和两种工作模式,其中,两种工作状态是已传信(signaled)和为传信(non-signaled)。工作模式则包括人工重设和自动重设。WSAEventSelect最初是在一种为传信的工作状态,并用一种人工重设模式,来创建事件句柄。若网络事件触发了与一个套接字关联在一起的事件对象,工作状态便会从为传信变为已传信。由于事件对象是在一种人工重设模式下创建的,所有完成了一个I/O请求处理之后,应用程序需要负责将工作模式从已传信更改为未传信,要做到这一点,可调用WSAResetEvent函数:

  1. BOOL WSAResetEvent(WSAEVENT hEvent);

完成了对某个事件对象的处理之后,便应调用WSACloseEvent函数释放由事件句柄使用的系统资源。

  1. BOOL WSACloseEvent(WSAEVENT hEvent);

套接字同一个事件对象句柄关联在一起后,应用程序便可开始I/O处理,这就需要应用程序等待网络事件触发事件对象句柄的工作状态,WSAWaitForMultipleEvents函数的设计宗旨就是用来等待一个或多个事件对象句柄,并在事先指定的一个或所有句柄进入已传信状态后,或在超过了一个规定的时间周期后,立即返回

  1. DWORD WSAWaitForMultipleEvents(
  2. DWORD cEvents,
  3. const WSAEVENT FAR* lphEvents,
  4. BOOL fWaitAll,
  5. DWORD dwTimeout,
  6. BOOL fAlertable
  7. );

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,从而得到具体的引用值。

  1. Index = WSAWaitForMultipleEvents(...);
  2. MyEvent = EventArray[Index - WSA_WAIT_0];

指定了造成网络事件的套接字后, 接下来可调用WSAEnumNetworkEvents函数,调查发生了那些网络事件,该函数定义如下:

  1. int WSAEnumNetworkEvents(
  2. SOCKET s,//造成网络事件的套接字
  3. WSAEVENT hEventObject,//可选参数,指定了一个事件句柄,对应于打算重设的那个事件对象
  4. LPWSANETWORKEVENTS lpNetworkEvents//指向WSANETWORKEVENTS的指针,用于检测套接字上发生的网络事件类型以及可能出现的任何错误代码
  5. );

WSANETWORKEVENTS结构如下:

  1. typedef struct _WSANETWORKEVNETS
  2. {
  3. long lNetworkEvents;
  4. inbt iErrorCode[FD_MAX_EVENTS];
  5. }WSANETWORKEVENTS, FAR* LPWSANETWORKEVENTS;

lNetworkEvents指定一个值,对应于该套接字上发生的所有网络事件类型

iErrorCode指定了一个错误代码数组,这个数组同lNetworkEvents中的事件关联在一起,针对每个网络事件类型,都存在着一个特殊的事件索引,它与事件类型的名称类似,只是事件类型名称后面添加一个"_BIT"作为后缀字符串。例如,对FD_READ事件类型来说,iErrorCode数组的索引标识便是FD_READ_BIT。

  1. //处理FD_READ通知
  2. if(NetworkEvents.lNetworkEvents & FD_READ)
  3. {
  4. if(NetworkEvents.iErrorCode[FD_READ_BIT]!=0)
  5. {
  6. printf("FD_READ failed with error %d \n", NetworkEvents.iErrorCode[FD_READ_BIT]);
  7. }
  8. }

演示用WSAEventSelect模型的创建步骤:

  1. SOCKET SocketArray[WSA_MAXIMUM_WAIT_EVENTS];
  2. WSAEVENT EventArray[WSA_MAXIMUM_WAIT_EVENTS];
  3. WSAEVENT NetEvent;
  4. SOCKADDR_IN addr;
  5. SOCKET Accept,Listen;
  6. DWORD EventTotal = 0;
  7. DWORD Index;
  8. DOWRD i;
  9. //创建一个TCP套接字在5050端口上的监听
  10. Listen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  11. addr.sin_family = AF_INET;
  12. addr.sin_port = htons(5050);
  13. addr.sin_addr.s_addr = htonl(INADDR_ANY);
  14. bind(Listen, (SOCKADDR*)&addr, sizeof(SOCKADDR_IN));
  15. NetEvent = WSACreateEvent();
  16. WSAEventSelect(Listen, NetEvent, FD_ACCEPT|FD_CLOSE);
  17. listen(Listen, 5);
  18. SocketArray[EventTotal] = Listen;
  19. EventArray[EventTotal] = NetEvent;
  20. EventTotal++;
  21. while(TRUE)
  22. {
  23. //等候所有套接字上的网络事件
  24. Index = WSAWaitForMultipleEvents(EventTotal, EventArray, FALSE, WSA_INFINITE, FALSE);
  25. Index = Index - WSA_WAIT_EVENT_0;
  26. //遍历所有事件,查看被传信的事件是否多于一个
  27. for(i = Index; i<EventTotal; i++)
  28. {
  29. Index = WSAWaitForMultipleEvents(1, &EventArray[i], TRUE, 1000, FALSE);
  30. if((Index==WSA_WAIT_FAILED)||(Index==WSA_WAIT_TIMEOUT))
  31. {
  32. continue;
  33. }
  34. else
  35. {
  36. Index = i;
  37. WSAEnumNetworkEvents(SocketArray[Index], EventArray[Index], &NetworkEvents);
  38. //检测FD_ACCEPT消息
  39. if(NetworkEvents.lNetworkEvents&FD_ACCEPT)
  40. {
  41. if(NetworkEvents.iErrorCode[FD_ACCEPT_BIT]!=0)
  42. {
  43. printf("FD_ACCEPT failed with error %d\n", NetworkEvents.iErrorCode[FD_ACCEPT_BIT]);
  44. break;
  45. }
  46. //接收一个新连接,并将它添加到套接字及事件列表中
  47. Accept = accept(SocketArray[Index], NULL, NULL);
  48. //无法处理多于WSA_MAXIMUM_WAIT_EVENTS数量套接字,故关闭接收套接字
  49. if(EventTotal>WSA_MAXIMUM_WAIT_EVENTS)
  50. {
  51. printf("Too Many Connections");
  52. closesocket(Accept);
  53. break;
  54. }
  55. NetEvent = WSACreateEvent();
  56. WSAEventSelect(Accept, NetEvent, FD_READ|FD_WRITE|FD_CLOSE);
  57. EventArray[EventTotal] = NetEvent;
  58. SocketArray[EventTotal] = Accept;
  59. EventTotal++;
  60. printf("Socket %d connected \n", Accept);
  61. }
  62. //处理FD_READ通知
  63. if(NetworkEvents.lNetworkEvents&FD_READ)
  64. {
  65. if(NetworkEvents.iErrorCode[FD_READ_BIT]!=0)
  66. {
  67. printf("FD_READ failed with error %d\n", NetworkEvents.iErrorCode[FD_READ_BIT]);
  68. break;
  69. }
  70. //从套接字读取数据
  71. recv(SocketArray[Index-WSA_WAIT_EVENT_0], buffer, sizeof(buffer), 0);
  72. }
  73. //处理FD_WRITE通知
  74. if(NetworkEvents.lNetworkEvents&FD_WRITE)
  75. {
  76. if(NetworkEvents.iErrorCode[FD_WRITE_BIT]!=0)
  77. {
  78. printf("FD_WRITE failed with error %d\n", NetworkEvents.iErrorCode[FD_WRITE_BIT]);
  79. break;
  80. }
  81. send(SocketArray[Index-WSA_WAIT_EVENT_0], buffer, sizeof(buffer), 0);
  82. }
  83. //处理FD_CLOSE通知
  84. if(NetworkEvents.lNetworkEvents&FD_CLOSE)
  85. {
  86. if(NetworkEvents.iErrorCode[FD_CLOSE_BIT]!=0)
  87. {
  88. printf("FD_CLOSE failed with error %d\n", NetworkEvents.iErrorCode[FD_CLOSE_BIT]);
  89. break;
  90. }
  91. closesocket(SocketArray[Index]);
  92. //从Socket和Event数组中删除套接字及与其关联的事件,并递减EventTotal
  93. CompressArrays(EventArray, SocketArray, &EventTotal);
  94. }
  95. }
  96. }
  97. }

优势,概念简单,不需要窗口环境。

缺点,它每次只等待64个事件,处理多个套接字时,有必要使用一个线程池

=======================================================================

  1. #include<stdio.h>
  2. #include<winsock2.h>
  3. #pragma comment(lib, "ws2_32.lib");
  4. #define PORT 5050
  5. #define MSGSIZE 1024
  6. int g_iTotalConn = 0;
  7. SOCKET g_CliSocketArr[MAXIMUM_WAIT_OBJECTS];
  8. WSAEVENT g_CliEventArr[MAXIMUM_WAIT_OBJECTS];
  9. DWORD WINAPI WorkerThread(LPVOID lpParam);
  10. void Cleanup(int index);
  11. int main()
  12. {
  13. WSADATA wsaData;
  14. SOCKET sListen, sClient;
  15. SOCKADDR_IN local, client;
  16. DWORD dwThreadId;
  17. int iAddrSize = sizeof(SOCKADDR_IN);
  18. WSAStartup(MAKEWORD(2,2), &wsaData);
  19. sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  20. memset(&local, 0, sizeof(SOCKADDR_IN));
  21. local.sin_family = AF_INET;
  22. local.sin_port = htons(PORT);
  23. local.sin_addr.s_addr = htonl(INADDR_ANY);
  24. bind(sListen, (SOCKADDR*)&local, sizeof(SOCKADDR_IN));
  25. listen(sListen, 3);
  26. CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);
  27. while(TRUE)
  28. {
  29. // Accept a connection
  30. sClient = accept(sListen, (SOCKADDR*)&client, &iAddrSize);
  31. printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
  32. // Associate socket with network event
  33. g_CliSocketArr[g_iTotalConn] = sClient;
  34. g_CliEventArr[g_iTotalConn] = WSACreateEvent();
  35. WSAEventSelect(g_CliSocketArr[g_iTotalConn], g_CliEventArr[g_iTotalConn], FD_READ|FD_CLOSE);
  36. g_iTotalConn++;
  37. }
  38. return 0;
  39. }
  40. DWORD WINAPI WorkerThread(LPVOID lpParam)
  41. {
  42. int ret, index;
  43. WSANETWORKEVENTS NetworkEvents;
  44. char szMessage[MSGSIZE];
  45. while (TRUE)
  46. {
  47. ret = WSAWaitForMultipleEvents(g_iTotalConn, g_CliEventArr, FALSE, 1000, FALSE);
  48. if (ret == WSA_WAIT_FAILED || ret == WSA_WAIT_TIMEOUT)
  49. {
  50. continue;
  51. }
  52. index = ret - WSA_WAIT_EVENT_0;
  53. WSAEnumNetworkEvents(g_CliSocketArr[index], g_CliEventArr[index], &NetworkEvents);
  54. if (NetworkEvents.lNetworkEvents & FD_READ)
  55. {
  56. // Receive message from client
  57. ret = recv(g_CliSocketArr[index], szMessage, MSGSIZE, 0);
  58. if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))
  59. {
  60. Cleanup(index);
  61. }
  62. else
  63. {
  64. szMessage[ret] = '\0';
  65. send(g_CliSocketArr[index], szMessage, strlen(szMessage), 0);
  66. }
  67. }
  68. if (NetworkEvents.lNetworkEvents & FD_CLOSE)
  69. {
  70. Cleanup(index);
  71. }
  72. }
  73. return 0;
  74. }
  75. void Cleanup(int index)
  76. {
  77. closesocket(g_CliSocketArr[index]);
  78. WSACloseEvent(g_CliEventArr[index]);
  79. if (index < g_iTotalConn-1)
  80. {
  81. g_CliSocketArr[index] = g_CliSocketArr[g_iTotalConn-1];
  82. g_CliEventArr[index] = g_CliEventArr[g_iTotalConn-1];
  83. }
  84. g_iTotalConn--;
  85. }

事件选择模型也比较简单,实现起来也不是太复杂,它的基本思想是将每个套接字都和一个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://blog.51cto.com/zcwtop/801601

套接字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. 套接字 I/O 模型 WSAEvent

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

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

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

  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内核架构>和<精通Linux内核网络>相关章节 文章目录 套接字及分层模型 核心基本术语 套接字 网络实现的分层模型 套接字缓冲区及net_devic ...

最新文章

  1. java实现fread_fread函数读取到的数据和实际数据不一样
  2. pandas.DataFrame.to_dict()的使用详解
  3. Struts的ONGL
  4. pinpoint 安装部署
  5. 6个炫酷又好用的 Python 工具,个个都很奔放呀
  6. 全局变量初始化顺序探究
  7. Request/Response【学习笔记03】
  8. 为什么有些WIFI不能用万能钥匙搜索到?怎么才能破解邻居家的WIFI密码?
  9. android手机测试用例,Android手机测试用例-从事手机测试必备
  10. 七、Linux常用命令——网络通信命令、系统关机命令
  11. WinRAR去除打开后弹出广告的方法
  12. java计算机毕业设计西藏民族大学论文管理系统源码+数据库+系统+lw文档+mybatis+运行部署
  13. “跨综服”——跨境电商综合服务合规化走向台前
  14. 启科量子在2022全球数字经济大会量子信息技术与应用论坛公布量子计算机工程化进展
  15. 科学家被称为计算机之父,被称为“计算机之父”,他超前的思维揭开计算机处理信息的本质!...
  16. linux下使用LVM合并挂载硬盘以及扩容
  17. Ardusub源码解析学习(五)——从manual model开始
  18. 兔子繁殖问题Java实现
  19. [Python爬虫]爬取东方财富网公司公告需要注意的几个问题
  20. 下载(导出)pdf模板文件(比如:审批单)

热门文章

  1. qa158.cn kuais.php,qukuaigou.skhjcf.com
  2. 监听元素宽高变化resize
  3. 《黎明时分的诗》王家新
  4. 各类测试工程师的面试秘籍
  5. 为什么你学了那么多,却没赚到钱?
  6. IBM Bluemix 中文公众版初体验
  7. 【量化笔记】配对交易
  8. nn.Parameter
  9. 全景拍摄中的全景接片是什么?怎么操作?
  10. uml建模工具 支持php,【UML 建模】在线UML建模工具 ProcessOn 使用详解