解鎖Windows異步黑科技:IOCP從入門到精通

在當今快節奏的數字化時代,軟件應用對性能的追求可謂永無止境。無論是高并發的網絡服務器,還是需要快速處理大量文件的桌面應用,都面臨著一個共同的挑戰:如何在有限的系統資源下,實現高效的數據輸入輸出(I/O)操作 。在 Windows 操作系統的廣袤世界里,有一種神秘而強大的異步機制 ——IOCP(Input/Output Completion Port,輸入輸出完成端口),如同隱藏在幕后的超級英雄,默默為無數高性能應用提供著強大的支持。

你是否曾好奇,那些能夠同時處理成千上萬用戶連接的網絡游戲服務器,是如何做到絲毫不卡頓,流暢地將玩家的操作指令與游戲世界的數據進行交互的?又或者,當你在使用一些專業的視頻編輯軟件,對大容量視頻文件進行快速剪輯和渲染時,軟件內部是怎樣巧妙地管理磁盤 I/O,以避免漫長的等待時間呢?

其實,在這些令人驚嘆的應用背后,IOCP 往往扮演著至關重要的角色。它打破了傳統 I/O 處理方式的局限,通過獨特的設計,讓應用程序能夠以異步的方式高效地處理 I/O 請求,極大地提升了系統的整體性能和響應速度 。

從 Windows NT 3.5 版本開始,IOCP 正式登上歷史舞臺,歷經多年的發展與完善,已經成為 Windows 系統中異步 I/O 處理的核心技術之一。它不僅在服務器端應用中大放異彩,助力構建高并發、低延遲的網絡服務架構,在桌面應用領域,也為提升用戶體驗立下了汗馬功勞。然而,盡管 IOCP 功能強大,但由于其工作原理較為復雜,涉及到操作系統內核層面的諸多機制,對于很多開發者來說,它就像一座神秘的寶藏,雖心向往之,卻不知從何下手挖掘 。接下來,就讓我們一同踏上這段充滿挑戰與驚喜的探索之旅,深入理解 Windows 異步機制中的 IOCP。從它的工作原理、核心組件,到實際應用中的編程技巧和最佳實踐,全方位地揭開 IOCP 的神秘面紗,讓你也能熟練掌握這一 Windows 異步黑科技,為自己的軟件項目注入強大的性能動力 。

一、IOCP 是什么?

IOCP模型屬于一種通訊模型,適用于Windows平臺下高負載服務器的一個技術。在處理大量用戶并發請求時,如果采用一個用戶一個線程的方式那將造成CPU在這成千上萬的線程間進行切換,后果是不可想象的。而IOCP完成端口模型則完全不會如此處理,它的理論是并行的線程數量必須有一個上限-也就是說同時發出500個客戶請求,不應該允許出現500個可運行的線程。目前來說,IOCP完成端口是Windows下性能最好的I/O模型,同時它也是最復雜的內核對象。它避免了大量用戶并發時原有模型采用的方式,極大地提高了程序的并行處理能力。

(1)原理圖

圖片

一共包括三部分:完成端口(存放重疊的I/O請求),客戶端請求的處理,等待者線程隊列(一定數量的工作者線程,一般采用CPU*2個)

完成端口中所謂的[端口]并不是我們在TCP/IP中所提到的端口,可以說是完全沒有關系。它其實就是一個通知隊列,由操作系統把已經完成的重疊I/O請求的通知放入其中。當某項I/O操作一旦完成,某個可以對該操作結果進行處理的工作者線程就會收到一則通知。

通常情況下,我們會在創建一定數量的工作者線程來處理這些通知,也就是線程池的方法。線程數量取決于應用程序的特定需要。理想的情況是,線程數量等于處理器的數量,不過這也要求任何線程都不應該執行諸如同步讀寫、等待事件通知等阻塞型的操作,以免線程阻塞。每個線程都將分到一定的CPU時間,在此期間該線程可以運行,然后另一個線程將分到一個時間片并開始執行。如果某個線程執行了阻塞型的操作,操作系統將剝奪其未使用的剩余時間片并讓其它線程開始執行。也就是說,前一個線程沒有充分使用其時間片,當發生這樣的情況時,應用程序應該準備其它線程來充分利用這些時間片。

(2) IOCP優點

基于IOCP的開發是異步IO的,決定了IOCP所實現的服務器的高吞吐量,通過引入IOCP,會大大減少Thread切換帶來的額外開銷,最小化的線程上下文切換,減少線程切換帶來的巨大開銷,讓CPU把大量的事件用于線程的運行。當與該完成端口相關聯的可運行線程的總數目達到了該并發量,系統就會阻塞。

I/O 完成端口可以充分利用 Windows 內核來進行 I/O 調度,相較于傳統的 Winsock 模型,IOCP 在機制上有明顯的優勢。

圖片

相較于傳統的Winsock模型,IOCP的優勢主要體現在兩方面:獨特的異步I/O方式和優秀的線程調度機制。

◆獨特的異步I/O方式

IOCP模型在異步通信方式的基礎上,設計了一套能夠充分利用Windows內核的I/O通信機制,主要過程為:

  • ① socket關聯iocp

  • ② 在socket上投遞I/O請求

  • ③ 事件完成返回完成通知封包

  • ④ 工作線程在iocp上處理事件

圖片

IOCP的這種工作模式:程序只需要把事件投遞出去,事件交給操作系統完成后,工作線程在完成端口上輪詢處理。該模式充分利用了異步模式高速率輸入輸出的優勢,能夠有效提高程序的工作效率。

◆優秀的線程調度機制

完成端口可以抽象為一個公共消息隊列,當用戶請求到達時,完成端口把這些請求加入其抽象出的公共消息隊列。這一過程與多個工作線程輪詢消息隊列并從中取出消息加以處理是并發操作。這種方式很好地實現了異步通信和負載均衡,因為它使幾個線程“公平地”處理多客戶端的I/O,并且線程空閑時會被掛起,不會占用CPU周期。

IOCP模型充分利用Windows系統內核,可以實現僅用少量的幾個線程來處理和多個client之間的所有通信,消除了無謂的線程上下文切換,最大限度的提高了網絡通信的性能。

(3)IOCP應用

①創建和關聯完成端口

//功能:創建完成端口和關聯完成端口HANDLE WINAPI CreateIoCompletionPort(* ? ?__in ? HANDLE FileHandle, ? ? ? ? ? ? ?// 已經打開的文件句柄或者空句柄,一般是客戶端的句柄* ? ?__in ? HANDLE ExistingCompletionPort, ?// 已經存在的IOCP句柄* ? ?__in ? ULONG_PTR CompletionKey, ? ? ? ?// 完成鍵,包含了指定I/O完成包的指定文件* ? ?__in ? DWORD NumberOfConcurrentThreads // 真正并發同時執行最大線程數,一般推介是CPU核心數*2* );
//創建完成端口句柄
HANDLE completionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);

②與socket進行關聯

typedef struct{SOCKET socket;//客戶端socketSOCKADDR_STORAGE ClientAddr;//客戶端地址
}PER_HANDLE_DATA, *LPPER_HANDLE_DATA;//與socket進行關聯
CreateIoCompletionPort((HANDLE)(PerHandleData -> socket),?
completionPort, (DWORD)PerHandleData, 0);

③獲取隊列完成狀態

//功能:獲取隊列完成狀態
/*
返回值:
調用成功,則返回非零數值,相關數據存于lpNumberOfBytes、lpCompletionKey、lpoverlapped變量中。失敗則返回零值。
*/
BOOL ? GetQueuedCompletionStatus(HANDLE ? CompletionPort, ? ? ? ? ?//完成端口句柄LPDWORD ? lpNumberOfBytes, ? ?//一次I/O操作所傳送的字節數PULONG_PTR ? lpCompletionKey, //當文件I/O操作完成后,用于存放與之關聯的CKLPOVERLAPPED ? *lpOverlapped, //IOCP特定的結構體DWORD ? dwMilliseconds); ? ? ? ? ? //調用者的等待時間
/*

④用于IOCP的特點函數

//用于IOCP的特定函數
typedef struct _OVERLAPPEDPLUS{OVERLAPPED ol; ? ? ?//一個固定的用于處理網絡消息事件返回值的結構體變量SOCKET s, sclient; ?int OpCode; ?//用來區分本次消息的操作類型(在完成端口的操作里面,                       是以消息通知系統,讀數據/寫數據,都是要發這樣的                       ?消息結構體過去的)WSABUF wbuf;     //讀寫緩沖區結構體變量?DWORD dwBytes, dwFlags; //一些在讀寫時用到的標志性變量?
}OVERLAPPEDPLUS;

⑤投遞一個隊列完成狀態

//功能:投遞一個隊列完成狀態
BOOL PostQueuedCompletionStatus(?HANDLE CompletlonPort, //指定想向其發送一個完成數據包的完成端口對象DW0RD dwNumberOfBytesTrlansferred, //指定—個值,直接傳遞給GetQueuedCompletionStatus                        函數中對應的參數?DWORD dwCompletlonKey, //指定—個值,直接傳遞給GetQueuedCompletionStatus函數中對應的參數LPOVERLAPPED lpoverlapped, ); //指定—個值,直接傳遞給GetQueuedCompletionStatus

二、IOCP 的工作原理

2.1核心組件剖析

IOCP 的工作原理涉及到幾個關鍵的核心組件,它們相互協作,共同實現了高效的異步 I/O 操作 。

首先是完成端口隊列,它就像是一個 “任務完成通知中心”,是操作系統維護的一個隊列,專門用于存儲已完成的 I/O 操作的相關信息。當一個 I/O 操作完成時,系統會生成一個完成通知,并將其放入這個隊列中。這個隊列采用先進先出(FIFO)的方式進行管理,確保每個完成的 I/O 操作都能按照順序被處理 。例如,當一個網絡數據包接收完成后,關于這個接收操作的完成通知就會被放入完成端口隊列,等待后續處理。

線程池調度則是 IOCP 高效運行的關鍵之一。線程池是一組預先創建好的線程的集合,這些線程被稱為工作線程。它們的主要任務是不斷地從完成端口隊列中獲取完成通知,并對其進行處理。線程池調度通過合理的算法,確保每個工作線程都能被充分利用,同時避免線程的過度創建和銷毀,從而大大減少了系統開銷。比如,當有多個 I/O 操作同時完成時,線程池調度會根據一定的策略,將這些完成通知分配給空閑的工作線程進行處理,實現了負載均衡 。

重疊 I/O 機制是 IOCP 實現異步操作的基礎。在重疊 I/O 模式下,應用程序可以在發起 I/O 操作后,立即繼續執行其他任務,而無需等待 I/O 操作的完成。這是通過使用 OVERLAPPED 結構來實現的,每個 I/O 操作都關聯一個 OVERLAPPED 結構,該結構包含了 I/O 操作的相關信息,如操作的偏移量、事件句柄等。當 I/O 操作完成時,系統會通過這個結構來通知應用程序,并傳遞操作的結果。例如,在進行文件讀取時,應用程序可以將讀取操作與一個 OVERLAPPED 結構關聯起來,然后繼續執行其他代碼,當文件讀取完成后,系統會根據 OVERLAPPED 結構中的信息通知應用程序,應用程序再進行后續處理。

這些核心組件緊密協作,當應用程序發起一個重疊 I/O 操作時,操作系統會將這個操作放入設備等待隊列中,同時標記該操作對應的 OVERLAPPED 結構。當 I/O 操作完成時,系統會將操作結果封裝成完成通知,放入完成端口隊列。此時,線程池中的工作線程會不斷地調用 GetQueuedCompletionStatus 函數,從完成端口隊列中獲取完成通知。一旦獲取到完成通知,工作線程就會根據通知中的信息,對完成的 I/O 操作進行處理,處理完成后,工作線程繼續等待下一個完成通知 。通過這種方式,IOCP 實現了高效的異步 I/O 處理,大大提高了系統的性能和響應速度。

2.2工作流程深度解析

初次學習使用IOCP的朋友在熟悉各個API時,建議參看MSDN的官方文檔。

IOCP的使用主要分為以下幾步:

  1. 創建完成端口(iocp)對象

  2. 創建一個或多個工作線程,在完成端口上執行并處理投遞到完成端口上的I/O請求

  3. Socket關聯iocp對象,在Socket上投遞網絡事件

  4. 工作線程調用GetQueuedCompletionStatus函數獲取完成通知封包,取得事件信息并進行處理

①創建完成端口對象

使用IOCP模型,首先要調用 CreateIoCompletionPort 函數創建一個完成端口對象,Winsock將使用這個對象為任意數量的套接字句柄管理 I/O 請求。函數定義如下:

HANDLE WINAPI CreateIoCompletionPort(_In_ ? ? HANDLE ? ?FileHandle,_In_opt_ HANDLE ? ?ExistingCompletionPort,_In_ ? ? ULONG_PTR CompletionKey,_In_ ? ? DWORD ? ? NumberOfConcurrentThreads
);

此函數的兩個不同功能:

  1. 創建一個完成端口對象

  2. 將一個或多個文件句柄(這里是套接字句柄)關聯到 I/O 完成端口對象

最初創建完成端口對象時,唯一需要設置的參數是 NumberOfConcurrentThreads,該參數定義了 允許在完成端口上同時執行的線程的數量。理想情況下,我們希望每個處理器僅運行一個線程來為完成端口提供服務,以避免線程上下文切換。NumberOfConcurrentThreads 為0表示系統允許的線程數量和處理器數量一樣多。因此,可以簡單地使用以下代碼創建完成端口對象,取得標識完成端口的句柄。

HANDLE m_hCompletion = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE,0,0,0);

②I/O工作線程和完成端口

I/O 工作線程在完成端口上執行并處理投遞的I/O請求。關于工作線程的數量,要注意的是,創建完成端口時指定的線程數量和這里要創建的線程數量不是一回事。CreateIoCompletionPort 函數的 NumberOfConcurrentThreads 參數明確告訴系統允許在完成端口上同時運行的線程數量。如果創建的線程數量多于 NumberOfConcurrentThreads,也僅有NumberOfConcurrentThreads 個線程允許運行。

但也存在確實需要創建更多線程的特殊情況,這主要取決于程序的總體設計。如果某個線程調用了一個函數,如 Sleep 或 WaitForSingleObject,進入了暫停狀態,多出來的線程中就會有一個開始運行,占據休眠線程的位置。

有了足夠的工作線程來處理完成端口上的 I/O 請求后,就該為完成端口關聯套接字句柄了,這就用到了 CreateCompletionPort 函數的前3個參數。

  1. FileHandle:要關聯的套接字句柄

  2. ExistingCompletionPort:要關聯的完成端口對象句柄

  3. CompletionKey:指定一個句柄唯一(per-handle)數據,它將與FileHandle套接字句柄關聯在一起

③完成端口和重疊I/O

向完成端口關聯套接字句柄之后,便可以通過在套接字上投遞重疊發送和接收請求處理 I/O。在這些 I/O 操作完成時,I/O 系統會向完成端口對象發送一個完成通知封包。I/O 完成端口以先進先出的方式為這些封包排隊。工作線程調用 GetQueuedCompletionStatus 函數可以取得這些隊列中的封包。函數定義如下:

BOOL GetQueuedCompletionStatus([in] ?HANDLE ? ? ? CompletionPort,LPDWORD ? ? ?lpNumberOfBytesTransferred,[out] PULONG_PTR ? lpCompletionKey,[out] LPOVERLAPPED *lpOverlapped,[in] ?DWORD ? ? ? ?dwMilliseconds
);

參數說明

  1. CompletionPort:完成端口對象句柄

  2. lpNumberOfBytesTransferred:I/O操作期間傳輸的字節數

  3. lpCompletionKey:關聯套接字時指定的句柄唯一數據

  4. lpOverlapped:投遞 I/O 請求時使用的重疊對象地址,進一步得到 I/O 唯一(per-I/O)數據

lpCompletionKey 參數包含了我們稱為 per-handle 的數據,該數據在套接字第一次關聯到完成端口時傳入,用于標識 I/O 事件是在哪個套接字句柄上發生的。可以給這個參數傳遞任何類型的數據。

lpOverlapped 參數指向一個 OVERLAPPED 結構,結構后面便是我們稱為per-I/O的數據,這可以是工作線程處理完成封包時想要知道的任何信息。

per-handle數據和per-I/O數據結構類型示例

#define BUFFER_SIZE 1024
//per-handle 數據
typedef struct _PER_HANDLE_DATA ?
{SOCKET s; ? ? ? ? ? ?//對應的套接字句柄SOCKADDR_IN addr; ? ?//客戶端地址信息
}PER_HANDLE_DATA,*PPER_HANDLE_DATA;
//per-I/O 數據
typedef struct _PER_IO_DATA ?
{OVERLAPPED ol; ? ? ? ? ? ?//重疊結構char buf[BUFFER_SIZE]; ? ?//數據緩沖區int nOperationType; ? ? ? //I/O操作類型
#define OP_READ 1
#define OP_WRITE 2
#define OP_ACCEPT 3
}PER_IO_DATA,*PPER_IO_DATA;

④示例程序

主線程首先創建完成端口對象,創建工作線程處理完成端口對象中的事件;然后創建監聽套接字,開始監聽服務端口;循環處理到來的連接請求,該過程具體如下:

  • 調用 accept 函數等待接受未決的連接請求

  • 接受新連接后,創建 per-handle 數,并將其關聯到完成端口對象

  • 在新接受的套接字上投遞一個接收請求,該I/O完成后,由工作線程負責處理

void main()
{int nPort = 4567;HANDLE hCompletion = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0); ? ?//創建完成端口對象::CreateThread(NULL, 0, ServerThread, (LPVOID)hCompletion, 0, 0); ? ?//創建工作線程//創建監聽套接字,綁定到本地地址,開始監聽SOCKET sListen = ::socket(AF_INET, SOCK_STREAM, 0);SOCKADDR_IN si;si.sin_family = AF_INET;si.sin_port = ::ntohs(nPort);si.sin_addr.S_un.S_addr = INADDR_ANY;::bind(sListen, (sockaddr*)&si, sizeof(si));::listen(sListen, 5);//循環處理到來的連接while (true) {//等待接受未決的連接請求SOCKADDR_IN saRemote;int nRemoteLen = sizeof(saRemote);SOCKET sNew = ::accept(sListen, (sockaddr*)&saRemote, &nRemoteLen);//接受到新連接之后,為它創建一個per-handle數據,并將它們關聯到完成端口對象PPER_HANDLE_DATA pPerHandle = (PPER_HANDLE_DATA)::GlobalAlloc(GPTR, sizeof(PER_HANDLE_DATA));pPerHandle->s = sNew;memcpy(&pPerHandle->addr, &saRemote, nRemoteLen);::CreateIoCompletionPort((HANDLE)pPerHandle->s, hCompletion, (DWORD)pPerHandle, 0);//投遞一個接收請求PPER_IO_DATA pPerIO = (PPER_IO_DATA)::GlobalAlloc(GPTR, sizeof(PER_IO_DATA));pPerIO->nOperationType = OP_READ;WSABUF buf;buf.buf = pPerIO->buf;buf.len = BUFFER_SIZE;DWORD dwRecv;DWORD dwFlags = 0;::WSARecv(pPerHandle->s, &buf, 1, &dwRecv, &dwFlags, &pPerIO->ol, NULL);}
}

I/O 工作線程循環調用 GetQueuedCompletionStatus 函數從 I/O 完成端口移除完成的 I/O 通知封包,解析并進行處理。

DWORD WINAPI ServerThread(LPVOID lpParam)
{ ? //得到完成端口對象句柄HANDLE hCompletion = (HANDLE)lpParam;DWORD dwTrans;PPER_HANDLE_DATA pPerHandle;PPER_IO_DATA pPerIO;while (true) {//在關聯到此完成端口的所有套接字上等待I/O完成BOOL bOK = ::GetQueuedCompletionStatus(hCompletion, &dwTrans, (PULONG_PTR)&pPerHandle, (LPOVERLAPPED*)&pPerIO, WSA_INFINITE);if (!bOK) {//在此套接字上由錯誤發生::closesocket(pPerHandle->s);::GlobalFree(pPerHandle);::GlobalFree(pPerIO);continue;}if (dwTrans == 0 && (pPerIO->nOperationType == OP_READ || pPerIO->nOperationType == OP_WRITE)) {::closesocket(pPerHandle->s);::GlobalFree(pPerHandle);::GlobalFree(pPerIO);continue;}switch (pPerIO->nOperationType){ ? //通過per-IO數據中的nOperationType域查看有什么I/O請求完成了case OP_READ: ?//完成一個接收請求{pPerIO->buf[dwTrans] = '\0';cout << "接收到數據:" << pPerIO->buf << endl;cout << "共有" << dwTrans << "字符" << endl;//繼續投遞接收I/O請求WSABUF buf;buf.buf = pPerIO->buf;buf.len = BUFFER_SIZE;pPerIO->nOperationType = OP_READ;DWORD nFlags = 0;::WSARecv(pPerHandle->s, &buf, 1, &dwTrans, &nFlags, &pPerIO->ol, NULL);}break;case OP_WRITE: //本例中沒有投遞這些類型的I/O請求case OP_ACCEPT: break;}}return 0;
}

⑤恰當地關閉IOCP

關閉 I/O 完成端口時,特別是有多個線程在socket上執行 I/O 時,要避免當重疊操作正在進行時釋放它的 OVERLAPPED 結構。阻止該情況發生的最好方法是在每個 socket 上調用 closesocket 函數,確保所有未決的重疊 I/O 操作都會完成。

一旦所有socket關閉,就該終止完成端口上處理 I/O 事件的工作線程了。可以通過調用 PostQueuedCompletionStatus 函數發送特定的完成封包來實現。所有工作線程都終止之后,可以調用 CloseHandle 函數關閉完成端口。

三、IOCP 與其他異步機制

在異步 I/O 的江湖中,IOCP 并非孤獨求敗,select、poll、epoll 等也是頗具威名的 “武林高手”,它們各自有著獨特的 “武功秘籍” ,在不同的場景下展現出不同的實力 。

select 作為異步 I/O 領域的 “元老”,有著廣泛的跨平臺支持,幾乎在所有主流操作系統中都能找到它的身影 。它就像是一個勤勞的 “管家”,通過維護一個文件描述符集合,來監聽多個 I/O 事件。當應用程序調用 select 時,它會遍歷這個集合,檢查每個文件描述符是否有事件發生。這種方式雖然簡單直接,但也存在明顯的弊端 。隨著文件描述符數量的增加,select 的性能會急劇下降,就像一個管家要同時照顧太多的事務,難免會顧此失彼 。

而且,每次調用 select 都需要將文件描述符集合從用戶態復制到內核態,這無疑增加了額外的開銷 。所以,select 更適合在少量連接的場景中發揮作用,就像一個小家庭的管家,管理少量事務時還能游刃有余 。例如,在一些簡單的網絡工具中,連接數較少,select 的性能瓶頸不太明顯,能夠很好地滿足需求 。

poll 在一定程度上改進了 select 的不足 。它同樣支持跨平臺,并且在處理大量連接時,比 select 更具效率 。poll 使用鏈表結構來管理文件描述符,避免了 select 中文件描述符集合大小的限制 。然而,poll 依然沒有擺脫遍歷整個描述符集合的命運 。當連接數非常大時,它的性能還是會受到影響,無法滿足大規模高并發場景的需求 。打個比方,poll 就像是一個稍微聰明一點的管家,雖然改進了管理方式,但面對大規模事務時,還是顯得力不從心 。比如在一些中型規模的網絡應用中,如果連接數不是特別巨大,poll 可以作為一個不錯的選擇 。

epoll 是 Linux 平臺上的 “異步 I/O 利器”,它采用了獨特的事件通知機制 。epoll 會將用戶關心的文件描述符及其事件注冊到內核的事件表中,當有事件發生時,內核會直接通知應用程序,而無需像 select 和 poll 那樣遍歷整個描述符集合 。這種方式大大提高了效率,尤其是在處理大量并發連接時,epoll 的優勢更加明顯 。它就像是一個擁有超能力的管家,能夠精準地感知到每個事務的變化,并及時做出響應 。epoll 還支持水平觸發和邊緣觸發兩種模式,為開發者提供了更多的靈活性 。不過,epoll 的局限性在于它僅在 Linux 平臺可用,不具備跨平臺性 。在連接數量較少時,它與 poll 的性能差距并不顯著 。比如在大型的 Linux 服務器上部署的網絡服務,需要處理大量并發連接,epoll 就能發揮其強大的性能優勢 。

與這些機制相比,IOCP 有著自己獨特的優勢 。它基于 Windows 平臺,采用異步 I/O 模型,工作線程不會被阻塞 。在處理大量并發連接時,IOCP 能夠充分利用 Windows 系統的特性,實現高效的 I/O 處理 。就像一個專業的 Windows 系統管家,對系統的各種資源和特性了如指掌,能夠高效地管理大量事務 。例如在 Windows 平臺上的高性能網絡服務器開發中,IOCP 能夠輕松應對大量用戶的并發請求,確保服務器的穩定運行 。但是,IOCP 也存在一些不足,它的編程模型相對復雜,學習成本較高 。對于開發者來說,需要花費更多的時間和精力去理解和掌握它的使用方法 。

select 和 poll 在處理大規模并發連接時性能較差,更適合連接數較少的場景;epoll 在 Linux 平臺上表現出色,尤其適用于大量并發連接的場景,但不具備跨平臺性;IOCP 則是 Windows 平臺下處理大量并發連接的首選,雖然編程模型復雜,但性能卓越 。在實際應用中,我們需要根據具體的需求和平臺特點,選擇最合適的異步機制,讓程序發揮出最佳性能 。

四、IOCP實戰項目

4.1網絡服務器搭建

在網絡服務器的搭建中,IOCP 就像是一位 “超級管家”,能夠高效地管理眾多客戶端的連接和數據傳輸請求,顯著提升服務器的性能和并發處理能力 。以一個簡單的 TCP 服務器為例,我們來看看 IOCP 是如何發揮作用的 。

首先,創建完成端口和監聽 Socket。通過調用 CreateIoCompletionPort 函數創建一個完成端口,這個完成端口就像是服務器的 “指揮中心” 。然后,使用 WSASocket 函數創建一個監聽 Socket,并將其與完成端口進行綁定 。例如:

HANDLE hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
if (hCompletionPort == NULL) {// 處理創建失敗的情況
}
SOCKET listenSocket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
if (listenSocket == INVALID_SOCKET) {// 處理創建Socket失敗的情況
}
CreateIoCompletionPort((HANDLE)listenSocket, hCompletionPort, (ULONG_PTR)0, 0);

接著,創建工作線程。工作線程就像是 “勤勞的小蜜蜂”,負責從完成端口隊列中獲取完成通知并進行處理 。通過調用 CreateThread 函數創建多個工作線程,每個工作線程都執行相同的函數,在這個函數中,使用 GetQueuedCompletionStatus 函數等待完成端口隊列中的完成通知 。例如:

DWORD WINAPI WorkerThread(LPVOID lpParam) {HANDLE hCompletionPort = (HANDLE)lpParam;while (true) {ULONG_PTR completionKey;OVERLAPPED* pOverlapped;DWORD bytesTransferred;BOOL ret = GetQueuedCompletionStatus(hCompletionPort, &bytesTransferred, (PULONG_PTR)&completionKey, &pOverlapped, INFINITE);if (ret) {// 處理I/O完成事件ProcessIoCompletion(completionKey, pOverlapped, bytesTransferred);}else {// 處理錯誤情況HandleError(GetLastError());}}return 0;
}
for (int i = 0; i < numThreads; ++i) {HANDLE hThread = CreateThread(NULL, 0, WorkerThread, (LPVOID)hCompletionPort, 0, NULL);if (hThread == NULL) {// 處理線程創建失敗的情況}CloseHandle(hThread);
}

然后,開始監聽客戶端連接。在監聽函數中,使用 AcceptEx 函數異步接受客戶端連接 。AcceptEx 函數可以在接受連接的同時接收對方發來的第一組數據,這大大提高了效率 。當有新的客戶端連接到來時,AcceptEx 函數會將連接信息封裝成完成通知放入完成端口隊列,工作線程會從隊列中獲取這個通知并進行后續處理,比如創建新的 Socket 用于與客戶端通信,并將其與完成端口綁定 。例如:

typedef BOOL(WINAPI* PFNACCEPTEX)(SOCKET, SOCKET, PVOID, DWORD, DWORD, DWORD, LPDWORD, LPOVERLAPPED);
PFNACCEPTEX pfnAcceptEx;
DWORD dwBytes;
GUID guidAcceptEx = WSAID_ACCEPTEX;
::WSAIoctl(listenSocket, SIO_GET_EXTENSION_FUNCTION_POINTER, &guidAcceptEx, sizeof(guidAcceptEx), &pfnAcceptEx, sizeof(pfnAcceptEx), &dwBytes, NULL, NULL);
SOCKET acceptSocket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
if (acceptSocket == INVALID_SOCKET) {// 處理創建Socket失敗的情況
}
CreateIoCompletionPort((HANDLE)acceptSocket, hCompletionPort, (ULONG_PTR)clientCtx, 0);
BOOL bRes = pfnAcceptEx(listenSocket, acceptSocket, buffer, uDataSize, uAddrSize, uAddrSize, &uAddrSize, (LPWSAOVERLAPPED)overlapped);
if (!bRes && WSAGetLastError() != ERROR_IO_PENDING) {// 處理接受連接失敗的情況
}

最后,處理客戶端數據收發。當客戶端有數據發送過來時,WSARecv 函數會將接收操作封裝成完成通知放入完成端口隊列,工作線程獲取通知后進行數據處理 。同樣,當服務器要向客戶端發送數據時,使用 WSASend 函數,它也會將發送操作封裝成完成通知放入隊列 。例如:

WSABUF wsaBuf = { bufferSize, pBuffer };
DWORD flags = 0;
OVERLAPPED* pOverlapped = new OVERLAPPED;
ZeroMemory(pOverlapped, sizeof(OVERLAPPED));
pOverlapped->hEvent = WSACreateEvent();
int result = WSARecv(clientSocket, &wsaBuf, 1, &bytesTransferred, &flags, pOverlapped, NULL);
if (result == SOCKET_ERROR && WSAGetLastError() != WSA_IO_PENDING) {// 處理接收失敗的情況
}

通過上述步驟,利用 IOCP 搭建的網絡服務器能夠輕松應對大量客戶端的并發連接,并且在數據收發處理上也能保持高效。在實際的高性能網絡服務器項目中,如游戲服務器、Web 服務器等,IOCP 被廣泛應用 。例如,在一款熱門的大型多人在線游戲服務器中,使用 IOCP 技術成功實現了支持數萬人同時在線的高并發場景,確保了游戲的流暢運行和玩家的良好體驗 。通過合理配置線程池和優化 I/O 操作,服務器的性能得到了極大提升,相比傳統的同步 I/O 模型,CPU 利用率顯著降低,響應速度更快,能夠快速處理玩家的各種操作請求,如移動、戰斗、聊天等 。

4.2文件處理應用

在文件處理領域,IOCP 同樣有著出色的表現,為文件讀寫等操作帶來了更高的效率 。當我們需要處理大文件的讀寫或者進行大量文件的并發操作時,IOCP 能夠充分發揮其異步 I/O 的優勢 。

以文件讀取為例,首先打開文件并創建完成端口 。使用 CreateFile 函數以重疊 I/O 模式打開文件,獲取文件句柄,然后調用 CreateIoCompletionPort 函數創建完成端口,并將文件句柄與完成端口進行關聯 。例如:

HANDLE hFile = CreateFile(L"test.txt", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
if (hFile == INVALID_HANDLE_VALUE) {// 處理文件打開失敗的情況
}
HANDLE hCompletionPort = CreateIoCompletionPort((HANDLE)hFile, NULL, (ULONG_PTR)0, 0);
if (hCompletionPort == NULL) {// 處理創建完成端口失敗的情況
}

接著,投遞異步讀取操作 。準備好 OVERLAPPED 結構和緩沖區,通過調用 ReadFileEx 函數投遞異步讀取請求 。ReadFileEx 函數會立即返回,系統會在后臺進行文件讀取操作 。例如:

OVERLAPPED overlapped;
ZeroMemory(&overlapped, sizeof(OVERLAPPED));
overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
char buffer[1024];
DWORD bytesRead;
BOOL result = ReadFileEx(hFile, buffer, sizeof(buffer), &overlapped, NULL);
if (!result && GetLastError() != ERROR_IO_PENDING) {// 處理讀取失敗的情況
}

然后,工作線程處理讀取完成的通知 。創建工作線程,在工作線程函數中,使用 GetQueuedCompletionStatus 函數等待完成端口隊列中的讀取完成通知 。當有通知到來時,根據通知中的信息處理讀取到的數據 。例如:

DWORD WINAPI FileReadThread(LPVOID lpParam) {HANDLE hCompletionPort = (HANDLE)lpParam;while (true) {ULONG_PTR completionKey;OVERLAPPED* pOverlapped;DWORD bytesTransferred;BOOL ret = GetQueuedCompletionStatus(hCompletionPort, &bytesTransferred, (PULONG_PTR)&completionKey, &pOverlapped, INFINITE);if (ret) {// 處理文件讀取完成事件char* buffer = new char[bytesTransferred];memcpy(buffer, ((OVERLAPPED_EX*)pOverlapped)->buffer, bytesTransferred);// 處理讀取到的數據ProcessReadData(buffer, bytesTransferred);delete[] buffer;delete (OVERLAPPED_EX*)pOverlapped;}else {// 處理錯誤情況HandleError(GetLastError());}}return 0;
}
HANDLE hThread = CreateThread(NULL, 0, FileReadThread, (LPVOID)hCompletionPort, 0, NULL);
if (hThread == NULL) {// 處理線程創建失敗的情況
}
CloseHandle(hThread);

在文件寫入方面,原理與讀取類似 。使用 CreateFile 函數以寫模式打開文件,創建完成端口并關聯文件句柄,然后通過 WriteFileEx 函數投遞異步寫入請求 。當寫入操作完成時,工作線程從完成端口隊列中獲取完成通知并進行相應處理 。例如:

HANDLE hFile = CreateFile(L"test.txt", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_FLAG_OVERLAPPED, NULL);
if (hFile == INVALID_HANDLE_VALUE) {// 處理文件打開失敗的情況
}
HANDLE hCompletionPort = CreateIoCompletionPort((HANDLE)hFile, NULL, (ULONG_PTR)0, 0);
if (hCompletionPort == NULL) {// 處理創建完成端口失敗的情況
}
OVERLAPPED overlapped;
ZeroMemory(&overlapped, sizeof(OVERLAPPED));
overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
char buffer[] = "Hello, World!";
DWORD bytesWritten;
BOOL result = WriteFileEx(hFile, buffer, sizeof(buffer), &overlapped, NULL);
if (!result && GetLastError() != ERROR_IO_PENDING) {// 處理寫入失敗的情況
}

在實際應用中,比如在一個大型數據處理系統中,需要頻繁地讀取和寫入大量的文件 。使用 IOCP 技術后,系統能夠同時處理多個文件的讀寫操作,大大提高了數據處理的效率 。通過合理設置線程池和優化緩沖區管理,系統在處理大文件時的速度明顯提升,減少了等待時間,提高了整個系統的性能 。在備份軟件中,IOCP 也被用于高效地備份大量文件,確保備份過程快速、穩定 。

五、IOCP常見問題及解決方案

在使用 IOCP 這把強大 “武器” 的過程中,開發者們難免會遇到一些棘手的問題 ,就像在探險途中遭遇各種障礙一樣 。下面我們來看看一些常見的問題以及對應的解決方案 。

5.1線程同步難題

線程同步問題是使用 IOCP 時經常遇到的挑戰之一 。在多線程環境下,多個線程可能會同時訪問共享資源,這就容易引發數據競爭和不一致的問題 。例如,當多個工作線程同時處理完成端口隊列中的完成通知時,如果沒有進行適當的同步,可能會導致對共享數據的錯誤操作 。比如在一個網絡服務器中,多個線程可能同時嘗試更新客戶端連接的狀態信息,如果沒有同步機制,就可能出現狀態信息混亂的情況 。

為了解決這個問題,我們可以使用互斥鎖(Mutex)、信號量(Semaphore)等同步原語 。互斥鎖就像是一把 “獨占鎖”,當一個線程獲取到互斥鎖后,其他線程就無法再獲取,直到該線程釋放鎖 。例如,在對共享數據進行訪問前,先獲取互斥鎖:

HANDLE hMutex = CreateMutex(NULL, FALSE, NULL);
WaitForSingleObject(hMutex, INFINITE);
// 訪問共享數據
ReleaseMutex(hMutex);

信號量則可以控制同時訪問共享資源的線程數量 。比如,我們可以創建一個信號量,設置其初始值為 1,表示只允許一個線程訪問共享資源:

HANDLE hSemaphore = CreateSemaphore(NULL, 1, 1, NULL);
WaitForSingleObject(hSemaphore, INFINITE);
// 訪問共享數據
ReleaseSemaphore(hSemaphore, 1, NULL);

在實際應用中,還可以結合條件變量(Condition Variable)來實現更復雜的線程同步邏輯 。條件變量可以讓線程在某個條件滿足時被喚醒,從而避免不必要的等待 。比如,當某個共享數據達到一定條件時,通過條件變量喚醒等待的線程:

HANDLE hConditionVariable = CreateConditionVariable();
HANDLE hMutex = CreateMutex(NULL, FALSE, NULL);
// 等待條件變量
SleepConditionVariableCS(hConditionVariable, hMutex, INFINITE);
// 滿足條件時,喚醒等待的線程
WakeConditionVariable(hConditionVariable);

5.2內存管理困境

內存管理也是使用 IOCP 時需要特別注意的問題 。在異步 I/O 操作中,頻繁的內存分配和釋放可能會導致內存碎片,降低內存的使用效率 。例如,在處理大量的網絡數據包時,如果每次接收或發送數據都進行內存分配,隨著時間的推移,內存中會出現很多不連續的小塊空閑內存,即內存碎片 。這些碎片會使得后續的內存分配變得困難,因為無法找到足夠大的連續內存塊來滿足分配需求,從而導致內存分配失敗 。

為了避免內存碎片,我們可以采用內存池技術 。內存池就像是一個預先準備好的 “內存倉庫”,在程序啟動時,預先分配一塊較大的內存空間 。當需要進行內存分配時,直接從這個內存池中獲取內存塊,而不是每次都向操作系統申請 。當使用完內存塊后,將其歸還到內存池中,而不是釋放給操作系統 。這樣可以大大減少內存分配和釋放的次數,降低內存碎片的產生 。比如,我們可以定義一個內存池類:

class MemoryPool {
public:MemoryPool(size_t blockSize, size_t poolSize);~MemoryPool();void* Allocate();void Deallocate(void* block);
private:size_t m_blockSize;size_t m_poolSize;char* m_pool;bool* m_blockUsed;
};

在這個類中,構造函數會根據傳入的參數分配內存池空間,并初始化相關數據結構 。Allocate 函數用于從內存池中分配內存塊,Deallocate 函數用于將使用完的內存塊歸還到內存池 。通過這種方式,有效地管理內存,提高內存使用效率 。

5.3I/O 操作錯誤處理

在進行 I/O 操作時,難免會出現各種錯誤,如網絡中斷、文件不存在等 。如果不能正確處理這些錯誤,可能會導致程序崩潰或出現異常行為 。例如,在進行文件讀取時,如果文件突然被刪除,而程序沒有對這種情況進行處理,就可能導致程序拋出異常 。

對于 I/O 操作錯誤,我們需要在代碼中進行全面的錯誤檢查和處理 。在調用異步 I/O 函數后,及時檢查返回值和錯誤碼 。以 WSARecv 函數為例:

int result = WSARecv(clientSocket, &wsaBuf, 1, &bytesTransferred, &flags, pOverlapped, NULL);
if (result == SOCKET_ERROR) {int errorCode = WSAGetLastError();if (errorCode == WSA_IO_PENDING) {// 操作正在進行中,無需處理}else {// 處理其他錯誤情況HandleError(errorCode);}
}

在這個例子中,當 WSARecv 函數返回 SOCKET_ERROR 時,我們通過 WSAGetLastError 函數獲取錯誤碼 。如果錯誤碼是 WSA_IO_PENDING,表示操作正在進行中,這是正常的異步 I/O 行為,無需特殊處理 。如果是其他錯誤碼,則調用 HandleError 函數進行錯誤處理 。在 HandleError 函數中,可以根據不同的錯誤碼進行相應的處理,如記錄錯誤日志、關閉相關資源、重新嘗試 I/O 操作等 。

5.4負載均衡不均

在多線程處理 I/O 操作時,可能會出現負載均衡不均的情況 。有些工作線程可能會承擔過多的任務,而有些則處于空閑狀態,這會導致整體性能下降 。例如,在一個網絡服務器中,如果某些客戶端的 I/O 操作比較頻繁,而這些操作又集中分配到了少數幾個工作線程上,就會使這些線程過于繁忙,而其他線程卻無事可做 。

為了解決負載均衡問題,我們可以采用一些負載均衡算法 。比如,簡單的輪詢算法,按照順序依次將任務分配給各個工作線程 。可以維護一個線程索引,每次有新的任務時,將任務分配給索引對應的線程,然后索引加 1,當索引超過線程數量時,重置為 0 。例如:

int threadIndex = 0;
while (true) {// 有新的I/O操作任務// 將任務分配給threadIndex對應的線程// 處理任務threadIndex = (threadIndex + 1) % numThreads;
}

除了輪詢算法,還可以根據線程的當前負載情況進行任務分配 。可以為每個線程維護一個負載計數器,記錄該線程正在處理的任務數量 。當有新任務時,將任務分配給負載計數器最小的線程 。這樣可以更合理地分配任務,實現更好的負載均衡 。例如:

// 假設threads是一個包含所有工作線程信息的數組
int minLoadIndex = 0;
for (int i = 1; i < numThreads; ++i) {if (threads[i].load < threads[minLoadIndex].load) {minLoadIndex = i;}
}
// 將新任務分配給minLoadIndex對應的線程
threads[minLoadIndex].load++;

在使用 IOCP 的過程中,通過合理地解決線程同步、內存管理、I/O 操作錯誤處理和負載均衡等問題,能夠讓我們更好地發揮 IOCP 的優勢,構建出更加穩定、高效的應用程序 。

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

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

相關文章

Java學習手冊:Spring 生態其他組件介紹

一、微服務架構相關組件 Spring Cloud 服務注冊與發現 &#xff1a; Eureka &#xff1a;由 Netflix 開源&#xff0c;包含 Eureka Server 和 Eureka Client 兩部分。Eureka Server 作為服務注冊表&#xff0c;接收服務實例的注冊請求并管理其信息&#xff1b;Eureka Client 負…

VMware Workstation 創建虛擬機并安裝 Ubuntu 系統 的詳細步驟指南

VMware Workstation 創建虛擬機并安裝 Ubuntu 系統 的詳細步驟指南 一、準備工作1. 下載 Ubuntu 鏡像2. 安裝 VMware Workstation 二、創建虛擬機1. 新建虛擬機向導2. 選擇虛擬機配置類型3. 加載安裝鏡像4. 系統類型配置5. 虛擬機命名與存儲6. 磁盤容量分配7. 硬件自定義&#…

串口的緩存發送以及緩存接收機制

#創作靈感# 在我們實際使用MCU進行多串口任務分配的時候&#xff0c;我們會碰到這樣一種情況&#xff0c;即串口需要短間隔周期性發送數據&#xff0c;且相鄰兩幀之間需要間隔一段時間&#xff0c;防止連幀。我們常常需要在軟件層面對串口的發送和接受做一個緩存的處理方式。 …

時間交織(TIADC)的失配誤差校正處理(以4片1GSPS采樣率的12bitADC交織為例講解)

待寫…有空再寫&#xff0c;有需要的留言。 存在失配誤差的4GSPS交織 校正完成后的4GSPS交織

Linux進程間通信(二)之管道1【匿名管道】

文章目錄 管道什么是管道匿名管道用fork來共享管道原理站在文件描述符角度-深度理解管道站在內核角度-管道本質 接口實例代碼管道特點管道的4種情況管道讀寫規則應用場景 管道 什么是管道 管道是Unix中最古老的進程間通信的形式。 我們把從一個進程連接到另一個進程的一個數…

Xilinx FPGA | 管腳約束 / 時序約束 / 問題解析

注&#xff1a;本文為 “Xilinx FPGA | 管腳約束 / 時序約束 / 問題解析” 相關文章合輯。 略作重排&#xff0c;未整理去重。 如有內容異常&#xff0c;請看原文。 Xilinx FPGA 管腳 XDC 約束之&#xff1a;物理約束 FPGA技術實戰 于 2020-02-04 17:14:53 發布 說明&#x…

家用服務器 Ubuntu 服務器配置與 Cloudflare Tunnel 部署指南

Ubuntu 服務器配置與 Cloudflare Tunnel 部署指南 本文檔總結了我們討論的所有內容&#xff0c;包括 Ubuntu 服務器配置、硬盤擴容、靜態 IP 設置以及 Cloudflare Tunnel 的部署步驟。 目錄 硬盤分區與擴容設置靜態 IPCloudflare Tunnel 部署SSH 通過 Cloudflare Tunnel常見…

分享5款開源、美觀的 WinForm UI 控件庫

前言 今天大姚給大家分享5款開源、美觀的 WinForm UI 控件庫&#xff0c;助力讓我們的 WinForm 應用更好看。 WinForm WinForm是一個傳統的桌面應用程序框架&#xff0c;它基于 Windows 操作系統的原生控件和窗體。通過簡單易用的 API&#xff0c;開發者可以快速構建基于窗體…

PHP盲盒商城系統源碼從零搭建部署:專業級開發與優化實踐

【導語&#xff1a;技術驅動商業創新】 在2025年社交電商全面升級的浪潮下&#xff0c;基于PHP的盲盒系統憑借其高開發效率與低成本優勢&#xff0c;成為中小企業的首選方案。本文將深度拆解盲盒源碼從開發到部署的全流程技術細節&#xff0c;涵蓋架構設計、性能優化與安全防護…

(33)VTK C++開發示例 ---圖片轉3D

文章目錄 1. 概述2. CMake鏈接VTK3. main.cpp文件4. 演示效果 更多精彩內容&#x1f449;內容導航 &#x1f448;&#x1f449;VTK開發 &#x1f448; 1. 概述 這是 VTK 測試 clipArt.tcl 的改編版本。 提供帶有 2D 剪貼畫的 jpg 文件&#xff0c;該示例將創建 3D 多邊形數據模…

2025東三省B題深圳杯B題數學建模挑戰賽數模思路代碼文章教學

完整內容請看文章最下面的推廣群 已經完成全部問題的代碼和建模 一、問題一的模型構建與優化&#xff08;RGB顏色空間轉換模型&#xff09; 基礎模型&#xff08;線性映射模型&#xff09;/高斯過程回歸模型&#xff08;GPR&#xff09;&#xff1a; 針對高清視頻源&#xff0…

linux netlink實現用戶態和內核態數據交互

1&#xff0c;內核態代碼 #include <linux/module.h> #include <linux/netlink.h> #include <net/sock.h> #define NETLINK_TEST 31 struct sock *nl_sk NULL; static void nl_recv_msg(struct sk_buff *skb) { struct nlmsghdr *nlh; int pid; …

LeetCode:DP-多狀態問題

簡單 面試題 17.16. 按摩師 一個有名的按摩師會收到源源不斷的預約請求&#xff0c;每個預約都可以選擇接或不接。在每次預約服務之間要有休息時間&#xff0c;因此她不能接受相鄰的預約。給定一個預約請求序列&#xff0c;替按摩師找到最優的預約集合&#xff08;總預約時間最…

Spring AOP---面向切面編程由認識到使用

1. AOP AOP(Aspect-Oriented Programming), 是一種思想, 面向切面編程。 在前文統一異常處理&#xff0c;統一結果返回就是使用了這一思想&#xff08;都是在集中處理某一類事情, 但又不影響原有代碼的正常運行&#xff09;&#xff0c;但他們不是AOP&#xff0c;只是應用了這…

專題二十四:虛擬專用網絡

一、VPN簡介 VPN&#xff08;Virtual Personal Network&#xff09;即虛擬專用網&#xff0c;泛指通過VPN技術在公用網絡上構建的虛擬專用網絡。VPN用戶在此虛擬網絡中傳輸私網流量&#xff0c;在不改變網絡現狀的情況下實現安全、可靠的連接。其主要功能是在公用網絡上建立專…

Milvus(12):分析器

1 分析器概述 在文本處理中&#xff0c;分析器是將原始文本轉換為結構化可搜索格式的關鍵組件。每個分析器通常由兩個核心部件組成&#xff1a;標記器和過濾器。它們共同將輸入文本轉換為標記&#xff0c;完善這些標記&#xff0c;并為高效索引和檢索做好準備。 在 Milvus 中&a…

Power Query精通指南1:查詢結構設計、數據類型、數據導入與遷移(平面文件、Excel、Web)

文章目錄 零、Power Query簡介0.1 Power Query 主要功能0.2 Power Query 的優勢0.3 Power Query 組件 一、Power Query數據處理基本流程1.1 前期準備1.2 提取1.3 轉換1.3.1 Power Query 編輯器界面1.3.2 默認轉換1.3.3 自定義轉換 1.4 加載1.4.1 自動檢測數據類型1.4.2 重命名查…

WebRTC 服務器之Janus概述和環境搭建

1 概述 Janus 是由 Meetecho 開發的通用 WebRTC 服務器&#xff0c;它為構建 WebRTC 應用程序提供了一個模塊化框架。服務器目標&#xff1a;Janus WebRTC 網關被設計為輕量級、通用的 WebRTC 服務器&#xff0c;除了實現以下方法外&#xff0c;它本身不提供任何功能&#xff1…

19:常見的Halcon數據格式

遍歷文件夾與文件選擇 1&#xff09;遍歷文件夾&#xff1a; list_files( : : Directory, Options : Files) Directory&#xff1a;目錄&#xff08;文件夾路徑&#xff09; Options&#xff1a;選項 files 指定搜索的格式為文件 directories 指定搜索的格式為文件夾 re…

QML圖像提供器 (Image Provider)

QML 中的圖像提供器是一種自定義圖像加載機制&#xff0c;允許你從非文件源&#xff08;如數據庫、網絡或程序生成的內容&#xff09;提供圖像數據。 主要類型 QQuickImageProvider - 基礎圖像提供器 QPixmapImageProvider - 提供 QPixmap 圖像 QImageImageProvider - 提供 …