IOCP學習筆記

前言

網(wǎng)絡(luò)上大部分的講解IOCP模型文章都比較斷章取義,要么是這里冒出一個術(shù)語,那邊出來一個不知名的名詞。

本文主要是給那些暫時還無太多的Windows編程基礎(chǔ)的人閱讀,里面解釋了一些相應(yīng)的前驅(qū)知識。比如管道、重疊I/O模型等等。

如果你已經(jīng)對這些了如指掌了,可以直接忽略本文——因為本文是給那些初學者看的。

不過即使是給初學者看的,很多概念只是提個大概,讓讀者心里有個印象而已。更進一步的詳細知識還是需要讀者自行翻閱相關(guān)資料。

前驅(qū)知識

管道

管道(PIPE)是用于進程間通信的一段共享內(nèi)存。創(chuàng)建管道的進程稱為管道服務(wù)器,連接到一個管道的進程稱為管道客戶機。一個進程在向管道寫入數(shù)據(jù)之后,另一個進程就可以從管道的另一端將其讀出來。

管道分兩種,匿名管道和命名管道。

匿名管道

匿名管道是在父進程和子進程間單向傳輸數(shù)據(jù)的一種未命名管道,只能在本地計算機中使用,而不能用于網(wǎng)絡(luò)間通信。

匿名管道由 CreatePipe() 函數(shù)創(chuàng)建。該函數(shù)在創(chuàng)建匿名管道的同時返回兩個句柄:讀句柄和寫句柄。其原型如下:

BOOL CreatePipe(
    PHANDLE hReadPipe,
    PHANDLE hWritePipe,
    LPSECURITY_ATTRIBUTES lpPipeAttributes,
    DWORD nSize
);

其中 hReadPipe 為指向讀句柄的指針, hWritePipe 為指向?qū)懢浔闹羔槪?lpPipeAttributes 為指向安全屬性的指針;最后的 nSize 為管道大小,若為 0 則由系統(tǒng)來決定。

匿名管道不支持異步讀寫操作。

命名管道

命名管道是在管道服務(wù)器和一臺或多臺管道客戶機之間進行單向或者雙向通信的一種命名的管道。一個命名管道的所有實例都共享同一個管道名,但是每一個實例都擁有獨立的緩存和句柄,并且為 客戶機 - 服務(wù)器 通信提供一個分離的管道。

命名管道可以在同一臺計算機的不同進程之間或者跨越一個網(wǎng)絡(luò)的不同計算機的不同進程間進行有連接的可靠數(shù)據(jù)通信。如果連接中斷,連接雙方都能立即受到連接斷開的信息。

每個命名管道都有一個唯一的名字,以區(qū)分存在于系統(tǒng)的命名對象列表中的其它命名管道。管道服務(wù)器在調(diào)用 CreateNamedPipe() 函數(shù)創(chuàng)建管道的一個或多個實例時為其指定了名稱。對于管道客戶機,則是在調(diào)用 CreateFile()CallNamedPipe() 函數(shù)在連接一個命名管道實例時對管道名進行指定。

命名管道對其標識采用 UNC格式

\\Server\Pipe\[Path]Name

其中第一部分 \\Server 指定了服務(wù)器的名字,命名管道服務(wù)就在此服務(wù)器創(chuàng)建。其字符串部分可以為一個小數(shù)點(表示本機)、星號(當前網(wǎng)絡(luò)字段)、域名或者是一個真正的服務(wù);第二部分是一個不可變化的硬編碼字符串;第三部分 \[Path]Name 則使應(yīng)用程序可以唯一定義及標識一個命名管道的名字,而且可以設(shè)置多級目錄。

管道服務(wù)器首次調(diào)用 CreateNamedPipe() 函數(shù)時,使用 nMaxInstance 參數(shù)指定了能同時存在的管道實例的最大數(shù)目。服務(wù)器可以重復調(diào)用 CreateNamedPipe() 函數(shù)去創(chuàng)建新的管道實例,直至達到設(shè)定的最大實例數(shù)。

下面給出 CreateNamedPipe() 的函數(shù)原型:

HANDLE CreateNamedPipe(
    LPCTSTR lpName,
    DWORD dwOpenMode,
    DWORD dwPipeMode,
    DWORD nMaxInstance,
    DWORD nOutBufferSize,
    DWORD nInBufferSize,
    DWORD nDefaultTimeOut,
    LPSECURITY_ATTRIBUTES lpSecurityAttributes
);

這里的 lpName 就是所謂的管道名稱指針了, dwOpenMode 為管道打開的模式(用來指示管道在創(chuàng)建好之后,它的傳輸方向、I/O控制以及安全模式), dwPipeMode 為管道模式, nMaxInstance 正如之前所說的是最大的管道實例數(shù), nOutBufferSize 為輸出緩存的大小, nInBufferSize 為輸入緩存的大小, nDefaultTimeOut 為超時設(shè)置,最后的 lpSecurityAttributes 為安全屬性的指針。

CreateFile, ReadFile等API

CreateFile()

這個函數(shù)可以創(chuàng)建或者打開一個對象的句柄,憑借此句柄我們就可以控制這些對象:

  • 控制臺對象
  • 通信資源對象
  • 目錄對象(只能打開)
  • 磁盤設(shè)備對象
  • 文件對象
  • 郵槽對象
  • 管道對象

函數(shù)原型:

HANDLE CreateFile(
    LPCTSTR lpFileName,
    DWORD dwDesiredAccess,
    DWORD dwShareMode,
    LPSECURITY_ATTRIBUTES lpSecurityAttributes,
    DWORD dwCreationDisposition,
    DWORD dwFlagsAndAttributes,
    HANDLE hTemplateFile
);

參數(shù)解析

  1. lpFileName: 一個指向無終結(jié)符的字符串指針,用來指明要創(chuàng)建或者打開的對象的名字。

  2. dwDesiredAccess: 指明對象的控制模式。一個應(yīng)用程序可以包含讀控制、寫控制、讀/寫控制、設(shè)備查詢控制。

  3. dwShareMode: 指定對象的共享模式。如果 dwShareMode == 0 則表示是互斥使用的。如果 CreateFile 打開成功,則別的程序只能等到當前程序關(guān)閉對象句柄 CloseHandle 后才能再打開或者使用。

  4. lpSecurityAttributes: 一個指向 SECURITY_ATTRIBUTES 結(jié)構(gòu)對象的指針,決定返回的句柄是否被子進程所繼承。如果 lpSecurityAttributes 參數(shù)為 NULL ,句柄就不能被子進程繼承。

  5. dwCreationDisposition: 指明當打開的對象存在或不存在的時候各需要怎么樣去處理。

  6. dwFlagsAndAttributes: 指定文件屬性和標志。

  7. hTemplateFile: 把具有 GENERIC_READ 權(quán)限的句柄指定為一個模板文件。這個模板文件提供了文件屬性和擴展屬性,用于創(chuàng)建文件。

返回值

如果調(diào)用成功,返回值是一個打開文件的句柄。

如果調(diào)用之前文件已經(jīng)存在,且 dwCreationDisposition 參數(shù)為 CREATE_ALWAYS 或者 OPEN_AWAYS ,用 GetLastError 返回 ERROR_ALREADY_EXISTS (即使調(diào)用成功也會返回這個值)。如果調(diào)用之前不存在 GetLastError 返回 0 。

如果調(diào)用失敗,返回值是 INVALID_HANDLE_VALUE 。要進一步了解出錯原因,調(diào)用 GetLastError。

CloseHandle()

用于關(guān)掉一個打開的對象句柄。

函數(shù)原型如下:

BOOL CloseHandle(
    HANDLE hObject
);

ReadFile()

ReadFile() 函數(shù)從文件指針指定的位置讀取數(shù)據(jù)。讀操作完畢之后,文件指針將根據(jù)實際讀出的數(shù)據(jù)自動進行調(diào)整,除非文件句柄是以 OVERLAPPED 屬性值打開的。如果是以 OVERLAPPED 打開的I/O,應(yīng)用程序就需要自己手動調(diào)整文件指針。

這個函數(shù)被設(shè)計成兼有同步和異步操作。 ReadFileEx() 函數(shù)則設(shè)計成只支持異步操作,異步操作允許應(yīng)用程序在讀文件期間可以同時進行其它的操作。

函數(shù)原型:

BOOL ReadFile(
    HANDLE hFile,
    LPVOID lpBuffer,
    DWORD nNumberOfBytesToRead,
    LPDWORD lpNumberOfBytesRead,
    LPOVERLAPPED lpOverlapped
);

參數(shù)解析

  1. hFile: 文件句柄(必須具有 GENERIC_READ 訪問權(quán)限)。

  2. lpBuffer: 用來接收從文件中讀出的數(shù)據(jù)的緩沖區(qū)。

  3. nNumberOfBytesToRead: 指明要讀取的字節(jié)總數(shù)。

  4. lpNumberOfBytesRead: 一個變量指針,用來存儲實際傳輸?shù)淖止?jié)總數(shù)。 ReadFile 在做所有事情(包括錯誤檢查)之前,先將這個值賦為 0。當 ReadFile 從一個命名管道上返回 TRUE 時這個參數(shù)為 0 ,說明消息管道另一端調(diào)用 WriteFile 時設(shè)置的 nNumberOfBytesToWrite 參數(shù)為 0 。如果 lpOverlapped 不是 NULLlpNumberOfBytesRead 可以設(shè)置為 NULL 。如果是一個 Overlapped 形式的讀操作,我們可以動用 GetOverlappedResult 函數(shù)來獲得傳輸?shù)膶嶋H字節(jié)數(shù)。如果 hFile 關(guān)聯(lián)的是一個完成端口(I/O Completion Port),那么可以調(diào)用 GetQueuedCompletionStatus 函數(shù)來獲得傳輸?shù)膶嶋H字節(jié)數(shù)。如果完成端口被占用,而你用的是一個用于釋放內(nèi)存的回調(diào)例程,對于 lpOverlapped 參數(shù)指向的 OVERLAPPED 結(jié)構(gòu)體來說,為這個參數(shù)指定 NULL 可以避免重新分配內(nèi)存時發(fā)生內(nèi)存泄露。內(nèi)存泄露會導致返回這個參數(shù)值時是一個非法值。

  5. lpOverlapped: 一個指向 OVERLAPPED 結(jié)構(gòu)體的指針。如果 hFile 是以 FILE_FLAG_OVERLAPPED 方式獲得的句柄,這個結(jié)構(gòu)是必須的,不能為 NULL (否則函數(shù)會在錯誤的時刻報告讀操作已經(jīng)完成了)。這時,讀操作在由 OVERLAPPEDOffset 成員指定的偏移地址開始讀,并且在實際完成讀操作之前就返回了。在這種情況下, ReadFile 返回 FALSE , GetLastError 報告的錯誤類型是 ERROR_IO_PENDING 。這允許調(diào)用進程繼續(xù)其它工作直到讀操作完成。 OVERLAPPED 結(jié)構(gòu)中的事件將會在讀操作完成時被使用。

返回值

有如下任一種情況發(fā)生都會導致函數(shù)返回:

  1. 在管道另一端的寫操作完成后。
  2. 請求的字節(jié)數(shù)傳輸完畢。
  3. 發(fā)生錯誤。

如果函數(shù)正確,返回非零。

如果返回值是非零但接受的字節(jié)數(shù)為 0 ,那么可能是文件指針在讀操作期間超出了文件的 end 位置。然而如果文件以 FILE_FLAG_OVERLAPPED 方式打開, lpOverlapped 參數(shù)不為 NULL ,文件指針在讀操作期間超出了文件的 end 位置,那么返回值肯定是 FALSE , GetLastError 返回的錯誤是 ERROR_HANDLE_EOF

WriteFile

可以以同步或異步方式向一個對象句柄中寫數(shù)據(jù)。

函數(shù)原型:

BOOL WriteFile(
    HANDLE hFile,
    LPCVOID lpBuffer,
    DWORD nNumberOfBytesToWrite,
    LPDWORD lpNumberOfBytesWritten,
    LPOVERLAPPED lpOverlapped
);

其它信息與 ReadFile 極其相似,可以參考 ReadFile

Winsock重疊I/O模型

重疊I/O模型的概念

當調(diào)用 ReadFile()WriteFile() 時,如果最后一個參數(shù) lpOverlapped 設(shè)置為 NULL ,那么線程就阻塞在這里,知道讀寫完指定的數(shù)據(jù)后,它們才會返回。這樣在讀寫大文件的時候,很多時間都浪費在等待 ReadFile()WriteFile() 的返回上面。如果 ReadFile()WriteFile() 是往管道里面讀寫數(shù)據(jù),那么有可能阻塞更久,導致程序性能下降。

為了解決這個問題,Windows引進了重疊I/O的概念,它能夠同時以多個線程處理多個I/O。其實你自己開多個線程也可以處理多個I/O,但是系統(tǒng)內(nèi)部對I/O的處理在性能上有很大的優(yōu)化。它是Windows下實現(xiàn)異步I/O的最常用的方式。

Windows為幾乎全部類型的文件提供這個工具:磁盤文件、通信端口、命名管道和套接字。通常,使用 ReadFile()WriteFile() 就可以很好地執(zhí)行重疊I/O。

重疊模型的核心是一個重疊數(shù)據(jù)結(jié)構(gòu)。若想以重疊方式使用文件,必須用 FILE_FLAG_OVERLAPPED 標志打開它,例如:

HANDLE hFile = CreateFile(
    lpFileName,
    GENERIC_READ | GENERIC_WRITE,
    FILE_SHARE_READ | FILE_SHARE_WRITE,
    NULL,
    OPEN_EXISTING,
    FILE_FLAG_OVERLAPPED,
    NULL
);

如果沒有規(guī)定該標志,則針對這個文件(句柄),重疊I/O是不可用的。如果設(shè)置了該標志,當調(diào)用 ReadFile()WriteFile() 操作這個文件(句柄)時,必須為最后一個參數(shù)提供 OVERLAPPED 結(jié)構(gòu):

// WINBASE.H
typedef struct _OVERLAPPED {
    DWORD  Internal;
    DWORD  InternalHigh;
    DWORD  Offset;
    DWORD  OffsetHigh;
    HANDLE hEvent;
} OVERLAPPED, *LPOVERLAPPED;

頭兩個32位的結(jié)構(gòu)字 InternalInternalHigh 由系統(tǒng)內(nèi)部使用。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一、溫故而知新 1. 內(nèi)存不夠怎么辦 內(nèi)存簡單分配策略的問題地址空間不隔離內(nèi)存使用效率低程序運行的地址不確定 關(guān)于...
    SeanCST閱讀 8,118評論 0 27
  • 姓名:莫益彰 學號:16030140019 【嵌牛導讀】:串口通信是指外設(shè)和計算機間,通過數(shù)據(jù)信號線 、地線、控制...
    換個名字消消毒閱讀 1,668評論 1 5
  • PHP常用函數(shù)大全 usleep() 函數(shù)延遲代碼執(zhí)行若干微秒。 unpack() 函數(shù)從二進制字符串對數(shù)據(jù)進行解...
    上街買菜丶迷倒老太閱讀 1,492評論 0 20
  • php usleep() 函數(shù)延遲代碼執(zhí)行若干微秒。 unpack() 函數(shù)從二進制字符串對數(shù)據(jù)進行解包。 uni...
    思夢PHP閱讀 2,133評論 1 24
  • 非原創(chuàng)文章,網(wǎng)絡(luò)收集,如遇原作者,請私聊會標明出處! 1--11 tcp協(xié)議中三次握手和四次揮手建立TCP需要三次...
    Juinjonn閱讀 2,269評論 0 28

友情鏈接更多精彩內(nèi)容