【網絡編程】WSAAsyncSelect 模型

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

接著上次的博客繼續分享:select模型

10.8 異步選擇模型WSAAsyncSelect

10.8.1 基本概念

  • WSAAsyncSelect模型是Windows socket的一個異步I/O 模型,利用這個模型,應用程序 可在一個套接字上接收以Windows 消息為基礎的網絡事件通知。

  • Windows sockets應用程序在 創建套接字后,調用WSAAsyncSelect 函數注冊感興趣的網絡事件,當該事件發生時Windows 窗口收到消息,應用程序就可以對接收到的網絡事件進行處理了。

  • 利用WSAAsyncSelect 函數, 將socket 消息發送到hWnd 窗口上,然后在那里處理相應的FD_READ 、FD_WRITE 等消息。

  • WSAAsyncSelect 模型與select 模型的相同點是它們都可以對多個套接字進行管理。

  • 但它 們也有不小的區別。首先WSAAsyncSelect 模型是異步的,且通知方式不同。更重要的一點是: WSAAsyncSelect 模型應用在基于消息的Windows 環境下,使用該模型時必須創建窗口,而 select 模型可以廣泛應用在UNIX/Linux 系統,使用該模型不需要創建窗口。最后一點區別是:應用程序在調用WSAAsyncSelect 函數后,套接字就被設置為非阻塞狀態;而使用select 函數 不改變套接字的工作方式。

  • 由于要關聯一個Windows 窗口來接收消息,因此如果處理成千上萬的套接字就力不從心 了。這也是該模型的一個缺點。另外,由于調用WSAAsyncSelect 后,套接字被設為非阻塞模 式,那么其他一些函數調用不一定能成功返回,必須要對這些函數的調用返回做處理。對于這 一點,可以從accept() 、receive() 和 send() 等函數的調用中得到驗證。

  • WSAAsyncSelect模型也有其優點,即提供了讀寫數據能力的異步通知。而且,該模型為 確保接收所有數據提供了很好的機制,通過注冊FD_CLOSE網絡事件,可以從容關閉服務器與客戶端的連接,保證了數據的全部接收。

10.8.2 WSAAsyncSelect函數

WSAAsyncSelect函數會自動將套接字設置為非阻塞模式,并且把發生在該套接字上且是 你所感興趣的事件以Windows 消息的形式發送到指定的窗口。

WSAAsyncSelect函數聲明如下:

int WSAAsyncSelect(in SOCKET s,in HWND hWnd,__in unsigned int wMsg,__in long lEvent);
  • s: 標識一個需要事件通知的套接口的描述符。

  • hWnd: 標識一個在網絡事件發生時需要接收消息的窗口句柄。

  • wMsg: 在網絡事件發生時要接收的消息。

  • IEvent: 位屏蔽碼,用于指明應用程序感興趣的網絡事件集合。IEvent 參數可取下列 值:

    • FD_READ: 欲接收讀準備好的通知。發生FD_READ 的條件是:
      • 調 用recv 或 者recvfrom 函數后,仍然有數據可讀。
      • 調用WSAAsyncSelect 有數據可讀。
    • FD_WRITE: 欲接收寫準備好的通知。發生FD_WRITE 的條件是:
      • 當調用WSAAsyncSelect 函數時,如果調用能夠發送數據。
      • 調用connect 或 者accept 函數后,當連接已經建立時。
      • 調用send 或 者sendto, 返 回WSAWOULDBLOCK 錯誤碼,再次調用send 或 者sendto 函數可能成功時。
    • FD_OOB: 欲接收帶邊數據到達的通知。
    • FD_ACCEPT: 欲接收將要連接的通知。
    • FD_CONNECT: 欲接收已連接好的通知。
    • FD_CLOSE: 欲接收套接口關閉的通知。發生FD CLOSE 的條件是:
      • 當調用WSAAsyncSelect 函數時,套接字連接關閉時。
      • 對方執行從容關閉后,沒有數據可讀時,如果數據已經到達并等待讀取,FD_CLOSE 事件不會被發送,直到所有數據都被接收。
      • 調用shutdown 函數執行從容關閉,對方應答FIN 后,此時無數據可讀。
      • 對方結束了連接,并且lparam 包 含WSAECONNRESET 錯誤時。
    • FD_QOS: 欲接收套接字服務質量發生變化的通知。
    • FD_GROUP_QOS: 欲接收套接字組服務質量發生變化的通知。
    • FD_ADDRESS_LIST_CHANGE: 欲接收針對套接字的協議簇,本地地址列表發生變化的通知。
    • FD ROUTING INTERFACE CHANGE: 欲在指定方向上與路由接口發生變化的通知 。

如果函數成功就返回0,如果出錯就返回 SOCKET_ERROR,此時可用函數 WSAGetLastError 獲取更多信息。

可根據需要同時注冊多個網絡事件,這時要把網絡事件類型執行按位或(OR) 運算,然 后將它們分配給 IEvent 參數。例如,應用程序希望在套接字上接收連接完成、數據可讀和套 接字關閉的網絡事件,可調用如下函數:

WSAAsyncSelect(s, hwnd, WM_SOCKET, FD_CONNECT | FD_READ | FD_CLOSE);

當該套接字連接完成、有數據可讀或者套接字關閉的網絡事件發生時,就會有 WM_SOCKET消息發送給窗口句柄為hwnd 的窗口。

值得注意的是,啟動一個 WSAAsyncSelect 將使為同一個套接口啟動的所有先前的 WSAAsyncSelect 作廢。

使用WSAAsyncSelect 函數需要注意的地方:

  • (1)調用該函數后,套接字被設置為非阻塞模式,要想恢復為阻塞模式,必須再次調用 該函數,取消掉注冊過的事件,再調用ioctlsocket 設為阻塞模式。如果要取消所有的網絡事件通知,告知windows sockets實現不再為該套接字發送任何網
    絡事件相關的消息,要以參數IEvent 值為0調用函數,即
WSAAsyncSelect(s, hwnd,0,0)

盡管應用程序調用上述函數取消了網絡事件通知,但是在應用程序消息隊列中,可能還有 網絡消息在排隊。所以調用上述函數取消網絡事件消息后,應用程序還應該繼續準備接收網絡 事 件 。

  • (2)消息函數的wParam 參數為事件發生的套接字,LParam 對應錯誤消息和相應的事件, 可以調用宏WSAGETSELECTERROR(IParam) 、WSAGETSELECTEVENT(IParam) 來獲取具體 的 信 息 。
  • (3)多次調用WSAAsyncSelect 函數在同一個套接字上注冊不同的事件(多次調用采用 同樣或者不同樣的消息),最后一次調用將取消前面注冊的事件。比如前后兩次調用:
WSAAsyncSelect(s, hwnd, WM_SOCKET, FD_READ);
WSAAsyncSelect(s, hwnd, WM_SOCKET, FD_WRITE);

此時雖然消息相同,都是WM_SOCKET, 但是應用程序只能接收到FD_WRITE 網絡事件。

還有一種情況是消息不同、網絡事件也不同,比如:

WSAAsyncSelect(s, hwnd, wMsg1, FD_READ);
WSAAsyncSelect(s, hwnd, wMsg2, FD_WRITE);

第二次函數調用依舊將會取消第一次函數調用的作用,只有 FD_WRITE 網絡事件通過wMsg2 通知到窗口。
這也是很多初學者發現接收不到網絡事件的原因。因為最后一次調用將取消前面注冊的事 件。

  • (4)使用accept 函數建立的套接字與監聽套接字具有同樣的屬性,也就是說,在監聽套 接字上注冊的事件同樣會對建立連接的套接字起作用,如果一個監聽套接字請求 FD_READ 和 FD_WRITE 網絡事件,那么在該監聽套接字上接受的任何套接字也會請求 FD_READ 和 FD_WRITE 網絡事件,以及發送同樣的消息。

我們一般會在監聽套接字建立連接后重新為其注冊事件。

  • (5)為一個FD_READ網絡事件不要多次調用recv(函數,如果應用程序為一個FD_READ 網絡事件調用多個recv()函數,就會使得該應用程序收到多個FD_READ 網絡事件。如果在一 次接收FD_READ 網絡事件時需要調用多次 recv()函數,應用程序就應該在調用recv()函數之 前關閉FD_READ消息。

  • (6)使用FD_CLOSE 事件來判斷套接字是否已經關閉,錯誤代碼指示套接字是從容關閉 還是硬關閉:錯誤碼為0,代表從容關閉;錯誤碼為WSAECONNERESET, 則為硬關閉。如 果套接字從容關閉,數據全部接收,應用程序就會收到FD_CLOSE。

  • (7)發送數據出現失敗。一個應用程序當接收到第一個FD_WRITE 網絡事件后,便認為 在該套接字上可以發送數據。當調用輸出函數發送數據時,會收到 WSAEWOULDBLOCKE 錯誤。經過這樣的失敗后,要在下一次接收到FD_WRITE網絡事件后再次發送數據,才能夠 將數據成功發送。

10.8.3 實戰WSAAsyncSelect 模型

WSAAsyncSelect 傳參需要窗口句柄。為了簡化代碼,這里直接創建了一個mfc 對話框程 序,用m_hwnd 給 WSAAsyncSelect 傳參。對話框類名為WSAAsyncSelecDlg。

服務端

#define _WINSOCK_DEPRECATED_NO_WARNINGS#include <winsock2.h>
#include <windows.h>
#pragma comment(lib, "ws2_32.lib")#define WM_SOCKET (WM_USER + 101)//-------------------窗口過程----------------------
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{switch (uMsg){case WM_SOCKET:{SOCKET ss = wParam;   // wParam 參數標志了網絡事件發生的套接口long event = WSAGETSELECTEVENT(lParam); // 事件int error = WSAGETSELECTERROR(lParam);  // 錯誤碼if (error){closesocket(ss);return 0;}switch (event){case FD_ACCEPT:   //-----①連接請求到來{sockaddr_in Cadd;int Cadd_len = sizeof(Cadd);SOCKET sNew = accept(ss, (sockaddr*)&Cadd, &Cadd_len);if (sNew == INVALID_SOCKET){MessageBox(hwnd, L"調用accept()失敗!", L"標題欄提示", MB_OK);}else{WSAAsyncSelect(sNew, hwnd, WM_SOCKET, FD_READ | FD_CLOSE);}} break;case FD_READ:   //-----②數據發送來{char cbuf[256];memset(cbuf, 0, sizeof(cbuf));int cRecv = recv(ss, cbuf, sizeof(cbuf), 0);if ((cRecv == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET) || cRecv == 0){MessageBox(hwnd, L"調用recv()失敗!", L"標題欄提示", MB_OK);closesocket(ss);}else if (cRecv > 0){// 轉換消息為寬字符wchar_t wbuf[256];MultiByteToWideChar(CP_ACP, 0, cbuf, -1, wbuf, sizeof(wbuf) / sizeof(wchar_t));MessageBox(hwnd, wbuf, L"收到的信息", MB_OK);char Sbuf[] = "Hello client! I am server";int isend = send(ss, Sbuf, sizeof(Sbuf), 0);if (isend == SOCKET_ERROR || isend <= 0){MessageBox(hwnd, L"發送消息失敗!", L"標題欄提示", MB_OK);}else{MessageBox(hwnd, L"已經發信息到客戶端!", L"標題欄提示", MB_OK);}}} break;case FD_CLOSE:    //----③關閉連接{closesocket(ss);}break;}}break;case WM_CLOSE:if (IDYES == MessageBox(hwnd, L"是否確定退出?", L"message", MB_YESNO))DestroyWindow(hwnd);break;case WM_DESTROY:PostQuitMessage(0);break;default:return DefWindowProc(hwnd, uMsg, wParam, lParam);}return 0;
}//----------------WinMain()函數------------------
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{WNDCLASS wc;wc.style = CS_HREDRAW | CS_VREDRAW;wc.lpfnWndProc = WindowProc;wc.cbClsExtra = 0;wc.cbWndExtra = 0;wc.hInstance = hInstance;wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);wc.hCursor = LoadCursor(NULL, IDC_ARROW);HBRUSH hbrush = CreateSolidBrush(RGB(0, 128, 25));wc.hbrBackground = hbrush;wc.lpszMenuName = NULL;wc.lpszClassName = L"Test";//---注冊窗口類(使用寬字符版本函數)---- RegisterClassW(&wc);//---創建窗口---- HWND hwnd = CreateWindowW(L"Test", L"WSAAsyncSelect模型-服務端窗口", WS_SYSMENU, 300, 0, 600, 400, NULL, NULL, hInstance, NULL);if (hwnd == NULL){MessageBoxW(NULL, L"創建窗口出錯", L"標題欄提示", MB_OK);return 1;}//---顯示窗口---- ShowWindow(hwnd, SW_SHOWNORMAL);UpdateWindow(hwnd);//---初始化WSA---WSADATA wsaData;WORD wVersionRequested = MAKEWORD(2, 2);if (WSAStartup(wVersionRequested, &wsaData) != 0){MessageBoxW(NULL, L"WSAStartup() Failed", L"調用失敗", 0);return 1;}SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (s == INVALID_SOCKET){MessageBoxW(NULL, L"socket() Failed", L"調用失敗", 0);return 1;}sockaddr_in sin;sin.sin_family = AF_INET;sin.sin_port = htons(6000);sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");if (bind(s, (sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR){MessageBoxW(NULL, L"bind() Failed", L"調用失敗", 0);return 1;}if (listen(s, 3) == SOCKET_ERROR){MessageBoxW(NULL, L"listen() Failed", L"調用失敗", 0);return 1;}elseMessageBoxW(hwnd, L"進入監聽狀態!", L"標題欄提示", MB_OK);WSAAsyncSelect(s, hwnd, WM_SOCKET, FD_ACCEPT | FD_CLOSE);//---消息循環----MSG msg;while (GetMessageW(&msg, 0, 0, 0)){TranslateMessage(&msg);DispatchMessageW(&msg);}closesocket(s);WSACleanup();return msg.wParam;
}

客戶端

#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/diannao/72973.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/72973.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/72973.shtml

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

相關文章

論文閱讀方法

文章目錄 步驟一&#xff1a;對論文進行自我判斷閱讀題目和關鍵詞。閱讀摘要閱讀總結要點 步驟二&#xff1a;閱讀文章閱讀圖表和圖表的注釋閱讀引言閱讀實驗部分閱讀結果和作者對結果的討論&#xff08;創新點&#xff09;要點 步驟三&#xff1a;精度論文回答問題1回答問題2回…

計算機網絡:計算機網絡的組成和功能

計算機網絡的組成&#xff1a; 計算機網絡的工作方式&#xff1a; 計算機網絡的邏輯功能; 總結&#xff1a; 計算機網絡的功能&#xff1a; 1.數據通信 2.資源共享 3.分布式處理:計算機網絡的分布式處理是指將計算任務分散到網絡中的多個節點&#xff08;計算機或設備&…

字符串習題

單詞個數統計 原作&#xff1a; 輸入&#xff1a; 一行字符串。僅有空格和英文字母構成。 輸出&#xff1a; 英文字母個數letter_num 單詞個數word_num 出現最多的字母max_letter 出現最多的字母的出現次數max_letter_frequ 處理&#xff1a; 統計并輸出此句子英文字母…

解決火絨啟動時,報安全服務異常,無法保障計算機安全

1.找到控制面板-安全和維護-更改用戶賬戶控制設置 重啟啟動電腦解決。

python總結(1)

數據結構是以某種方式(如通過編號)組合起來的數據元素(如數、字符乃至其他數據結構)集合。在Python中&#xff0c;最基本的數據結構為序列(sequence)。序列中的每個元素都有編號&#xff0c;即其位置或索引&#xff0c;其中第一個元素的索引為0&#xff0c;第二個元素的索引為1…

NAT NAPT

NAT NAT&#xff08;Network Address Translation&#xff0c;網絡地址轉換&#xff09; 主要用于在不同網絡&#xff08;如私有網絡和公共互聯網&#xff09;之間進行 IP 地址轉換&#xff0c;解決IP 地址短缺問題&#xff0c;并提供一定的安全性。 IPv4 地址是 32 位&#xf…

快速排序(二叉樹的前序遞歸遍歷思想)

思路 之前我們從選擇排序&#xff0c;到選擇排序的穩定性優化&#xff0c;到冒泡排序&#xff0c;到插入排序&#xff0c;到插入排序的提前截止時間&#xff0c;到希爾排序&#xff0c;雖然逐步一直都在優化&#xff0c;但是時間復雜度還是N得平方&#xff0c;力扣提交的結果一…

Redis 面試篇

Redis相關面試題 緩存三劍客 面試官&#xff1a;什么是緩存穿透 ? 怎么解決 ? 緩存穿透是指查詢一個一定不存在的數據&#xff0c;如果從存儲層查不到數據則不寫入緩存&#xff0c;這將導致這個不存在的數據每次請求都要到 DB 去查詢&#xff0c;可能導致 DB 掛掉。這種情況…

群暉DS223 Docker搭建為知筆記

群暉DS223 Docker搭建為知筆記&#xff0c;打造你的專屬知識寶庫 一、引言 在數字化信息爆炸的時代&#xff0c;筆記軟件成為了我們管理知識、記錄靈感的得力助手。為知筆記&#xff0c;作為一款專注于工作筆記和團隊協作的云筆記產品&#xff0c;以其豐富的功能和便捷的使用體…

Linux網絡之數據鏈路層協議

目錄 數據鏈路層 MAC地址與IP地址 數據幀 ARP協議 NAT技術 代理服務器 正向代理 反向代理 上期我們學習了網絡層中的相關協議&#xff0c;為IP協議。IP協議通過報頭中的目的IP地址告知了數據最終要傳送的目的主機的IP地址&#xff0c;從而指引了數據在網絡中的一步…

分類評價指標

基礎概念解釋 TP、TN、FP、FN 這里T是True&#xff0c;F是False&#xff0c;P為Positive&#xff0c;N為Negative TP&#xff1a;被模型正確地預測為正樣本&#xff08;原本為正樣本&#xff0c;預測為正樣本&#xff09; TN&#xff1a;被模型正確地預測為負樣本&#xff0…

LeetCode 哈希章節

簡單 1. 兩數之和 給定一個整數數組 nums 和一個整數目標值 target&#xff0c;請你在該數組中找出 和為目標值 target 的那 兩個 整數&#xff0c;并返回它們的數組下標。 你可以假設每種輸入只會對應一個答案&#xff0c;并且你不能使用兩次相同的元素。 你可以按任意順序返…

WLAN(無線局域網)安全

WLAN安全涉及到保護無線局域網免受各種威脅和攻擊&#xff0c;以確保數據的保密性、完整性和可用性。以下是關于WLAN安全的多方面介紹&#xff1a; 一、主要安全威脅 竊聽&#xff1a;攻擊者利用特殊設備監聽無線信號&#xff0c;獲取傳輸中的數據&#xff0c;如用戶的賬號密…

江科大51單片機筆記【11】AT24C02(I2C總線)

一、存儲器 1.介紹 RAM的特點是存儲速度特別快&#xff0c;但是掉電會丟失&#xff1b;ROM的特點是存儲速度特別慢&#xff0c;但是掉電不會丟失 SRAM是所有存儲器最快的&#xff0c;一般用于電腦的CPU高速緩存&#xff0c;容量相對較少&#xff0c;成本較高&#xff1b;DRAM…

【C++指南】一文總結C++類和對象【中】

&#x1f31f; 各位看官好&#xff0c;我是egoist2023&#xff01; &#x1f30d; 種一棵樹最好是十年前&#xff0c;其次是現在&#xff01; &#x1f680; 今天來學習C類和對象的語法知識。注意&#xff1a;在本章節中&#xff0c;小編會以Date類舉例 &#x1f44d; 如果覺得…

PgSql 操作技巧

1、查詢數據導出csv數據 \COPY (SELECT w.* from t_sys_warn w ) TO /home/cuadmin/warn_output.csv WITH CSV HEADER;2、導出sql Insert語句 pg_dump -U 用戶名 -h 主機名 -p 端口號 -d 數據庫名 --inserts -t 表名 > 導出文件.sqlpg_dump -U username -d dbname -t tabl…

Unity ES3保存類的問題

有以下一個物品類 public class Item_Base//基礎物品 { public string ID; private Attribute_Data Item_attribute new(); } 當使用ES3保存這個類時&#xff0c;Item_attribute的數據不會被保存&#xff0c;因為它是私有private ES3保存類時&#xff0c;只會保存…

react基本功

useLayoutEffect useLayoutEffect 用于在瀏覽器重新繪制屏幕之前同步執行代碼。它與 useEffect 相同,但執行時機不同。 主要特點 執行時機:useLayoutEffect 在 DOM 更新完成后同步執行,但在瀏覽器繪制之前。這使得它可以在瀏覽器渲染之前讀取和修改 DOM,避免視覺上的閃爍…

Spring Boot筆記(上)

01 概要 Spring Boot 是 Java 領域最流行的 快速開發框架&#xff0c;專為簡化 Spring 應用的初始搭建和開發而設計。 一、Spring Boot 解決了什么問題&#xff1f; 傳統 Spring 痛點 ? 繁瑣的 XML 配置 ? 需要手動管理依賴版本 ? 部署依賴外部 Web 服務器&#xff08;如 …

目標檢測YOLO實戰應用案例100講-基于毫米波雷達的多目標檢測 (續)

目錄 3.2 改進的CFAR目標檢測算法 3.3 算法步驟描述 3.4 實驗結果與分析 基于VGG16-Net的毫米波雷達目標檢測算法 4.1 VGG16-Net網絡模型 4.2 改進VGG16-Net網絡的目標檢測算法 4.3 算法步驟描述 4.4 實驗結果與分析 知識拓展 基于毫米波雷達的多目標檢測:使…