-
Notifications
You must be signed in to change notification settings - Fork 0
MS_InterProcessCommunication
- 戻る(その他、開発の色々)
プロセス間通信についてまとめた。
補足(IPC の選択軸): 手段が多いので、
**先に「何で選ぶか」**を整理しておくと読みやすい。
軸 内容 同一マシンか、ネットワーク越しか 共有メモリ・パイプは同一マシン限定。TCP は跨げる 親子関係があるか 匿名パイプは親子限定。名前付きなら無関係でもよい 速度 共有メモリ > パイプ > ローカル TCP データ量 大量・高頻度なら共有メモリ/小さなメッセージならパイプ 方向 一方向か双方向か プラットフォーム Windows 専用か、Linux でも動くか ★ 【選択の目安】 親プロセスが子を起動して制御したい → 匿名パイプ / 標準入出力 常駐サービスと GUI が通信する(同一 PC) → 名前付きパイプ 大量データを高速に共有する → 共有メモリ 別マシンにも展開する可能性がある → 【TCP / HTTP / gRPC】★ Linux コンテナでも動かす → TCP / Unix ドメイン ソケット最後の 2 つが現在では重要である。
「同一マシン前提の IPC」はコンテナ化・クラウド移行で足枷になるため、
迷ったら TCP(あるいは HTTP / gRPC)にしておく方が寿命が長い。
- 共有メモリ、パイプ(名前付き/匿名)、TCP, UDP などがある。
- 子プロセスとの通信なら匿名パイプを使用すると良い。
- 内部的には、ページングファイルに支持されたメモリマップド・ファイルを使用する。
- 実装については、下記の「SharedMem.xaml.cs」を参照のこと。
ただし、Touryo.Infrastructure.Public.Util.SharedMemory に依存している。
移行メモ(「ページングファイルに支持された」): 原文の表現は
**「ページ ファイルによってバックされた(backed by the page file)」**の
訳と読める。内容は正確である。【メモリ マップト ファイルの 2 形態】 ① 【ファイルにバックされる】 実ファイルをメモリに写像する → 大きなファイルの一部だけを読み書きできる ② 【ページ ファイルにバックされる】★ 共有メモリはこちら 実ファイルを持たず、名前だけを付ける → プロセス間で「名前」を共有すれば、同じ領域を見られる → プロセスが全部終了すると消える// .NET での共有メモリ(System.IO.MemoryMappedFiles) // 書く側 using var mmf = MemoryMappedFile.CreateOrOpen("MySharedMem", 1024); using var acc = mmf.CreateViewAccessor(); acc.Write(0, 12345); // 読む側(別プロセス) using var mmf2 = MemoryMappedFile.OpenExisting("MySharedMem"); using var acc2 = mmf2.CreateViewAccessor(); Console.WriteLine(acc2.ReadInt32(0));共有メモリ固有の注意点:
① 【同期を自前で行う必要がある】★ 共有メモリ自体は「同時に書いたらどうなるか」を保証しない → Mutex / Semaphore(名前付き)で排他する ② 【セッション分離】([(ログオン)セッション](MS_LogonSession)) サービス(Session 0)とユーザー アプリで名前空間が違う → "Global\\MySharedMem" と Global 名前空間を明示する → [Windowsの外字](MS_WindowsGaiji) の Session 0 問題と同根 ③ 【クロス プラットフォーム】 名前付き共有メモリは .NET Core の Linux では未対応 → Linux でも動かすなら、この方式は選べない
-
名前付きパイプ
-
名前付きパイプを使用して、プロセス間通信を行う場合、
パイプ・オブジェクトの始点と終端を2プロセス間で持つ。 -
このとき、パイプ・オブジェクトを ID のようなもので
識別するのが、名前付きパイプになる。 -
従って、名前付きパイプの場合、
2プロセスがパイプ名を共有している必要がある。 -
詳細については、下記の「NdPipe.xaml.cs」を参照のこと。
-
-
匿名パイプ
-
一方、匿名パイプは、名前が無いので、名前で共有できない。
-
その代りに、親プロセス側から、子プロセス側に、ハンドラを複製して渡す。
(従って、匿名パイプは、親子プロセス間通信での利用が一般的になる) -
このハンドラの複製に、
- Windows OS では、DuplicateHandle 関数を使用し、
- Unix 系 OS では、fork 関数を使用する。
-
参考
-
[ C言語 ] プロセスの生成 ( fork ) と パイプによる
プロセス間通信 ( pipe ) – 行け!偏差値40プログラマー
http://hensa40.cutegirl.jp/archives/1032 -
親プロセス・子プロセス間通信 - Qiita
https://qiita.com/SoyaTakahashi/items/f28d0b5ef404afd339fa -
.NET では、このハンドラの複製に、
https://github.com/OpenTouryoProject/SampleProgram/blob/master/Other/PipesFamilyHouse/AnonymousPipe/Parent/Program.cs#L77
HandleInheritability.Inheritable を使用している。
これは、内部(Win32)的に、DuplicateHandle 関数と思われる。
-
-
補足(原文の推測は概ね正しい): 「内部(Win32)的に、
DuplicateHandle 関数と思われる」という推測は、方向として正しい。
ただし、もう少し正確にしておく。【HandleInheritability.Inheritable の実体】 ① ハンドルを作る際、SECURITY_ATTRIBUTES.bInheritHandle = TRUE を指定 → 「このハンドルは子に継承できる」という印を付ける ② CreateProcess を bInheritHandles = TRUE で呼ぶ → 【OS が継承可能なハンドルをまとめて複製する】★ → DuplicateHandle を明示的に呼ぶわけではないが、 OS 内部で行われるのは実質的に同じ複製処理である
forkとの違いも補足しておく。【Unix の fork】 プロセスを丸ごと複製する → ファイル記述子も【自動的に引き継がれる】 → パイプを作ってから fork するだけでよい 【Windows の CreateProcess】 fork に相当するものがない(新規プロセスを作る) → ハンドルを【明示的に継承させる】必要がある → さらに、子プロセスに「どのハンドルか」を教える必要がある ★ (通常はコマンドライン引数で渡す)// .NET での匿名パイプ(親) using var pipe = new AnonymousPipeServerStream( PipeDirection.Out, HandleInheritability.Inheritable); var psi = new ProcessStartInfo("Child.exe", pipe.GetClientHandleAsString()); // ← ハンドル値を引数で渡す ★ var child = Process.Start(psi); using var sw = new StreamWriter(pipe) { AutoFlush = true }; sw.WriteLine("hello"); pipe.DisposeLocalCopyOfClientHandle(); // ← これを忘れると子が終了を検知できない
DisposeLocalCopyOfClientHandle()の呼び忘れは
典型的な不具合で、
子プロセスが「まだ書き込む人がいる」と誤認して待ち続ける。
- コネクションレス型の通信
- 実装については、下記の「UDP.xaml.cs」を参照のこと。
- コネクション型の通信
- 実装については、下記の「TCP.xaml.cs」を参照のこと。
補足(同一マシンで TCP を使う選択): 「プロセス間通信」の文脈で
TCP を挙げるのは一見遠回りに見えるが、
現在はむしろ有力な選択肢である。【ループバック TCP の利点】 ・【同一マシンでも別マシンでも同じコードで動く】★ → 後からサービスを別サーバに移せる → コンテナに分割できる ・OS を問わない(Windows / Linux / macOS) ・既存のツール(telnet、curl、Wireshark)で調査できる ・HTTP / gRPC といった上位プロトコルが使える 【欠点】 ・共有メモリ・パイプより遅い(とはいえ十分速い) ・ポート番号の管理が要る(競合、ファイアウォール)Unix ドメイン ソケットは、その中間である。
// .NET 5 以降、Windows でも Unix ドメイン ソケットが使える var ep = new UnixDomainSocketEndPoint("/tmp/myapp.sock"); using var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified); socket.Connect(ep);【Unix ドメイン ソケットの位置付け】 ・TCP と同じ API(Socket)で書ける ・ネットワーク スタックを通らないので【速い】 ・ファイル システムの権限で【アクセス制御できる】★ ・Windows 10 / Server 2019 以降で利用可能 → 同一マシン限定で良いなら、名前付きパイプの代替になる
-
DCE/RPC
- DCOM
- .NET Remoting
-
Web サービス
- XML-RPC
- JSON-RPC
-
WCF (Windows Communication Foundation)
補足(この一覧の現況): 挙げられているものは、
ほぼすべて非推奨または終了している。
技術 現況 DCOM 動くが新規採用しない。ファイアウォール越えが困難 .NET Remoting 非推奨。.NET Core 以降存在しない XML-RPC 歴史的 JSON-RPC 一部で現役(Language Server Protocol 等)★ WCF .NET Core 以降なし(クライアントのみ部分対応/CoreWCF が受け皿) 現在の選択肢:
手段 向く場面 HTTP + JSON(REST) 最も汎用的。どこからでも呼べる gRPC サービス間通信。高速・型安全・双方向ストリーミング ★ メッセージ キュー(Service Bus、RabbitMQ) 非同期・疎結合。落ちても失われない SignalR サーバからクライアントへのプッシュ 名前付きパイプ / UDS 同一マシン内の高速通信 【.NET Remoting / WCF からの移行先】 社内サービス間、性能重視 → 【gRPC】 Web ブラウザからも呼ぶ → REST(+ OpenAPI) 非同期・信頼性重視 → メッセージ キュー 既存の SOAP 契約を残したい → CoreWCF// gRPC は .NET でファースト クラスに扱える var channel = GrpcChannel.ForAddress("https://localhost:5001"); var client = new Greeter.GreeterClient(channel); var reply = await client.SayHelloAsync(new HelloRequest { Name = "world" });gRPC は Unix ドメイン ソケットや名前付きパイプ上でも動くため、
**「同一マシンでは高速に、別マシンでは TCP で」**という
使い分けが 1 つの API でできる。
補足(
WM_COPYDATAによる IPC): ウィンドウ メッセージも
古典的な IPC 手段である。【WM_COPYDATA】 SendMessage で【任意のバイト列を別プロセスに渡せる】 → OS がプロセス境界を越えてデータをコピーしてくれる 【制約】 ・受信側に【ウィンドウとメッセージ ループ】が要る ・【同期呼び出し】(SendMessage)なので、相手が固まると自分も固まる ★ ・UIPI(整合性レベル)により、 管理者権限プロセス → 標準権限プロセスへは届くが、逆は弾かれる → ChangeWindowMessageFilterEx で明示的に許可が要る ・Session 0 分離により、サービスからは届かない「既に起動しているインスタンスに引数を渡す」(多重起動の抑止)
といった用途では今も使われるが、
新規に汎用の IPC として選ぶ理由はない。
- UNIX ドメインソケット
- POSIX メッセージキュー
補足(.NET から POSIX の IPC を使う): .NET のクロスプラットフォーム対応
により、Linux 上で .NET を動かす場面が増えたため補足する。
POSIX の機構 .NET からの利用 UNIX ドメイン ソケット UnixDomainSocketEndPoint(標準。Windows でも可)★POSIX メッセージ キュー 標準 API なし(P/Invoke か、別手段) POSIX 共有メモリ MemoryMappedFileはファイル バックのみ(名前付きは不可)シグナル PosixSignalRegistration(.NET 6 以降)★パイプ(FIFO) FileStreamで開ける// .NET 6 以降:SIGTERM を受けて綺麗に終了する(コンテナで必須) using var reg = PosixSignalRegistration.Create(PosixSignal.SIGTERM, ctx => { ctx.Cancel = true; // 既定の終了処理を止める _shutdown.Cancel(); // 自前の後始末を行う });【コンテナ運用での重要性】 ・Kubernetes は停止時に【SIGTERM】を送る ・受け取って処理中のリクエストを捌き切ってから終了する → これをしないと【処理中のリクエストが切れる】★ ・ASP.NET Core の汎用ホストは【既定で対応済み】 (IHostApplicationLifetime)
- Category:プロセス間通信
https://ja.wikipedia.org/wiki/Category:%E3%83%97%E3%83%AD%E3%82%BB%E3%82%B9%E9%96%93%E9%80%9A%E4%BF%A1- プロセス間通信
https://ja.wikipedia.org/wiki/%E3%83%97%E3%83%AD%E3%82%BB%E3%82%B9%E9%96%93%E9%80%9A%E4%BF%A1 - 共有メモリ
https://ja.wikipedia.org/wiki/%E5%85%B1%E6%9C%89%E3%83%A1%E3%83%A2%E3%83%AA - パイプ (コンピュータ)
https://ja.wikipedia.org/wiki/%E3%83%91%E3%82%A4%E3%83%97_(%E3%82%B3%E3%83%B3%E3%83%94%E3%83%A5%E3%83%BC%E3%82%BF) - UNIXドメインソケット
https://ja.wikipedia.org/wiki/UNIX%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3%E3%82%BD%E3%82%B1%E3%83%83%E3%83%88 - POSIX 1003.1b > Realtime (XSI Option) > _POSIX_MESSAGE_PASSING
https://ja.wikipedia.org/wiki/POSIX_1003.1b
- プロセス間通信
- プロセス間通信 - UnixClassWiki
https://uc2.h2np.net/index.php/%E3%83%97%E3%83%AD%E3%82%BB%E3%82%B9%E9%96%93%E9%80%9A%E4%BF%A1 - プロセス間通信の比較 - ka2yanの日記
https://d.hatena.ne.jp/ka2yan/20090327
https://github.com/OpenTouryoProject/SampleProgram/tree/master/Other/InterProcComm
https://github.com/OpenTouryoProject/SampleProgram/tree/master/Other/PipesFamilyHouse
- AnonymousPipe(匿名パイプによる親子プロセス間通信)
- StdIOAndPipe(標準入出力による親子プロセス間通信)
補足(標準入出力による IPC は今も現役):
PipesFamilyHouseの
StdIOAndPipe(標準入出力)方式は、
現在最もよく使われる IPC の 1 つである。【標準入出力 IPC が使われている例】 ・Language Server Protocol(VS Code ↔ 言語サーバ)★ ・Model Context Protocol(MCP) ・多くの CLI ツールの連携(パイプで繋ぐ) → [自作CUI(CLI)の話](MS_BuildingYourOwnCLI) 参照var psi = new ProcessStartInfo("child.exe") { RedirectStandardInput = true, RedirectStandardOutput = true, RedirectStandardError = true, // ← stderr も必ず読む ★ UseShellExecute = false, StandardOutputEncoding = System.Text.Encoding.UTF8, // ← 文字コード };【定番の落とし穴】 ① 【stderr を読まないとデッドロックする】 子が stderr に大量出力 → バッファが埋まる → 子が止まる → 親は stdout を待ち続ける → 相互に停止 ★ → 必ず両方を【非同期で読む】(BeginOutputReadLine 等) ② 【文字コード】 既定はコンソールのコードページ(日本語なら CP932) → UTF-8 を明示する([アプリケーションのUnicode化](MS_ApplicationUnicodeMigration)) ③ 【終了を待つ順序】 WaitForExit の前に、出力の読み切りを完了させる
- パイプ(.NET)
https://learn.microsoft.com/ja-jp/dotnet/standard/io/pipe-operations - メモリ マップト ファイル
https://learn.microsoft.com/ja-jp/dotnet/standard/io/memory-mapped-files - .NET での gRPC の概要
https://learn.microsoft.com/ja-jp/aspnet/core/grpc/ - gRPC でのプロセス間通信
https://learn.microsoft.com/ja-jp/aspnet/core/grpc/interprocess
Tags: 移行, プログラミング, その他、開発の色々, .NET開発
このWikiは「Open棟梁Project」,「OSSコンソーシアム 開発基盤部会」によって運営されています。