.NET 8のプロセス間通信には、gRPCのパイプ通信が優秀なようです



# 概要

Windowsアプリを作成していると、PC内のプロセス同士で通信をしたい場合があります。その実装方法として、.NET 8で公式サポートされたgRPCのパイプ通信が優秀なようです。
従来のgRPCのTCP通信との比較や移行について、次の流れで紹介します。
（以下、プロセス間通信をIPCと略記します）

 1. 話の前提：IPCを使いたいケースと、gRPCでのIPC
 1. 標準のTCP通信をパイプ通信に移行
 1. そのメリットと実測データ

# 結論

説明が長くなるので結論だけ先に書いておくと、こんな感じになります。

 *  速度メリットとしては、ストリームを作らずに接続・切断を繰り返すケースではかなり速い。速度以外のメリットもある。デメリットは無さそう。
 *  既存のgRPC（TCP通信）からの移行も容易
 *  gRPC自体の使いやすさは変わらずにメリットを得られるので、.NET 8でIPCをするのなら第一選択肢だと思う

具体的なコードの説明については、[次の記事](https://qiita.com/spc_ksudoh/items/b998a0decc772b0e4785)にあります。

# 話の前提

## IPCを使いたいケース

Windowsアプリでは、プロセス1つで機能を完結できず、他のプロセスと通信したいケースがあります。主に、動作するセッションが異なる場合が多いと思います。

例えば次の図のように、バックグラウンドで動くWindowsサービスと、GUIを持つユーザーセッションアプリで通信したい場合などです。

![.NET8でgRPCのパイプ通信を評価してみる＆ついでに既存プロジェクトを.NET8へ移行_図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616143/bbe862f1-7818-367e-5ea7-f31a0284057e.png)

## gRPCでのIPC

この用途で、gRPCを使うことができます。gRPCとは、専用の構文で通信内容を定義しておくことで、クロスプラットフォームの通信ができる方式です。かなり有名だと思うので、ここでは詳しくは触れません。


![.NET8でgRPCのパイプ通信を評価してみる＆ついでに既存プロジェクトを.NET8へ移行_図-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616143/810ccadf-b062-f1e6-da8b-7419ab838146.png)

そうした方式ですが、同一プラットフォーム内でIPCする今回の用途でも、十分にメリットがあります。通信内容と通信方式が分離されていて、かつ通信方式部のコード生成も手厚いことから、他のIPC方式よりもかなり手軽に使えます。速度としても、localhostへのTCP通信となるので、十分に速いです。

# 標準のTCP通信をパイプ通信に移行

## パイプ通信にできる

TCPでも十分に速いと書きましたが、とはいえWindows内のIPCであればTCP通信よりもパイプ通信の方が速いはずです。.NET 8で、パイプ通信が公式サポートされました。gRPCの特徴から、通信内容の定義はそのままにして、通信方式だけをパイプ通信へ変えることが出来ます。


![.NET8でgRPCのパイプ通信を評価してみる＆ついでに既存プロジェクトを.NET8へ移行_図-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616143/5d4b181c-38fa-d2d2-a0e8-f2497731a361.png)

## パイプ通信にするメリット

TCP通信と比べてのメリットは、このようなものがあります。

1. 他ソフトとのポート番号の衝突を気にしなくていい
    * パイプは文字列なので衝突する可能性は低い
1. ファイルと同様に、WindowsのACLでの権限管理ができる 
1. 「TCPでも十分に速い」とは言ったが、より高速な用途ではパイプが優れるはず

デメリットは標準のTCPよりも実装に少し手間が増えるくらいで、他は特に見当たりません。

# そのメリットと実測データ

パイプのほうが速いはずだと言ってきましたが、具体的にどういうケースでどれくらい速いのか。実測してみました。

## 接続・通信・切断を繰り返すケース

100ms間隔で「接続・送信・受信・切断」を繰り返し、1回ごとの所要時間の中央値を計測しました。

![.NET8でgRPCのパイプ通信を評価してみる＆ついでに既存プロジェクトを.NET8へ移行_図-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616143/c4f12ba6-8708-530d-f8c6-934186fb3770.png)

結果は・・・

 * TCP：約2000ms/1回
 * パイプ：約1ms /1回

おそらくTCPの接続のハンドシェイク等の時間かと思いますが、かなり大きな差が付きました。数秒オーダーよりも頻繁に通信を繰り返す場合、パイプ通信が有効と言えます。

## ストリーム通信を確立し、その上で通信を繰り返すケース

ストリーム通信を確立した上で、その上で10ms間隔での通信を繰り返し、次の内容を計測しました。

1. ループ1回ごとの所要時間の中央値
1. 送信元の通信データ作成から、送信先の通信データ取得までの所要時間の中央値

![.NET8でgRPCのパイプ通信を評価してみる＆ついでに既存プロジェクトを.NET8へ移行_図-4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616143/53802aeb-030e-7555-d80f-b28416a16fcc.png)

結果は・・・

1. ループ1回の所要時間
    * TCP：約15.65ms
    * パイプ：約15.55ms
1. 送信元の通信データ作成から、送信先の通信データ取得までの所要時間
    * TCP：約0.25ms
    * パイプ：約0.15ms

通信繰り返しの所要時間はほぼ同じで、通信内容そのものの到達時間にはわずかに差がありました。と言っても、通信全体の所要時間からすると誤差に近いです。

よほど通信遅延のタイミングにシビアな用途でなければ、差は無いと言って良いと思います。


# まとめ

PC内でのプロセス間通信について、gRPCのパイプ通信はTCP通信よりも優れた選択肢のようです。速度面では悪くとも同等、ストリームを使わずに接続・通信・切断を繰り返すようなケースであれば大きく優れます。それ以外の運用上のメリットもあります。また、すでにTCP通信で実装している場合にも、通信内容の実装に影響することなくパイプ通信への置き換えができます。

以上より、新規実装・既存の置き換えのどちらでも、gRPCのパイプ通信は第一選択肢になると思います。これ自体が .NET 8への移行の動機にもなるかもしれません。

具体的なコードの説明については、[次の記事](https://qiita.com/spc_ksudoh/items/b998a0decc772b0e4785)にあります。