• [C++ 网络协议] 异步通知I/O模型


    1.什么是异步通知I/O模型

    如图是同步I/O函数的调用时间流:

    如图是异步I/O函数的调用时间流:

    可以看出,同异步的差别主要是在时间流上的不一致。select属于同步I/O模型。epoll不确定是不是属于异步I/O模型,这个在概念上有些混乱,期望大佬的指点

    这里说的异步通知I/O模型,实际上是select模型的改进方案。

    2.实现异步通知I/O模型

    2.1 实现异步通知I/O模型步骤

    2.2 WSAEventSelect函数

    1. #include
    2. int WSAEventSelect(
    3. SOCKET s, //监视对象的套接字句柄
    4. WSAEVENT hEventObject, //传递事件对象句柄以验证事件发生与否
    5. long lNetworkEvents //监视的事件类型信息
    6. );
    7. 成功返回0
    8. 失败返回SOCKET_ERROR

    参数hEventObject:

    #define WSAEVENT HANDLE

    WSAEVENT就是HANDLE。

    参数lNetworkEvents:

    含义
    FD_READ是否存在需要接收的数据
    FD_WRITE能否以非阻塞的方式传输数据
    FD_OOB是否收到带外数据
    FD_ACCEPT是否有新的连接请求
    FD_CLOSE是否有断开连接的请求

    可以通过位或运算指定多个信息。

    函数解释:

    传入的套接字参数s,只要s发送lNetworkEvents事件,就会将hEventObject事件对象所指内核对象的状态,改为signaled状态。

    与select函数的比较:

    每个通过WSAEventSelect函数注册的套接字信息就已经注册到操作系统中了,这意味着,无需针对已注册的套接字重复调用WSAEventSelect。

    还有一个实现方式是WSAAsyncSelect函数,使用这个函数时需要指定Windows句柄以获取发生的事件(跟UI有关)

    2.3 创建WSAEVENT对象

    创建manual-reset模式的事件对象。

    方式一:

    使用“windows中的线程同步”中所讲的CreateEvent函数。

    方式二:

    1. #include
    2. WSAEVENT WSACreateEvent(void);
    3. 成功返回事件对象句柄
    4. 失败返回WSA_INVALID_EVENT

    这种方式会直接创建manual-reset模式的事件对象。 其销毁函数:

    1. #include
    2. BOOL WSACloseEvent(WSAEVENT hEvent);
    3. 成功返回TRUE
    4. 失败返回FALSE

    2.4 验证是否发生了事件

    1. #include
    2. DWORD WSAWaitForMultipleEvent(
    3. DWORD cEvents, //需要验证是否转为signaled状态的事件对象个数
    4. const WSAEVENT* lphEvents, //存有事件对象句柄的数组地址值
    5. BOOL fWaitAll, //TRUE,所有事件对象都在signaled状态时返回
    6. //FALSE,只要其中1个变为signaled状态就返回
    7. DWORD dwTimeout, //以1/1000秒为单位指定超时,传递WSA_INFINITE时,直到signaled状态时才返回
    8. //传递0时,表明不阻塞,是否是signaled状态都返回
    9. BOOL fAlertable //传递TRUE可进入alertable_wait(可警告等待)状态
    10. );
    11. 成功:
    12. 返回值减去WSA_WAIT_EVENT_0时,可以得到第一个转变为signaled状态的事件对象句柄对应的索引,可在第二个参数中查找对应句柄。
    13. 超时则返回WSA_WAIT_TIMEOUT。
    14. 失败:
    15. 返回WSA_WAIT_FAILED(注意,原版书籍里这里打印错了)

    最多可监视的事件对象数量为:WSA_MAXIMUM_WAIT_EVENTS常量。

    要想监视更多,要么创建线程,要么扩展保存句柄的数组并多次调用这个函数。

    注意:参数fwaitAll为FALSE时,是说只要其中1个变为signaled状态就返回,函数是返回了,但其有可能有多个事件对象变为了signaled状态

    通过事件对象为manual-reset模式的特点,可以获取转为signaled状态的所有事件对象的句柄。

    1. int start;
    2. WSAEVENT events[num];
    3. start=WSAWaitForMultipleEvents(num,events,FALSE,WSA_INFINITE,FALSE);
    4. int first=start-WSA_WAIT_EVENT_0;
    5. for(int i=first,i//first是变为singaled状态的事件对象的索引的最小值
    6. {
    7. //从第一个的signaled状态的事件对象开始,一个个判断是否siganled
    8. int sigEventIdx=WSAWaitForMultipleEvents(1,&events[i],TRUE,0,FALSE);
    9. ......
    10. }

    2.5 区分事件类型

    1. #include
    2. int WSAEnumNetworkEvents(
    3. SOCKET s, //发生事件的套接字句柄
    4. WSAEVENT hEventObject, //与套接字相连的signaled状态的事件对象句柄
    5. LPWSANETWORKEVENTS lpNetworkEvents //保存发生的事件类型信息和错误信息的
    6. //WSANETWORKEVENTS结构体变量地址值
    7. );
    8. 成功返回0
    9. 失败返回SOCKET_ERROR
    1. struct _WSANETWORKEVENTS
    2. {
    3. long lNetworkEvents; //事件类型
    4. int iErrorCode[FD_MAX_EVENTS]; //错误信息
    5. }WSANETWORKEVENTS,*LPWSANETWORKEVENTS;

    事件类型的验证:

    就是FD_READ、FD_ACCEPT等,和WSAEventSelect第三个参数一样。

    错误信息的验证:

    如果发生FD_XXX相关错误,则在iErrorCode[FD_XXX_BIT]中保存除0以外的其他值。

    如:

    1. WSANETWORKEVENTS netEvents;
    2. ......
    3. WSAEnumNetworkEvents(hSock,hEvent,netEvents);
    4. ......
    5. if(netEvents.lNetworkEvents & FD_ACCEPT)
    6. {
    7. ......
    8. }
    9. ......
    10. if(netEvents.iErrorCode[FD_READ_BIT]!=0)
    11. {
    12. ......
    13. }

    3.用异步通知I/O模型实现回声服务器端

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. std::vector vecSocket;
    8. std::vector vecEvent;
    9. void ErrorHandle(WSANETWORKEVENTS network);
    10. int main()
    11. {
    12. WSADATA wsaData;
    13. if (0 != WSAStartup(MAKEWORD(2, 2), &wsaData))
    14. {
    15. std::cout << "start up fail!" << std::endl;
    16. return 0;
    17. }
    18. SOCKET server = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    19. if (server == INVALID_SOCKET)
    20. {
    21. std::cout << "socket fail!" << std::endl;
    22. return 0;
    23. }
    24. sockaddr_in serverAddr;
    25. memset(&serverAddr, 0, sizeof(serverAddr));
    26. serverAddr.sin_family = AF_INET;
    27. serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    28. serverAddr.sin_port = htons(9130);
    29. if (SOCKET_ERROR == bind(server, (sockaddr*)&serverAddr, sizeof(serverAddr)))
    30. {
    31. std::cout << "bind fail!" << std::endl;
    32. return 0;
    33. }
    34. if (SOCKET_ERROR == listen(server, 2))
    35. {
    36. std::cout << "listen fail!" << std::endl;
    37. return 0;
    38. }
    39. WSAEVENT serverEvent=WSACreateEvent();
    40. if (SOCKET_ERROR == WSAEventSelect(server, serverEvent, FD_ACCEPT))
    41. {
    42. std::cout << "event select fail!" << std::endl;
    43. return 0;
    44. }
    45. vecSocket.push_back(server);
    46. vecEvent.push_back(serverEvent);
    47. while (1)
    48. {
    49. int eventSize = vecEvent.size();
    50. int res=WSAWaitForMultipleEvents(eventSize, vecEvent.data(), FALSE, WSA_INFINITE, FALSE);
    51. int a = (int)WSA_INVALID_EVENT;
    52. if (res == WSA_WAIT_FAILED)
    53. {
    54. std::cout << "wait fail!" << std::endl;
    55. //continue;
    56. }
    57. int first = res - WSA_WAIT_EVENT_0;
    58. for (int i = first; i < eventSize; ++i)
    59. {
    60. int sig = WSAWaitForMultipleEvents(1, &vecEvent[i], TRUE, 0, FALSE);
    61. if (sig == WSA_WAIT_FAILED)
    62. continue;
    63. int index = sig - WSA_WAIT_EVENT_0;
    64. WSANETWORKEVENTS network;
    65. int result = WSAEnumNetworkEvents(vecSocket[i], vecEvent[i], &network);
    66. if (result == SOCKET_ERROR)
    67. {
    68. ErrorHandle(network);
    69. }
    70. else
    71. {
    72. if (network.lNetworkEvents & FD_ACCEPT)
    73. {
    74. SOCKET client;
    75. sockaddr_in clientAddr;
    76. memset(&clientAddr, 0, sizeof(clientAddr));
    77. int clientAddrLen = sizeof(clientAddr);
    78. client=accept(vecSocket[i], (sockaddr*)&clientAddr, &clientAddrLen);
    79. if (INVALID_SOCKET==client)
    80. {
    81. std::cout << "accept fail!" << std::endl;
    82. continue;
    83. }
    84. else
    85. {
    86. WSAEVENT clientEvent = WSACreateEvent();
    87. WSAEventSelect(client, clientEvent, FD_READ|FD_CLOSE);
    88. vecSocket.push_back(client);
    89. vecEvent.push_back(clientEvent);
    90. }
    91. }
    92. else if (network.lNetworkEvents & FD_READ)
    93. {
    94. char buff[1024];
    95. int readLen=recv(vecSocket[i], buff, sizeof(buff), 0);
    96. std::cout << "客户端发来的消息:" << buff << std::endl;
    97. if (readLen != 0)
    98. {
    99. send(vecSocket[i], buff, readLen, 0);
    100. }
    101. }
    102. else if (network.lNetworkEvents & FD_CLOSE)
    103. {
    104. closesocket(vecSocket[i]);
    105. CloseHandle(vecEvent[i]);
    106. auto itSocket = vecSocket.begin() + i;
    107. if(itSocketend())
    108. vecSocket.erase(itSocket);
    109. auto itEvent = vecEvent.begin() + i;
    110. if (itEvent < vecEvent.end())
    111. vecEvent.erase(itEvent);
    112. }
    113. }
    114. }
    115. }
    116. CloseHandle(serverEvent);
    117. closesocket(server);
    118. WSACleanup();
    119. return 0;
    120. }
    121. void ErrorHandle(WSANETWORKEVENTS network)
    122. {
    123. if (network.iErrorCode[FD_ACCEPT_BIT]!=0)
    124. {
    125. std::cout << "accept error!" << std::endl;
    126. }
    127. else if (network.iErrorCode[FD_READ_BIT] != 0)
    128. {
    129. std::cout << "read error!" << std::endl;
    130. }
    131. else if (network.iErrorCode[FD_CLOSE_BIT] != 0)
    132. {
    133. std::cout << "close error!" << std::endl;
    134. }
    135. }

  • 相关阅读:
    C语言:动态内存管理
    Java基础之接口与抽象类区别
    性能测试 —— Jmeter定时器
    从零开始学习Dubbo6——控制端dubbo-admin
    Android 11 系统开发增加低电量弹窗提示 手机 平板 车载 TV 投影 通用
    【C语言】详解计算机二级c语言程序题
    NodeJs实战-待办列表(7)-connect组件简化代码
    基于Python的学生兼职平台的设计和实现
    一起来学Kotlin:概念:8. Kotlin Control Flow: When, For, While, Range 等使用
    PTA 7-77 查找指定字符
  • 原文地址:https://blog.csdn.net/A_ns_wer_/article/details/133298067