具名管道實務 ── 從設計到安全,看懂 Windows 行程間通訊的標準做法

· · Windows, 行程間通訊, Windows 開發, C#, C++, 安全性, Win32 API

更新紀錄(僅初版,2026年08月22日 發布)
初次發布
引用本文(DOI(已登錄存檔): 10.5281/zenodo.22176791)

以下 DOI 指向先前登錄的存檔,內容可能與目前正文不同。引用目前正文時,請使用本頁網址。

Go Komura(2026)。〈具名管道實務 ── 從設計到安全,看懂 Windows 行程間通訊的標準做法〉。小村軟體有限公司。 https://comcomponent.com/zh-TW/blog/windows-named-pipes-practical-guide/

DOI(已登錄存檔)
10.5281/zenodo.22176791
DOI(上次登錄版本)
10.5281/zenodo.22176792

「想從設定畫面對常駐服務下指令」「想只把需要系統管理員權限的處理拆到另一個行程」──在 Windows 上要做這類行程間通訊(IPC)時,最先值得考慮的就是具名管道(named pipe)。

它在同一台 PC 內的通訊中成為第一選擇,理由不只是讀寫方便。可以用 Windows 的 ACL 收斂連線者,必要時確認對方的 Windows 帳戶並以該權限執行處理,這才是最大的好處。不過,光是把管道建出來並不等於安全,連線與權限的設定還是要做。123

本文依連線形態 → 訊息邊界 → 伺服器結構 → 安全 → 故障時的行為的順序整理設計。讀者對象是撰寫 Windows 業務應用程式與服務的開發者。行程間通訊選型表的文章中提出的「同一台機器 IPC 的第一選擇」,這裡要一路挖到實作時的判斷。

1. 先講結論:設計上要決定的有 5 件事

在挑 API 之前,先把通訊對象、資料的單位、同時連線、權限、失敗時的處理決定好。

要決定的事 基本的選法 詳細閱讀
和誰通訊 對方是同一台 PC 內的 Windows 行程,就把具名管道當作第一選擇。若更重視遠端部署、其他作業系統、沿用既有協定,則考慮以 TCP 為基礎的做法 第 2 章
什麼算一則 想把一次寫入當成一則來處理,用訊息模式。已經有既有的封裝框架,用位元組模式 第 3 章
如何接受多個用戶端 用同一個名稱準備多個執行個體。新寫的 .NET 實作,非同步 I/O 加 async/await 最直接 第 4 章
允許誰連線與借用權限 把拒絕遠端、ACL、第一個執行個體的保證、用戶端的模擬等級配成一套來設計 第 5、6 章
通訊失敗了怎麼辦 把等待啟動、重新連線、訊息上限、回應確認寫進協定 第 7 章

具名管道可以把以 ACL 為基礎的連線控制與對用戶端的模擬,當成 Windows 內建的機制來用。改用 localhost 的 TCP 時,就得另外設計一套驗證來確認通訊對象是誰。反過來說,若將來想延伸到遠端通訊、想和其他作業系統通訊、想沿用 gRPC 等既有資產,以 TCP 為基礎的做法比較有利。23

特別重要的一點是:模擬失敗的要求不要執行。忽略失敗,處理就會以伺服器本身而非用戶端的權限繼續跑下去。要做具特權的服務時,請不要只看程式碼範例,一定讀到第 5、6 章。4

2. 連線機制:名稱共用,通道則是每個用戶端各一條

2.1 把建立、連線、讀寫的職責分開

具名管道是以 \\.\pipe\MyCompany.MyApp.Control 這類名稱識別的單向或雙向通道。伺服器與用戶端最先使用的 API 並不相同。1

階段 伺服器端 用戶端
準備通道 用 CreateNamedPipe 建立執行個體 使用伺服器準備好的管道名稱
建立連線 用 ConnectNamedPipe 等待用戶端 用 CreateFile 開啟同一個名稱
收送資料 用 ReadFile / WriteFile 讀寫 用 ReadFile / WriteFile 讀寫

連線建立之後,雙方都能以和檔案 I/O 相同的方式處理。除了名稱的指定之外,掌握下面關於執行個體與方向的想法,多重連線和連線錯誤也會比較容易理解。

2.2 一個執行個體負責一個用戶端

同一個名稱的管道可以建立多個,一個執行個體就是與一個用戶端之間的通道。名稱相同,並不代表所有用戶端共用一條通道。最大執行個體數在第一次呼叫 CreateNamedPipe 時指定,上限也可以寫成 PIPE_UNLIMITED_INSTANCES。15

具名管道的基本結構伺服器建立多個同名的管道執行個體並用 ConnectNamedPipe 等待連線,各用戶端用 CreateFile 開啟該名稱,與其中一個執行個體建立一對一的雙向通道伺服器執行個體 1執行個體 2執行個體 3用戶端 A用戶端 B用戶端 C

圖 1:雙向管道的例子。用同一個名稱準備多個執行個體,伺服器就能分別與多個用戶端一對一通訊。

2.3 「方向」是以伺服器端為準的說法

用戶端指定的存取方式,要和伺服器建立管道時的方向相符。不一致時 CreateFile 會失敗。6

伺服器建立的方向 伺服器的動作 用戶端指定的存取
雙向 又讀又寫 唯讀、唯寫、兩者兼具都可以開啟
輸出 只寫入 以唯讀方式開啟
輸入 只讀取 以唯寫方式開啟

所有執行個體都在使用中時,用戶端的 CreateFile 會得到 ERROR_PIPE_BUSY。這時用 WaitNamedPipe 等待空位,再呼叫一次 CreateFile。它和「伺服器還沒建立管道」是不同的失敗,會在 7.1 連同啟動順序的競爭一起整理。6

2.4 即使只在本機使用,也要決定遠端連線怎麼處理

具名管道也能以 \\server\pipe\名稱 的形式,透過 SMB 用於遠端連線。不過在現今的設計中幾乎沒有理由主動走這條路,本機 IPC 的重點是不要讓用不到的路徑一直開著。用 5.1 的 PIPE_REJECT_REMOTE_CLIENTS 明確拒絕。17

3. 模式的選法:先決定「到哪裡算一則」

3.1 要求與回應用訊息模式,已有邊界機制就用位元組模式

傳輸模式的差別,在於管道是否保留寫入的邊界。5

模式 接收端看到的東西 適合的用法
位元組模式(PIPE_TYPE_BYTE) 和 TCP 一樣,沒有斷點的位元組串流 自帶長度前綴等封裝框架的資料,或串流傳輸
訊息模式(PIPE_TYPE_MESSAGE 加訊息讀取) 以一次寫入為一則訊息的單位 一則一則的要求與回應
位元組模式與訊息模式的差別位元組模式下三次寫入會變成沒有斷點的位元組串流,接收端必須自己劃分邊界;訊息模式下每次寫入的單位會被保留,原樣送達接收端位元組模式:寫入 AAA、BB、CCCC收到的是 AAABBCCCC 位元組串流邊界(封裝框架)要自己設計訊息模式:同樣的三次寫入收到 AAA、BB、CCCC 三則寫入的單位被保留下來

圖 2:位元組模式下由接收端管理邊界。訊息模式能保留寫入的單位,但一次讀取未必就拿得到整則訊息。

如果還沒有自己的邊界機制,又想把「一次寫入=一個要求」來處理,訊息模式很方便。如果已經在用帶長度前綴的序列化格式之類,位元組模式就夠了。在 .NET 中,PipeTransmissionMode.Message 相當於訊息型的指定。8

對訊息型的雙向管道,還有把要求送出與回應接收合併成一次呼叫的 TransactNamedPipe。9

3.2 訊息型與讀取模式是兩套設定

讀取模式是逐控制代碼設定的。即使在 CreateNamedPipe 中指定了 PIPE_READMODE_MESSAGE,那也只是伺服器端的設定。用戶端用 CreateFile 開啟的控制代碼,預設仍是位元組讀取。6

實作 伺服器端 用戶端
Win32 指定 PIPE_TYPE_MESSAGE 與 PIPE_READMODE_MESSAGE 連線後用 SetNamedPipeHandleState 指定 PIPE_READMODE_MESSAGE
.NET 指定 PipeTransmissionMode.Message 連線後把 NamedPipeClientStream.ReadMode 設為 Message

重點在於,不要以為只把伺服器做成訊息型,用戶端就能照同樣的單位讀取。

3.3 即使是訊息模式,也會需要續讀

訊息的邊界被保留,和接收緩衝區裝得下整則訊息,是兩回事。緩衝區太小時 ReadFile 會傳回 ERROR_MORE_DATA,訊息會被拆開。需要一個迴圈:留住已經收到的部分,把剩下的續讀進來並串接。56

讀取結果 處理方式
成功 當成一則完整的訊息來處理
ERROR_MORE_DATA 還有剩餘,續讀並串接
其他錯誤 不再繼續處理該要求,當成中斷之類的通訊失敗來處理

出現「小的要求沒事,只有大的要求會壞掉」時,就檢查這段續讀處理。另外,也不是可以無限串接下去。單則訊息的最大大小同樣要當成協定決定下來,超過上限就中斷連線,以免巨大的訊息把記憶體吃光。

4. 伺服器結構:把接待與用戶端處理分開

4.1 比較同步執行緒型與 overlapped 型

伺服器的基本動作,是建立執行個體 → 等待連線 → 讀寫 → 中斷後進入下一輪的循環。要同時接受多個用戶端,就用多個執行個體並行推進這條流程。

結構 機制 優點與注意事項
同步執行緒型 每個執行個體配置一條執行緒,用同步 I/O 處理 程式碼直接。但執行緒數會隨用戶端數增加,停止時還得想辦法讓封鎖式 I/O 脫身
overlapped(非同步)型 用 FILE_FLAG_OVERLAPPED 建立,連線、讀取、寫入都以非同步方式發出 少量執行緒就能處理多個執行個體的完成

Microsoft 的官方範例把各執行個體的事件放進陣列,用 WaitForMultipleObjects 等待,是以單一執行緒處理多個連線的結構。這樣用戶端數與執行緒數就脫鉤了。規模再大一些,也可以接到 IOCP 或執行緒集區 I/O 上。10

非同步 I/O 本身的機制,請參閱同步 I/O 與非同步 I/O 的文章。

4.2 新寫的 .NET 實作,async/await 最直接

在 .NET 中可以使用 NamedPipeServerStream 的 WaitForConnectionAsync / ReadAsync / WriteAsync 搭配 async/await。新寫的實作若沒有特別理由,推薦這種非同步型。接待迴圈收到連線後交給個別的處理流程,然後回到下一個執行個體的接待。8

下面是那個接待迴圈的骨架。作為同一使用者的行程之間使用的例子,這裡指定了 PipeOptions.Asynchronous 與 PipeOptions.CurrentUserOnly。在 Windows 上,CurrentUserOnly 是限定為同一使用者且同一提升等級的指定。它不是把不同帳戶下的服務與 UI 接起來的例子。那種情況需要 5.2 的 ACL 設計。11

// C#: 接受多個用戶端的伺服器骨架
while (!token.IsCancellationRequested)
{
    var server = new NamedPipeServerStream(
        "MyCompany.MyApp.Control",
        PipeDirection.InOut,
        NamedPipeServerStream.MaxAllowedServerInstances,
        PipeTransmissionMode.Message,
        PipeOptions.Asynchronous | PipeOptions.CurrentUserOnly);

    try
    {
        await server.WaitForConnectionAsync(token);
    }
    catch
    {
        await server.DisposeAsync();        // 在連線建立前脫離時自行釋放
        throw;
    }
    _ = HandleClientAsync(server, token);   // 連線建立後所有權交給處理端
}

若在連線建立前發生例外,就由接待端釋放;連線建立之後,HandleClientAsync 端接手串流的所有權。後者不只包含讀寫,也包含結束時的釋放。

這段程式碼略去了通訊協定與處理常式本體,只是骨架。實際運作時還要管理分離出去的工作的例外與結束,並把第 3 章的續讀與大小上限、第 5、6 章的安全、第 7 章的中斷與回應確認組合進來。CurrentUserOnly 是不必自己寫 ACL 就能限定連線者的省事選項,但光靠它並不能構成給具特權服務用的完整設計。

5. 安全:伺服器端三點,用戶端一點

具名管道在安全上的好處,要正確設定之後才拿得到。特別是在「具系統管理員權限的服務 + 低權限的 UI 應用程式」這種代理(broker)結構中,管道本身就是權限的邊界。

5.1 伺服器端:拒絕不需要的遠端連線

當成本機 IPC 使用時,要在 CreateNamedPipe 上指定 PIPE_REJECT_REMOTE_CLIENTS。遠端用戶端會被自動拒絕,可以堵上「本來是給同一台 PC 的應用程式做的,結果從網路上也開得起來」這條路。7

5.2 伺服器端:用 ACL 收斂連線者與權利

把安全性描述元交給 SECURITY_ATTRIBUTES,明確寫出允許連線的使用者與群組。預設的 ACL 未必對該用途夠嚴格。2

這裡容易漏掉的,是給用戶端的寫入權限。在 ACL 中授予泛用寫入(GENERIC_WRITE / FILE_GENERIC_WRITE),就會連相當於 FILE_CREATE_PIPE_INSTANCE 的權利也一起帶上。於是被允許的用戶端自己就能建立同名的伺服器執行個體,把後續的連線攔走。2

讀寫要用所需的個別權利授予,不要把建立執行個體的權利交給用戶端。ACL 的設計不只是「允許誰」,還包括「允許做什麼」。

5.3 伺服器端:確認第一個執行個體沒有被搶先建立

惡意的行程搶先建立了同名的管道,用戶端就會連到那個假伺服器。伺服器只在建立第一個執行個體時指定 FILE_FLAG_FIRST_PIPE_INSTANCE,藉此保證自己是第一個。失敗就要懷疑被搶先,停止處理。5

這個旗標只用於占住名稱的第一條執行個體。給第二條以後的執行個體也加上,建立就會失敗。在多用戶端用的接待迴圈裡,請注意不要對所有的建立都重複同樣的指定。5

不過,這只是正規伺服器在啟動時偵測異常的機制,並不是用戶端對連線對象的驗證。正規服務不在時,攻擊者仍有先建立同名管道守在那裡的空間。

5.4 用戶端:決定讓伺服器借用權限到什麼程度

作為對假伺服器的防備,用戶端要把模擬等級壓到必要的最低限度。若設計上只允許確認身分,就在 CreateFile 上指定 SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION。這樣伺服器雖然能確認帳戶,卻無法借用其權限去做實際的存取。3

希望伺服器做的事 用戶端的方針
只確認連線者的身分 收斂到識別等級(SECURITY_IDENTIFICATION)
以用戶端權限開啟檔案等,進行實際存取 需要允許模擬等級(SECURITY_IMPERSONATION)。但要和「確認連到的是正規伺服器」配成一套

停在識別等級上,就做不了以用戶端權限進行實際存取的代理處理。反過來說,不確認連線對象就允許模擬,則有被假伺服器借走權限的危險。

原則是:只在能藉由服務的啟動保證、或連線後的相互驗證確認連線對象為正規一方時,才允許必要的模擬。不要以為有了 5.3 的搶先偵測,用戶端就不必確認。

6. 模擬的用法:失敗的要求不要執行

6.1 不是連上就模擬,而是讀到要求之後再模擬

伺服器確認用戶端的身分並以其權限處理,用的 API 是 ImpersonateNamedPipeClient。它的對象,是從該管道最後讀到的那則訊息的發送者的安全內容。所以要先讀取要求,之後再呼叫。4

模擬會套用到呼叫它的那條執行緒。在這個狀態下開啟檔案,能否存取就以用戶端的權限為基準判定。即使是具特權的服務,這也是「以委託者的權限執行被委託的操作」的機制。3

6.2 把讀取、成功確認、還原脈絡當成一整套

必要的順序是:讀取要求 → 嘗試模擬 → 確認成功 → 以用戶端權限操作 → 回到原本的脈絡。

使用模擬處理要求的流程伺服器從管道讀取要求,確認 ImpersonateNamedPipeClient 成功之後再以用戶端的權限進行操作,並用 RevertToSelf 回到自己的脈絡。模擬失敗時不執行要求而是拒絕伺服器用戶端伺服器用戶端失敗則不執行要求, 直接拒絕送出要求讀取要求ImpersonateNamedPipeClient以用戶端權限執行操作用 RevertToSelf 還原回覆結果

圖 3:模擬失敗就不要執行要求。即使成功,也要把「做完之後用 RevertToSelf 還原」算進這一連串處理裡。

不要在沒有檢查 ImpersonateNamedPipeClient 傳回值的情況下就進入要求的處理。失敗時並沒有切換到用戶端的權限,用的是伺服器行程本身的權限。伺服器若是具特權的帳戶,就會把用戶端本來不該做的操作也放行。Microsoft 的文件同樣明確寫著,失敗時不要執行用戶端的要求。4

工作結束後,要用 RevertToSelf 確實回到原本的脈絡。成功確認與善後兩者都要齊備。權杖、模擬等級、SeImpersonatePrivilege 等細節,在 Windows 模擬權杖的文章中有討論。

7. 實務上的陷阱:以「通訊會斷」為前提來設計

7.1 等待啟動與等待空位,要當成不同的失敗

連不上時,要分清是伺服器還沒建立管道,還是已建立的執行個體全都在使用中。69

狀態 用戶端的處理
管道不存在 把等待伺服器啟動納入考量,稍等一下再重試
ERROR_PIPE_BUSY 用 WaitNamedPipe 等待空位,再重試 CreateFile
連上了 進入通訊。必要時也設定 3.2 的讀取模式
用戶端連線的重試流程用 CreateFile 開啟管道,管道不存在就稍等一下再重試,所有執行個體都在使用中的 ERROR_PIPE_BUSY 就用 WaitNamedPipe 等待空位後再重試,成功則進入通訊成功管道不存在ERROR_PIPE_BUSY用 CreateFile 開啟結果如何?開始通訊稍等一下(伺服器尚未啟動)用 WaitNamedPipe 等待空位

圖 4:「不存在」和「額滿」是不同的狀態。兩者都要依狀態等待之後,再重試連線。

伺服器端的原則,是在用戶端啟動之前就用 ConnectNamedPipe 開始等候。即便如此,啟動順序的競爭仍然可能發生,所以用戶端也要內建重試。9

7.2 把中斷做成一般的處理路徑,而不是異常事態

對方的行程結束後,讀寫會以 ERROR_BROKEN_PIPE 之類失敗。這要當成通訊的日常來處理。

伺服器偵測到中斷後,用 DisconnectNamedPipe 把執行個體卸下,為下一次連線做準備。用戶端則重新連線。睡眠喚醒的文章中說明過的、重複執行也不會破壞狀態的「冪等重新連線」想法,在這裡同樣有效。

7.3 大訊息需要「續讀」與「上限」兩者

3.3 的 ERROR_MORE_DATA 是把訊息分次讀完的處理;而最大訊息大小是限制接收資料量的約定。請不要把這兩件事混為一談。

惡意的一方,或帶有錯誤的一方送來巨大的訊息時,無限續讀的實作會把記憶體耗盡。要在協定中訂下上限,超過就中斷連線。

7.4 區分寫入成功與對方處理完成

WriteFile 成功,並不代表對方的應用程式已經處理了資料。需要確知是否真的執行了的操作,要用回應訊息來確認。

另外,為了能對上回應是針對哪一個要求,要把要求與回應的對應關係寫進協定。把「送出去了」和「處理完了」分開,才談得上業務上的完成確認。

8. 總結:通道、資料、權限分開來決定

具名管道兼具和檔案 I/O 相同的易用性,以及 ACL 與模擬這套 Windows 安全模型,是同一台機器 IPC 的第一選擇。不過,做出一條通道,和做出一套安全、不易壞的協定,是兩件事。

連線與資料的設計,以「一個執行個體負責一個用戶端」為出發點。要求與回應型選訊息模式,已有封裝框架則選位元組模式,而訊息讀取在用戶端也要設定。分次讀取的因應與最大大小同樣必要。要接受多重連線的新 .NET 實作,非同步 I/O 加 async/await 最直接。

權限的設計,把拒絕遠端、明確的 ACL、第一個執行個體的保證、用戶端模擬等級的最小化配成一套。使用模擬時,先讀要求再呼叫,失敗就不執行,做完之後用 RevertToSelf 還原。

最後,請把啟動順序、中斷、訊息上限、回應確認寫成自己的協定。實務的重點不是「反正在同一台 PC 裡不會失敗」,而是先決定好即使通訊中斷,也能不越過對方權限地復原的形態,然後再動手實作。

相關文章

相關諮詢領域

小村軟體承接伴隨行程間通訊的設計與實作,例如服務與 UI 應用程式的分離、系統管理員權限的分離;也承接把既有 IPC(共用記憶體、自製 socket、COM 等)換成具名管道,以及具特權服務的管道通訊安全審查。從協定設計的討論階段開始諮詢也可以。

參考連結

  1. Microsoft Learn, Named Pipes. 關於具名管道是管道伺服器與一個以上管道用戶端之間的單向或雙向通道;所有執行個體共用同一個名稱但各自擁有獨立的緩衝區與控制代碼;以及可從本機與遠端行程使用。 ↩ ↩2 ↩3 ↩4

  2. Microsoft Learn, Named Pipe Security and Access Rights. 關於具名管道存取權限的組成;GENERIC_WRITE 包含 FILE_CREATE_PIPE_INSTANCE,因此給用戶端泛用寫入也會連伺服器執行個體的建立一起允許;以及資料的讀寫應以個別的存取權限授予。 ↩ ↩2 ↩3 ↩4

  3. Microsoft Learn, Impersonating a Named Pipe Client. 關於模擬讓伺服器執行緒可以在用戶端權限的範圍內運作;預設的模擬等級是 SecurityImpersonation;以及用戶端可在 CreateFile 時以 SECURITY_SQOS_PRESENT 旗標控制模擬等級(SECURITY_IDENTIFICATION 則只允許確認身分)。 ↩ ↩2 ↩3 ↩4

  4. Microsoft Learn, ImpersonateNamedPipeClient function (namedpipeapi.h). 關於伺服器端執行緒在從管道最後讀到的那則訊息的用戶端安全內容中開始模擬;完成後用 RevertToSelf 還原;以及模擬失敗仍繼續處理會在伺服器行程本身的(具特權的)內容中執行,因此必須檢查傳回值,失敗時不得執行用戶端的要求。 ↩ ↩2 ↩3

  5. Microsoft Learn, CreateNamedPipeW function (namedpipeapi.h). 關於管道方向(輸入、輸出、雙向)、位元組型與訊息型(PIPE_TYPE_BYTE / PIPE_TYPE_MESSAGE)及讀取模式(PIPE_READMODE_MESSAGE)、最大執行個體數(PIPE_UNLIMITED_INSTANCES)、由 FILE_FLAG_OVERLAPPED 啟用的非同步模式、由 FILE_FLAG_FIRST_PIPE_INSTANCE 提供的第一個執行個體保證,以及 WaitNamedPipe 用的預設逾時。 ↩ ↩2 ↩3 ↩4 ↩5

  6. Microsoft Learn, Named Pipe Client. 關於用戶端用 CreateFile 開啟管道;所有執行個體都在使用中時會得到 ERROR_PIPE_BUSY,用 WaitNamedPipe 等待空位;以及開啟的控制代碼預設是位元組讀取、封鎖式、非 overlapped,可用 SetNamedPipeHandleState 改為訊息讀取模式。 ↩ ↩2 ↩3 ↩4 ↩5

  7. Microsoft Learn, CreateNamedPipeW function (namedpipeapi.h). 關於兩種遠端用戶端模式:PIPE_ACCEPT_REMOTE_CLIENTS(接受遠端連線並以安全性描述元檢查)與 PIPE_REJECT_REMOTE_CLIENTS(自動拒絕遠端用戶端的連線)。 ↩ ↩2

  8. Microsoft Learn, How to: Use Named Pipes for Network Interprocess Communication (.NET). 關於以 NamedPipeServerStream / NamedPipeClientStream 建立連線與讀寫、以 PipeTransmissionMode.Message 做訊息單位的傳輸,以及以非同步方法處理多個用戶端。 ↩ ↩2

  9. Microsoft Learn, Named Pipe Operations. 關於以 ReadFileEx / WriteFileEx 進行 overlapped 操作、以 PeekNamedPipe 做不取走資料的讀取、在訊息型雙向管道上以一次呼叫完成要求送出與回應接收的 TransactNamedPipe,以及用戶端啟動前的封鎖式讀取可能造成競爭。 ↩ ↩2 ↩3

  10. Microsoft Learn, Named Pipe Server Using Overlapped I/O. 關於單一執行緒的伺服器以 overlapped 操作處理與多個用戶端同時連線的官方範例。關於以 WaitForMultipleObjects 等待各執行個體的 OVERLAPPED 結構與事件、推進已完成執行個體的狀態機的構造,以及以 GetOverlappedResult 確認擱置 I/O 是否完成。 ↩

  11. Microsoft Learn, PipeOptions Enum (System.IO.Pipes). 關於以 Asynchronous 啟用非同步 I/O,以及以 CurrentUserOnly 只允許與同一使用者(且同一提升等級)的行程連線。 ↩

共用相同標籤的最新文章。能以相近的主題延伸理解。

與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。

本文連結到以下服務頁面,歡迎從最接近的入口查看。

常見問題

整理諮詢這個主題時常見的問題。

具名管道和 TCP(localhost socket)該如何分別使用?
只要是同一台機器內的行程間通訊,具名管道就是第一選擇。原因在於安全模型。管道可以用 Windows 的安全性描述元(ACL)在作業系統層級控制「誰可以連線」,伺服器端還能用 ImpersonateNamedPipeClient 確認並借用連線對象的 Windows 帳戶。相對地,localhost 的 TCP 連接埠誰都能連上,必須自己另做一套驗證來確認對方是誰。反過來說,若日後很可能延伸為遠端通訊、要和其他作業系統上的行程對話、想沿用 gRPC 等既有協定資產,那麼以 TCP 為基礎的做法比較有利。這個判斷在行程間通訊選型表的文章裡也整理過。
位元組模式和訊息模式該用哪一個?
如果想把「一次寫入=一個意義單位」來處理,訊息模式(PIPE_TYPE_MESSAGE + PIPE_READMODE_MESSAGE)比較方便。接收端可以照發送端寫入的單位讀取,不必自己管理邊界。位元組模式和 TCP 一樣是「沒有斷點的位元組串流」,必須自己設計長度前綴之類的邊界(封裝框架)。如果承載的協定本身已有封裝框架(例如帶長度前綴的序列化格式),用位元組模式就沒有問題。需要留意的是,即使在訊息模式下,接收緩衝區小於訊息時仍會發生分次讀取(ERROR_MORE_DATA),這段處理還是得寫。另外,讀取模式是逐控制代碼設定的,CreateNamedPipe 設定到的只有伺服器端。用戶端必須在 CreateFile 之後用 SetNamedPipeHandleState 指定 PIPE_READMODE_MESSAGE。在 .NET 中,伺服器指定 PipeTransmissionMode.Message,用戶端則在連線後把 NamedPipeClientStream.ReadMode 設為 Message。
同時面對多個用戶端的伺服器該怎麼寫?
具名管道可以用同一個名稱建立多個執行個體,一個執行個體負責一個用戶端。結構有兩種。一種是為每個用戶端配置一條執行緒的同步型,寫起來直接,但會依用戶端數量消耗執行緒。另一種是用 FILE_FLAG_OVERLAPPED 做成非同步 I/O,以少量執行緒處理所有執行個體的 ConnectNamedPipe、ReadFile、WriteFile;Microsoft 的官方範例中也提供了單一執行緒處理多個執行個體的實作。用 .NET 的話,搭配 NamedPipeServerStream 的 WaitForConnectionAsync 與 async/await,可以把非同步型寫得像同步型一樣直接。新的實作若沒有特別理由,我推薦 .NET 的非同步型。
具名管道的安全至少該做哪些事?
有四點。第一,若不需要遠端連線,就指定 PIPE_REJECT_REMOTE_CLIENTS,明確拒絕經由網路的連線。第二,用 SECURITY_ATTRIBUTES 設定適當的 ACL,收斂可以連線的使用者與群組(預設的 ACL 對某些用途來說太寬鬆)。第三,建立第一個執行個體時指定 FILE_FLAG_FIRST_PIPE_INSTANCE,用來偵測同名管道被搶先建立的「名稱劫持」(第二個以後的執行個體不要加)。第四,只想讓伺服器確認身分的用戶端,應在 CreateFile 上指定 SECURITY_SQOS_PRESENT|SECURITY_IDENTIFICATION,避免假伺服器借用(模擬)自己的權限。在伺服器以用戶端權限進行實際存取的代理(broker)結構中必須允許模擬,因此這項限制要依「設計上是否讓伺服器借用權限」來取捨。
使用 ImpersonateNamedPipeClient 時有哪些需要留意的地方?
最重要的是檢查傳回值。模擬失敗卻繼續處理,之後的操作就會以伺服器行程本身(往往較高)的權限執行,本來不該允許用戶端做的操作也會通過。官方文件也明確寫著:失敗時不得執行用戶端的要求。另外,模擬是在「最後從管道讀到的那則訊息」的內容脈絡下進行的,所以務必先讀取了什麼之後再呼叫;工作結束後也要用 RevertToSelf 確實回到原本的脈絡。模擬相關的機制(權杖、模擬等級、SeImpersonatePrivilege)在模擬權杖的文章中有詳細說明。

作者檔案

本文作者的個人檔案頁面。

Go Komura

小村軟體有限公司 代表

以 Windows 軟體開發、技術諮詢與故障調查為中心,在難以重現的故障調查與既有資產仍在運作的專案上具有優勢。

回到部落格一覽