【網絡編程】事件選擇模型

十、基于I/O模型的網絡開發

10.9 事件選擇模型

10.0.1 基本概念

事件選擇(WSAEventSelect) 模型是另一個有用的異步 I/O 模型。和 WSAAsyncSelect 模 型類似的是,它也允許應用程序在一個或多個套接字上接收以事件為基礎的網絡事件通知,最 主要的差別在于網絡事件會投遞至一個事件對象句柄,而非投遞到一個窗口例程。

10.9.2 WSAEventSelect函數

WSAEventSelect 模型主要由函數WSAEventSelect 來實現。注意,這里用了“主要由”, 說明還有其他配套函數一起輔助來實現這個模型。

后面會講到其他函數。這里先看一下 WSAEventSelect。

WSAEventSelect 函數將一個已經創建好的事件對象(由WSACreateEvent 創建)與某個套 接字關聯在一起,同時注冊自己感興趣的網絡事件類型。WSAEventSelect 的函數聲明如下:

int WSAAPI WSAEventSelect(SOCKET S,WSAEVENT hEventObject, long lNetworkEvents);
  • 其中,s 是套接字描述符;

  • hEventObject標識要與指定的網絡事件集關聯的事件對象的句 柄 ;

  • INetworkEvents指定應用程序感興趣的網絡事件組合的位掩碼。

  • 如果函數成功,那么返回值為零;否則,將返回值SOCKET_ERROR, 并且可以通過調用 WSAGetLastError來獲取特定的錯誤號。

  • 與select 和 WSAAsyncSelect 函數一樣,WSAEventSelect 通常用于確定何時可以進行數據 收發操作(確定調用send或 recv能立即成功的時間點)。如果時間點沒到,那么函數會返回 WSAEWOULDBLOCK, 此時我們要正確處理這個錯誤碼。

10.9.3 實 戰WSAEventSelect模型

事件選擇模型的基本思路是:為感興趣的一組網絡事件創建一個事件對象,再調用 WSAEventSelect 函數將網絡事件和事件對象關聯起來。當網絡事件發生時,Winsock 會使相 應的事件對象收到通知,在事件對象上等待的函數就會返回。之后,再調用 WSAEnumNetworkEvents函數便可獲取發生了什么網絡事件。
事件選擇模型寫的TCP 服務器實現的過程如下:

  • (1)創建事件對象和套接字。創建一個事件對象的方法是調用 WSACreateEvent 函數, 它的定義如下:
WSAEVENT WSAAPI WSACreateEvent();
  • 如果沒有發生錯誤,那么函數將返回事件對象的句柄;

  • 否則,返回值為 WSA_INVALID_EVENT, 可以通過WSAGetLastError 函數獲取更多的錯誤信息。這個事件對 象創建后,其初始狀態為“未受信”,就是沒有收到通知狀態。

  • WSACreateEvent 創建的事件有兩種工作狀態以及兩種工作模式:工作狀態分別是“有信 號 " (signaled) 和“無信號" (nonsignaled), 工作模式包括“人工重設” (manual reset) 和“自動重設” (auto reset) 。WSACreateEvent 創建的事件開始是處于一種無信號的工作狀 態,并用一種人工重設模式來創建事件句柄。

  • (2)將事件對象與套接字關聯在一起,同時注冊自己感興趣的網絡事件類型(FD_READ、 FD_WRITE、FD_ACCEPT、FD_CONNECT、FD_CLOSE 等),這個過程通過函數 WSAEventSelect實現。

  • (3)調用事件等待函數WSAWaitForMultipleEvents在所有事件對象上等待,該函數返回后,我們就可以確認在哪些套接字上發生了網絡事件。 當一個或所有指定的事件對象處于信號狀態、超時或執行了 I/O 完成例程時,函數 WSAWaitForMultipleEvents返回,該函數聲明如下:

#include <winsock2.h>
#pragma comment(lib, "Ws232.lib")
DWORD WSAAPI WSAWaitForMultipleEvents(DWORD CEvents,const WSAEVENT *lphEvents, BOOL fWaitAll,DWORD dwTimeout,BOOL fAlertable);
  • cEvents: 表 示lphEvent 所指數組中的事件對象句柄數,事件對象句柄的最大數量是 WSA_MAXIMUM_WAIT_EVENTS, 必須指定一個或多個事件。
  • lphEvents: 指向事件對象句柄數組的指針,數組可以包含不同類型對象的句柄,如果 后面參數fWaitAll 設置為TRUE, 那么它不能包含同一句柄的多個副本,如果在等待 仍處于掛起狀態時關閉其中一個句柄,那么WSAWaitForMultipleEvents 的行為將不 可知。另外,句柄必須具有同步訪問權限。
  • fWaitAll: 輸入參數,用于指定等待類型的值。如果賦值為TRUE, 那么當lphEvents 數組中所有對象的狀態都處于有信號時,函數將返回。注意,是所有對象都處于信號 狀態才返回。如果賦值為FALSE, 則當向任一事件對象發出信號時,函數返回。在 這一種情況下,返回值減去WSA_WAIT_EVENT_0 表示其狀態導致函數返回的事件 對象的索引。如果在調用期間有多個事件對象發出信號,那么返回值指示信號事件對 象的lphEvents 數組索引的最小值。
  • dwTimeout: 超時時間,單位是毫秒。如果超時時間到,則函數返回,即使不滿足 fWaitAll 參數指定的條件。如果 dw Timeout參數為零,則函數將測試指定事件對象的 狀態并立即返回。如果dwTimeout 是 WSA_INFINITE, 則函數將永遠等待。
  • fAlertable: 指定線程是否處于可警報的等待狀態,以便系統可以執行I/O 完成例程。 如果為TRUE, 則線程將處于可警報的等待狀態,并且當系統執行I/O 完成例程時, 函數可以返回。在這種情況下,將返回 WSA_WAIT_IO_COMPLETION, 并且尚未 發出正在等待的事件的信號。應用程序必須再次調用WSAWaitForMultipleEvents 函 數。如果為FALSE, 則線程不會處于可警報的等待狀態,也不會執行I/O 完成例程。

如果函數成功,那么返回值為以下值之一:

  • WSA_WAIT_EVEN_0 到 (WSA_WAIT_EVENT_0+cEvents-1): 如果參數 fWaitAll 參數為 TRUE, 則返回值指示已向所有指定的事件對象發出信號。如果 fWaitAll 參數為FALSE, 則返回值減去WSA_WAIT_EVENT_0 表示其狀態導致函數 返回的事件對象的索引。如果在調用期間有多個事件對象發出信號,則返回值指示信 號事件對象的lphEvents 數組索引的最小值。
  • WSA_WAIT_IO_COMPLETION:等待被執行的一個或多個I/O 完成例程結束。正在 等待的事件尚未發出信號,應用程序必須再次調用WSAWaitForMultipleEvents 函數。 只有fAlertable 參數為TRUE 時,才能返回此返回值。
  • WSA_WAIT_TIMEOUT: 超時間隔已過,并且未滿足fWaitAll參數指定的條件,未
    執行任何I/O完成例程。

如果函數失敗,則返回值為WSA_WAIT_FAILED。此時可以通過函數WSAGetLastError 獲取更多錯誤碼,常見錯誤碼如下:

  • WSANOTINITIALISED: 在調用本API 之前應成功調用WSAStartup()。

  • WSAENETDOWN: 網絡子系統失效。

  • WSA_NOT_ENOUGH_MEMORY: 無足夠內存完成該操作。

  • WSA_INVALID_HANDLE:lphEvents 數組中的一個或多個值不是合法的事件對象句柄。

  • WSA_INVALID_PARAMETER:cEvents參數未包含合法的句柄數目。

  • (4)檢測所指定套接字上發生網絡事件,然后處理發生的網絡事件,完畢繼續在事件對 象上等待。檢測所指定套接字上發生網絡事件是通過函數WSAEnumNetworkEvents 來實現, 該函數聲明如下:

#include <winsock2.h>
#pragma comment(lib, "Ws232.lib")int WSAAPI WSAEnumNetworkEvents(SOCKET s,WSAEVENT hEventObject,LPWSANETWORKEVENTS lpNetworkEvents);
  • s: 套接字描述符。
  • hEventObject: 標識要重置的關聯事件對象的可選句柄。
  • lpNetworkEvents: 指 向WSANETWORKEVENTS 結構的指針,該結構由發生的網絡 事件和任何相關錯誤代碼的記錄填充。

如果操作成功,函數返回值為零;否則,將返回值SOCKET ERROR, 并且可以通過調用WSAGetLastError來獲取特定的錯誤碼。

以上4步是使用事件選擇模型的基本步驟。下面我們看一個實例。

服務端

#define _WINSOCK_DEPRECATED_NO_WARNINGS#include <winsock2.h>
#include <Windows.h>
#include <iostream>
#pragma comment(lib,"ws2_32.lib")using std::cout;
using std::cin;
using std::endl;
using std::ends;void WSAEventServerSocket()
{SOCKET server = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (server == INVALID_SOCKET) {cout << "創建SOCKET失敗!,錯誤代碼:" << WSAGetLastError() << endl;return;}int error = 0;sockaddr_in addr_in;addr_in.sin_family = AF_INET;addr_in.sin_port = htons(6000);addr_in.sin_addr.s_addr = INADDR_ANY;error = ::bind(server, (sockaddr*)&addr_in, sizeof(sockaddr_in));if (error == SOCKET_ERROR) {cout << "綁定端口失敗!,錯誤代碼:" << WSAGetLastError() << endl;return;}listen(server, 5);if (error == SOCKET_ERROR) {cout << "監聽失敗!,錯誤代碼:" << WSAGetLastError() << endl;return;}cout << "成功監聽端口 :" << ntohs(addr_in.sin_port) << endl;WSAEVENT eventArray[WSA_MAXIMUM_WAIT_EVENTS];        // 事件對象數組SOCKET sockArray[WSA_MAXIMUM_WAIT_EVENTS];            // 事件對象數組對應的SOCKET句柄int nEvent = 0;                    // 事件對象數組的數量 WSAEVENT event0 = ::WSACreateEvent();::WSAEventSelect(server, event0, FD_ACCEPT | FD_CLOSE);eventArray[nEvent] = event0;sockArray[nEvent] = server;nEvent++;while (true) {int nIndex = ::WSAWaitForMultipleEvents(nEvent, eventArray, false, WSA_INFINITE, false);if (nIndex == WSA_WAIT_IO_COMPLETION || nIndex == WSA_WAIT_TIMEOUT) {cout << "等待時發生錯誤!錯誤代碼:" << WSAGetLastError() << endl;break;}nIndex = nIndex - WSA_WAIT_EVENT_0;WSANETWORKEVENTS event;SOCKET sock = sockArray[nIndex];::WSAEnumNetworkEvents(sock, eventArray[nIndex], &event);if (event.lNetworkEvents & FD_ACCEPT) {if (event.iErrorCode[FD_ACCEPT_BIT] == 0) {if (nEvent >= WSA_MAXIMUM_WAIT_EVENTS) {cout << "事件對象太多,拒絕連接" << endl;continue;}sockaddr_in addr;int len = sizeof(sockaddr_in);SOCKET client = ::accept(sock, (sockaddr*)&addr, &len);if (client != INVALID_SOCKET) {cout << "接受了一個客戶端連接 " << inet_ntoa(addr.sin_addr) << ":" << ntohs(addr.sin_port) << endl;WSAEVENT eventNew = ::WSACreateEvent();::WSAEventSelect(client, eventNew, FD_READ | FD_CLOSE | FD_WRITE);eventArray[nEvent] = eventNew;sockArray[nEvent] = client;nEvent++;}}}else if (event.lNetworkEvents & FD_READ) {if (event.iErrorCode[FD_READ_BIT] == 0) {char buf[2500];ZeroMemory(buf, 2500);int nRecv = ::recv(sock, buf, 2500, 0);if (nRecv > 0) {cout << "收到一個消息 :" << buf << endl;char strSend[] = "hi,client,I am server, I recvived your message.";::send(sock, strSend, strlen(strSend), 0);}}}else if (event.lNetworkEvents & FD_CLOSE) {::WSACloseEvent(eventArray[nIndex]);::closesocket(sockArray[nIndex]);cout << "一個客戶端連接已經斷開了連接" << endl;for (int j = nIndex; j < nEvent - 1; j++) {eventArray[j] = eventArray[j + 1];sockArray[j] = sockArray[j + 1];}nEvent--;}else if (event.lNetworkEvents & FD_WRITE) {cout << "一個客戶端連接允許寫入數據" << endl;}} // end while::closesocket(server);
}int main(){WSADATA wsaData;int error;WORD wVersionRequested;wVersionRequested = WINSOCK_VERSION;error = WSAStartup(wVersionRequested, &wsaData);if (error != 0) {WSACleanup();return 0;}WSAEventServerSocket();WSACleanup();return 0;
}

客戶端

#define _WINSOCK_DEPRECATED_NO_WARNINGS#include<stdlib.h>
#include<WINSOCK2.H>
#include <windows.h> 
#include <process.h>  #include<iostream>
#include<string>using namespace std;#define BUF_SIZE 64
#pragma comment(lib,"WS2_32.lib")void recv(PVOID pt)
{SOCKET  sHost = *((SOCKET*)pt);while (true){char buf[BUF_SIZE];//清空接收數據的緩沖區memset(buf, 0, BUF_SIZE);int retVal = recv(sHost, buf, sizeof(buf), 0);if (SOCKET_ERROR == retVal){int  err = WSAGetLastError();//無法立即完成非阻塞Socket上的操作if (err == WSAEWOULDBLOCK){Sleep(1000);//printf("\nwaiting  reply!");continue;}else if (err == WSAETIMEDOUT || err == WSAENETDOWN || err == WSAECONNRESET)//已建立連接{printf("recv failed!");closesocket(sHost);WSACleanup();return;}}Sleep(100);printf("\n%s", buf);//break;}
}int main()
{WSADATA wsd;SOCKET sHost;SOCKADDR_IN servAddr;//服務器地址int retVal;//調用Socket函數的返回值char buf[BUF_SIZE];//初始化Socket環境if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0){printf("WSAStartup failed!\n");return -1;}sHost = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);//設置服務器Socket地址servAddr.sin_family = AF_INET;servAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//在實際應用中,建議將服務器的IP地址和端口號保存在配置文件中servAddr.sin_port = htons(6000);//計算地址的長度int sServerAddlen = sizeof(servAddr);//調用ioctlsocket()將其設置為非阻塞模式int iMode = 1;retVal = ioctlsocket(sHost, FIONBIO, (u_long FAR*) & iMode);if (retVal == SOCKET_ERROR){printf("ioctlsocket failed!");WSACleanup();return -1;}//循環等待while (true){//連接到服務器retVal = connect(sHost, (LPSOCKADDR)&servAddr, sizeof(servAddr));if (SOCKET_ERROR == retVal){int err = WSAGetLastError();//無法立即完成非阻塞Socket上的操作if (err == WSAEWOULDBLOCK || err == WSAEINVAL){Sleep(1);printf("check  connect!\n");continue;}else if (err == WSAEISCONN)//已建立連接{break;}else{printf("connection failed!\n");closesocket(sHost);WSACleanup();return -1;}}}unsigned long threadId = _beginthread(recv, 0, &sHost);//啟動一個線程接收數據的線程   while (true){//向服務器發送字符串,并顯示反饋信息printf("input a string to send:\n");std::string str;//接收輸入的數據std::cin >> str;//將用戶輸入的數據復制到buf中ZeroMemory(buf, BUF_SIZE);strcpy_s(buf, str.c_str());if (strcmp(buf, "quit") == 0){printf("quit!\n");break;}while (true){retVal = send(sHost, buf, strlen(buf), 0);if (SOCKET_ERROR == retVal){int err = WSAGetLastError();if (err == WSAEWOULDBLOCK){//無法立即完成非阻塞Socket上的操作Sleep(5);continue;}else{printf("send failed!\n");closesocket(sHost);WSACleanup();return -1;}}break;}}return 0;
}

參考書籍《Visual C++2017 網絡編程實戰》

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/73197.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/73197.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/73197.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

STM32 F407ZGT6開發板

#ifndef _tftlcd_H #define _tftlcd_H #include "system.h" //定義LCD彩屏的驅動類型 可根據自己手上的彩屏背面型號來選擇打開哪種驅動 //#def…

江科大51單片機筆記【15】直流電機驅動(PWM)

寫在前言 此為博主自學江科大51單片機&#xff08;B站&#xff09;的筆記&#xff0c;方便后續重溫知識 在后面的章節中&#xff0c;為了防止篇幅過長和易于查找&#xff0c;我把一個小節分成兩部分來發&#xff0c;上章節主要是關于本節課的硬件介紹、電路圖、原理圖等理論…

鴻蒙模擬器運行NDK項目失敗 9568347

鴻蒙編譯NDK項目 模擬器運行NDK項目失敗 9568347 23:32:17.572: $ hdc file send D:\study\hongmeng\MyTestNdk\entry\build\default\outputs\default\entry-default-unsigned.hap "data/local/tmp/9fff4611338a424bb31b521bdc3555af" in 30 ms 23:32:17.651: $ hd…

uniapp+Vue3 開發小程序的下載文件功能

小程序下載文件&#xff0c;可以先預覽文件內容&#xff0c;然后在手機上打開文件的工具中選擇保存。 簡單示例&#xff1a;&#xff08;復制到HBuilder直接食用即可&#xff09; <template><view class"container-detail"><view class"example…

電機控制常見面試問題(九)

文章目錄 一、談談電機死區時間1.死區時間過短的后果&#xff1a;2.如何判斷死區時間不足?3.解決方案 二、請描述對實時操作系統&#xff08;RTOS&#xff09;的理解三.解釋FOC算法的原理并比較與其他無刷電機控制算法的優劣四.什么是電機堵轉&#xff0c;如何避免電機堵轉五.…

【分布式】聊聊分布式id實現方案和生產經驗

對于分布式Id來說&#xff0c;在面試過程中也是高頻面試題&#xff0c;所以主要針對分布式id實現方案進行詳細分析下。 應用場景 對于無論是單機還是分布式系統來說&#xff0c;對于很多場景需要全局唯一ID&#xff0c; 數據庫id唯一性日志traceId 可以方便找到日志鏈&#…

【性能測試】Jmeter如何做一份測試報告(3)

本篇文章主要介紹Jmeter中下載插件&#xff08;Jmeter Plugins&#xff09; 如何使用監聽器插件&#xff0c;線程組插件&#xff0c;梯度壓測線程組 測試報告需要去關注的數據&#xff0c;怎么看測試報告圖表 目錄 一&#xff1a;插件下載 1&#xff1a;下載地址 2&#xff…

cocos creator使用mesh修改圖片為圓形,減少使用mask,j減少drawcall,優化性能

cocos creator版本2.4.11 一個mask占用drawcall 3個以上&#xff0c;針對游戲中技能圖標&#xff0c;cd,以及多玩家頭像&#xff0c;是有很大優化空間 1.上代碼&#xff0c;只適合單獨圖片的&#xff0c;不適合在圖集中的圖片 const { ccclass, property } cc._decorator;c…

AI重構SEO關鍵詞布局

內容概要 在搜索引擎優化&#xff08;SEO&#xff09;領域&#xff0c;AI技術的深度應用正在顛覆傳統關鍵詞布局邏輯。通過機器學習算法與語義分析模型&#xff0c;智能系統能夠實時解析海量搜索數據&#xff0c;構建動態詞庫并精準捕捉用戶意圖。相較于依賴人工經驗的關鍵詞篩…

泛微ecode的頁面開發發送請求參數攜帶集合

1.在開發過程中我們難免遇見會存在需要將集合傳遞到后端的情況&#xff0c;那么這里就有一些如下的注意事項&#xff0c;如以下代碼&#xff1a; // 新增action.boundasync addQuestion(formData) {var theList this.questionAnswerList;var questionAnswerListArray new Ar…

20250212:linux系統DNS解析卡頓5秒的bug

問題: 1:人臉離線識別記錄可以正常上傳云端 2:人臉在線識別請求卻一直超時 3:客戶使用在線網絡 思路:

道路運輸安全員考試:備考中的心理調適與策略

備考道路運輸安全員考試&#xff0c;心理調適同樣重要。考試壓力往往會影響考生的學習效率和考試發揮。? 首先&#xff0c;要正確認識考試壓力。適度的壓力可以激發學習動力&#xff0c;但過度的壓力則會適得其反。當感到壓力過大時&#xff0c;要學會自我調節。可以通過運動…

LLM - 白話RAG(Retrieval-Augmented Generation)

文章目錄 Pre一、大模型的"幻覺"之謎1.1 何為"幻覺"現象&#xff1f;1.2 專業場景的致命挑戰 二、RAG技術解析&#xff1a;給大模型裝上"知識外掛"2.1 核心原理&#xff1a;動態知識增強2.2 技術實現三部曲 三、RAG vs 微調&#xff1a;技術選型…

探索現代 C++:新特性、工程實踐與熱點趨勢

目錄 一、現代 C 的關鍵特性與熱點關聯 二、精簡代碼示例解析 三、工程實踐中的應用思考 四、總結與展望 近幾年&#xff0c;人工智能、邊緣計算與跨語言開發成為技術熱點&#xff0c;而 C 作為高性能系統編程的主力軍&#xff0c;也在不斷進化。從 C11 到 C20&#xff0c;…

《HTML + CSS + JS 打造炫酷輪播圖詳解》

《HTML CSS JS 打造炫酷輪播圖詳解》 一、項目概述 本次項目旨在使用 HTML、CSS 和 JavaScript 實現一個具有基本功能的輪播圖&#xff0c;包括圖片自動輪播、上一張 / 下一張按鈕切換、小圓點指示與切換等功能&#xff0c;以提升網頁的交互性和視覺吸引力。 二、實現步驟…

257. 二叉樹的所有路徑(遞歸+回溯)

257. 二叉樹的所有路徑 力扣題目鏈接(opens new window) 給定一個二叉樹&#xff0c;返回所有從根節點到葉子節點的路徑。 說明: 葉子節點是指沒有子節點的節點。 示例: 思路&#xff1a;在葉子節點收割結果&#xff0c;如果不是葉子節點&#xff0c;則依次處理左右子樹&a…

【架構差異】SpringとSpringBoot:Bean機制的深入剖析與自動配置原理

目錄標題 SpringBoot框架中Bean機制的深入剖析與自動配置原理摘要1. 引言2. SpringBoot與Spring的架構差異2.1 從Spring到SpringBoot的演進2.2 SpringBoot中的Bean容器體系 3. SpringBoot的自動配置機制3.1 SpringBootApplication解析3.2 自動配置原理深度解析3.2.1 自動配置類…

CSDN博客:Markdown編輯語法教程總結教程(中)

?個人主頁&#xff1a;折枝寄北的博客 Markdown編輯語法教程總結 前言1. 列表1.1 無序列表1.2 有序列表1.3 待辦事項列表1.4 自定義列表 2. 圖片2.1 直接插入圖片2.2 插入帶尺寸的圖片2.3 插入寬度確定&#xff0c;高度等比例的圖片2.4 插入高度確定寬度等比例的圖片2.5 插入居…

ChebyKAN0、ChebyKAN1 網絡閱讀

目錄 ChebyKAN0 Chebyshev Polynomial-Based Kolmogorov-Arnold Networks: An Efficient Architecture for Nonlinear Function Approximation 參考文獻 文章內容 文章詳細結構 5. Experiments and Results 5.1 Digit Classification on MNIST 5.2 Function Approximat…

RK3588部署YOLOv8(2):OpenCV和RGA實現模型前處理對比

目錄 前言 1. 結果對比 1.1 時間對比 1.2 CPU和NPU占用對比 2. RGA實現YOLO前處理 2.1 實現思路 2.2 處理類的聲明 2.3 處理類的實現 總結 前言 RK平臺上有RGA (Raster Graphic Acceleration Unit) 加速&#xff0c;使用RGA可以減少資源占用、加速圖片處理速度。因此…