自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

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

系統(tǒng) Windows
從 Windows NT 3.5 版本開始,IOCP 正式登上歷史舞臺,歷經(jīng)多年的發(fā)展與完善,已經(jīng)成為 Windows 系統(tǒng)中異步 I/O 處理的核心技術(shù)之一。它不僅在服務(wù)器端應(yīng)用中大放異彩,助力構(gòu)建高并發(fā)、低延遲的網(wǎng)絡(luò)服務(wù)架構(gòu),在桌面應(yīng)用領(lǐng)域,也為提升用戶體驗立下了汗馬功勞。

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

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

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

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

一、IOCP 是什么?

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

(1)原理圖

圖片圖片

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

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

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

(2) IOCP優(yōu)點

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

I/O 完成端口可以充分利用 Windows 內(nèi)核來進行 I/O 調(diào)度,相較于傳統(tǒng)的 Winsock 模型,IOCP 在機制上有明顯的優(yōu)勢。

圖片圖片

相較于傳統(tǒng)的Winsock模型,IOCP的優(yōu)勢主要體現(xiàn)在兩方面:獨特的異步I/O方式和優(yōu)秀的線程調(diào)度機制。

◆獨特的異步I/O方式

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

  • ① socket關(guān)聯(lián)iocp
  • ② 在socket上投遞I/O請求
  • ③ 事件完成返回完成通知封包
  • ④ 工作線程在iocp上處理事件

圖片圖片

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

◆優(yōu)秀的線程調(diào)度機制

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

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

(3)IOCP應(yīng)用

①創(chuàng)建和關(guān)聯(lián)完成端口

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

②與socket進行關(guān)聯(lián)

typedef struct{
    SOCKET socket;//客戶端socket
    SOCKADDR_STORAGE ClientAddr;//客戶端地址
}PER_HANDLE_DATA, *LPPER_HANDLE_DATA;

//與socket進行關(guān)聯(lián)
CreateIoCompletionPort((HANDLE)(PerHandleData -> socket), 
completionPort, (DWORD)PerHandleData, 0);

③獲取隊列完成狀態(tài)

//功能:獲取隊列完成狀態(tài)
/*
返回值:
調(diào)用成功,則返回非零數(shù)值,相關(guān)數(shù)據(jù)存于lpNumberOfBytes、lpCompletionKey、lpoverlapped變量中。失敗則返回零值。
*/
BOOL   GetQueuedCompletionStatus(
    HANDLE   CompletionPort,          //完成端口句柄
    LPDWORD   lpNumberOfBytes,    //一次I/O操作所傳送的字節(jié)數(shù)
    PULONG_PTR   lpCompletionKey, //當文件I/O操作完成后,用于存放與之關(guān)聯(lián)的CK
    LPOVERLAPPED   *lpOverlapped, //IOCP特定的結(jié)構(gòu)體
    DWORD   dwMilliseconds);           //調(diào)用者的等待時間
/*

④用于IOCP的特點函數(shù)

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

⑤投遞一個隊列完成狀態(tài)

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

二、IOCP 的工作原理

2.1核心組件剖析

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

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

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

重疊 I/O 機制是 IOCP 實現(xiàn)異步操作的基礎(chǔ)。在重疊 I/O 模式下,應(yīng)用程序可以在發(fā)起 I/O 操作后,立即繼續(xù)執(zhí)行其他任務(wù),而無需等待 I/O 操作的完成。這是通過使用 OVERLAPPED 結(jié)構(gòu)來實現(xiàn)的,每個 I/O 操作都關(guān)聯(lián)一個 OVERLAPPED 結(jié)構(gòu),該結(jié)構(gòu)包含了 I/O 操作的相關(guān)信息,如操作的偏移量、事件句柄等。當 I/O 操作完成時,系統(tǒng)會通過這個結(jié)構(gòu)來通知應(yīng)用程序,并傳遞操作的結(jié)果。例如,在進行文件讀取時,應(yīng)用程序可以將讀取操作與一個 OVERLAPPED 結(jié)構(gòu)關(guān)聯(lián)起來,然后繼續(xù)執(zhí)行其他代碼,當文件讀取完成后,系統(tǒng)會根據(jù) OVERLAPPED 結(jié)構(gòu)中的信息通知應(yīng)用程序,應(yīng)用程序再進行后續(xù)處理。

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

2.2工作流程深度解析

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

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

  1. 創(chuàng)建完成端口(iocp)對象
  2. 創(chuàng)建一個或多個工作線程,在完成端口上執(zhí)行并處理投遞到完成端口上的I/O請求
  3. Socket關(guān)聯(lián)iocp對象,在Socket上投遞網(wǎng)絡(luò)事件
  4. 工作線程調(diào)用GetQueuedCompletionStatus函數(shù)獲取完成通知封包,取得事件信息并進行處理

①創(chuàng)建完成端口對象

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

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

此函數(shù)的兩個不同功能:

  • 創(chuàng)建一個完成端口對象
  • 將一個或多個文件句柄(這里是套接字句柄)關(guān)聯(lián)到 I/O 完成端口對象

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

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

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

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

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

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

  • FileHandle:要關(guān)聯(lián)的套接字句柄
  • ExistingCompletionPort:要關(guān)聯(lián)的完成端口對象句柄
  • CompletionKey:指定一個句柄唯一(per-handle)數(shù)據(jù),它將與FileHandle套接字句柄關(guān)聯(lián)在一起

③完成端口和重疊I/O

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

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

參數(shù)說明

  • CompletionPort:完成端口對象句柄
  • lpNumberOfBytesTransferred:I/O操作期間傳輸?shù)淖止?jié)數(shù)
  • lpCompletionKey:關(guān)聯(lián)套接字時指定的句柄唯一數(shù)據(jù)
  • lpOverlapped:投遞 I/O 請求時使用的重疊對象地址,進一步得到 I/O 唯一(per-I/O)數(shù)據(jù)

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

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

per-handle數(shù)據(jù)和per-I/O數(shù)據(jù)結(jié)構(gòu)類型示例:

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

④示例程序

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

  • 調(diào)用 accept 函數(shù)等待接受未決的連接請求
  • 接受新連接后,創(chuàng)建 per-handle 數(shù),并將其關(guān)聯(lián)到完成端口對象
  • 在新接受的套接字上投遞一個接收請求,該I/O完成后,由工作線程負責處理
void main()
{
	int nPort = 4567;
	HANDLE hCompletion = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0);    //創(chuàng)建完成端口對象
	::CreateThread(NULL, 0, ServerThread, (LPVOID)hCompletion, 0, 0);    //創(chuàng)建工作線程
	//創(chuàng)建監(jiān)聽套接字,綁定到本地地址,開始監(jiān)聽
	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);

	//循環(huán)處理到來的連接
	while (true) {
		//等待接受未決的連接請求
		SOCKADDR_IN saRemote;
		int nRemoteLen = sizeof(saRemote);
		SOCKET sNew = ::accept(sListen, (sockaddr*)&saRemote, &nRemoteLen);
		//接受到新連接之后,為它創(chuàng)建一個per-handle數(shù)據(jù),并將它們關(guān)聯(lián)到完成端口對象
		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 工作線程循環(huán)調(diào)用 GetQueuedCompletionStatus 函數(shù)從 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) {
		//在關(guān)聯(lián)到此完成端口的所有套接字上等待I/O完成
		BOOL bOK = ::GetQueuedCompletionStatus(hCompletion, &dwTrans, (PULONG_PTR)&pPerHandle, (LPOVERLAPPED*)&pPerIO, WSA_INFINITE);
		if (!bOK) {
			//在此套接字上由錯誤發(fā)生
			::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數(shù)據(jù)中的nOperationType域查看有什么I/O請求完成了
		case OP_READ:  //完成一個接收請求
		{
			pPerIO->buf[dwTrans] = '\0';
			cout << "接收到數(shù)據(jù):" << pPerIO->buf << endl;
			cout << "共有" << dwTrans << "字符" << endl;
			//繼續(xù)投遞接收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;
}

⑤恰當?shù)仃P(guān)閉IOCP

關(guān)閉 I/O 完成端口時,特別是有多個線程在socket上執(zhí)行 I/O 時,要避免當重疊操作正在進行時釋放它的 OVERLAPPED 結(jié)構(gòu)。阻止該情況發(fā)生的最好方法是在每個 socket 上調(diào)用 closesocket 函數(shù),確保所有未決的重疊 I/O 操作都會完成。

一旦所有socket關(guān)閉,就該終止完成端口上處理 I/O 事件的工作線程了??梢酝ㄟ^調(diào)用 PostQueuedCompletionStatus 函數(shù)發(fā)送特定的完成封包來實現(xiàn)。所有工作線程都終止之后,可以調(diào)用 CloseHandle 函數(shù)關(guān)閉完成端口。

三、IOCP 與其他異步機制

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

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

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

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

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

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

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

四、IOCP實戰(zhàn)項目

4.1網(wǎng)絡(luò)服務(wù)器搭建

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

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

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

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

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) {
        // 處理線程創(chuàng)建失敗的情況
    }
    CloseHandle(hThread);
}

然后,開始監(jiān)聽客戶端連接。在監(jiān)聽函數(shù)中,使用 AcceptEx 函數(shù)異步接受客戶端連接 。AcceptEx 函數(shù)可以在接受連接的同時接收對方發(fā)來的第一組數(shù)據(jù),這大大提高了效率 。當有新的客戶端連接到來時,AcceptEx 函數(shù)會將連接信息封裝成完成通知放入完成端口隊列,工作線程會從隊列中獲取這個通知并進行后續(xù)處理,比如創(chuàng)建新的 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) {
    // 處理創(chuàng)建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) {
    // 處理接受連接失敗的情況
}

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

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

4.2文件處理應(yīng)用

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

以文件讀取為例,首先打開文件并創(chuàng)建完成端口 。使用 CreateFile 函數(shù)以重疊 I/O 模式打開文件,獲取文件句柄,然后調(diào)用 CreateIoCompletionPort 函數(shù)創(chuàng)建完成端口,并將文件句柄與完成端口進行關(guān)聯(lián) 。例如:

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) {
    // 處理創(chuàng)建完成端口失敗的情況
}

接著,投遞異步讀取操作 。準備好 OVERLAPPED 結(jié)構(gòu)和緩沖區(qū),通過調(diào)用 ReadFileEx 函數(shù)投遞異步讀取請求 。ReadFileEx 函數(shù)會立即返回,系統(tǒng)會在后臺進行文件讀取操作 。例如:

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) {
    // 處理讀取失敗的情況
}

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

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);
            // 處理讀取到的數(shù)據(jù)
            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) {
    // 處理線程創(chuàng)建失敗的情況
}
CloseHandle(hThread);

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

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) {
    // 處理創(chuàng)建完成端口失敗的情況
}
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) {
    // 處理寫入失敗的情況
}

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

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

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

5.1線程同步難題

線程同步問題是使用 IOCP 時經(jīng)常遇到的挑戰(zhàn)之一 。在多線程環(huán)境下,多個線程可能會同時訪問共享資源,這就容易引發(fā)數(shù)據(jù)競爭和不一致的問題 。例如,當多個工作線程同時處理完成端口隊列中的完成通知時,如果沒有進行適當?shù)耐?,可能會?dǎo)致對共享數(shù)據(jù)的錯誤操作 。比如在一個網(wǎng)絡(luò)服務(wù)器中,多個線程可能同時嘗試更新客戶端連接的狀態(tài)信息,如果沒有同步機制,就可能出現(xiàn)狀態(tài)信息混亂的情況 。

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

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

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

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

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

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

5.2內(nèi)存管理困境

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

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

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;
};

在這個類中,構(gòu)造函數(shù)會根據(jù)傳入的參數(shù)分配內(nèi)存池空間,并初始化相關(guān)數(shù)據(jù)結(jié)構(gòu) 。Allocate 函數(shù)用于從內(nèi)存池中分配內(nèi)存塊,Deallocate 函數(shù)用于將使用完的內(nèi)存塊歸還到內(nèi)存池 。通過這種方式,有效地管理內(nèi)存,提高內(nèi)存使用效率 。

5.3I/O 操作錯誤處理

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

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

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 函數(shù)返回 SOCKET_ERROR 時,我們通過 WSAGetLastError 函數(shù)獲取錯誤碼 。如果錯誤碼是 WSA_IO_PENDING,表示操作正在進行中,這是正常的異步 I/O 行為,無需特殊處理 。如果是其他錯誤碼,則調(diào)用 HandleError 函數(shù)進行錯誤處理 。在 HandleError 函數(shù)中,可以根據(jù)不同的錯誤碼進行相應(yīng)的處理,如記錄錯誤日志、關(guān)閉相關(guān)資源、重新嘗試 I/O 操作等 。

5.4負載均衡不均

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

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

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

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

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

在使用 IOCP 的過程中,通過合理地解決線程同步、內(nèi)存管理、I/O 操作錯誤處理和負載均衡等問題,能夠讓我們更好地發(fā)揮 IOCP 的優(yōu)勢,構(gòu)建出更加穩(wěn)定、高效的應(yīng)用程序 。

責任編輯:武曉燕 來源: 深度Linux
相關(guān)推薦

2010-02-06 15:31:18

ibmdwAndroid

2009-07-22 14:55:16

ibmdwAndroid

2017-05-09 08:48:44

機器學(xué)習(xí)

2016-12-08 22:39:40

Android

2022-06-10 08:17:52

HashMap鏈表紅黑樹

2012-02-29 00:49:06

Linux學(xué)習(xí)

2025-02-24 10:07:10

2010-11-08 10:20:18

2024-02-26 08:52:20

Python傳遞函數(shù)參數(shù)參數(shù)傳遞類型

2022-09-02 15:11:18

開發(fā)工具

2025-03-06 14:00:00

C#性能頁面

2023-10-13 08:23:05

2009-07-03 18:49:00

網(wǎng)吧綜合布線

2011-10-26 20:47:36

ssh 安全

2024-06-07 08:51:50

OpenPyXLPythonExcel文件

2009-03-19 13:36:53

SSH安全通道遠程

2025-03-21 14:31:14

NumPyPython數(shù)組

2017-01-09 09:34:03

Docker容器傳統(tǒng)虛擬機

2023-05-09 08:34:51

PythonWith語句

2024-01-11 09:35:12

單元測試Python編程軟件開發(fā)
點贊
收藏

51CTO技術(shù)棧公眾號