6K Views
November 30, 23
スライド概要
【オンライン】.NET Conf 後! C# Tokyo イベント - connpass
https://csharp-tokyo.connpass.com/event/292517/
このイベントで発表した資料です。
.NET8でgRPCのパイプ通信 を評価してみる &ついでに既存プロジェクトを.NET8へ移行 2023/11/30 須藤(suusanex)
自己紹介 ID:suusanex( connpass・Twitter・GitHub共通) 名前:須藤圭太 サイエンスパーク株式会社という独立系ソフトウェアベンダーに所属 4年ほど受託開発で、上流から下流まで全部を回す ここ10年ほどは、自社製品開発も担当 Windowsアプリ開発のネタが多い
概要 Windowsアプリでは、プロセス間で通信をしたい場合がある .NET 8でgRPCのパイプ通信が公式サポートされた ということで、このような話をしてみます 話の前提:プロセス間通信したいケースと、gRPC 標準のTCP通信をパイプ通信に移行 そのメリットと実測データ パイプ通信への移行の具体的な実装方法 既存のプロジェクトを.NET 8に移行
話の前提:プロセス間通信したい Windowsアプリでは、プロセス1つで完結できず、他のプロセスと通信したい ケースがある 例えば、バックグラウンドで動くWindowsサービスと、GUIを持つユーザー セッションアプリで通信したい場合など
話の前提: gRPCをプロセス間通信に使う gRPC:通信内容を専用構文で定義することで、クロスプラットフォームの通信がで きる(通常はTCP) 同一プラットフォーム内でプロセス間通信に使うだけでもメリットはある 最初から通信内容と通信方式が分離されていて、コード生成なども手厚い つまり他のプロセス間通信手法より手軽 TCP通信でもlocalhostが対象なら十分に速い
TCPからパイプ通信へ .NET 8で、gRPCでのパイプ通信を公式サポート 通信内容の定義はそのままに、通信方式をパイプ通信にできる
なぜパイプ通信? TCP通信と比べてのメリットは? 他ソフトとのポート番号の衝突を気にしなくて良い パイプは文字列なので衝突する可能性は低い ファイルと同様にACLでの権限管理ができる 「TCPでも十分に速い」とは言ったが、より高速な用途ではパイプが優れる
いくつかのケースでの実測1 「接続・送信・受信・切断」の繰り返し 100ms間隔で「接続・送信・受信・切断」を繰り返し、1回ごとの所要時間を計測 TCP:約2000ms/1回 パイプ:約1ms /1回 通信確立の所要時間の違いか、圧倒的な差が付く 数秒オーダーよりも頻繁に通信を繰り返す場合、パイプ通信が有効
いくつかのケースでの実測2 ストリーム通信を確立し、その上で通信を繰り返し 1,ループ1回の所要時間と、 2,送信元の通信データ作成から、送信先の通信データ取得までの所要時間を測定 10ms間隔でループ 結果は次ページ
いくつかのケースでの実測2 1,ループ1回の所要時間 TCP:約15.65ms パイプ:約15.55ms 2, 送信元の通信データ作成から、送信先の通信データ取得までの所要時間 TCP:約0.25ms パイプ:約0.15ms 通信繰り返しの所要時間はほぼ同じ 通信内容そのものの到達時間にはわずかに差がある(通信全体の所要時間から すると誤差に近い) よほど通信遅延のタイミングにシビアな用途でなければ、差は無いと言って良 い
速度のメリットが得られるケース まとめ ストリームを常時確立せずに単発の通信をしたく、かつ数秒間隔では性能不足 のケースにはぴったり ストリーム通信の場合はTCPと大差無い
実装方法は割と簡単 クライアント側
TCPの場合
var channel = GrpcChannel.ForAddress("http://localhost:50100/Connect1");
パイプの場合、パイプ接続処理をオプションへ渡すだけ
var channel = GrpcChannel.ForAddress("http://localhost/Connect1", new
{
HttpHandler = new
{
ConnectCallback = async (context, token) =>
{
var clientStream = new NamedPipeClientStream(/*パイプ名など、諸々のオプション*/);
await clientStream.ConnectAsync().ConfigureAwait(false);
return clientStream;
}
}
});
色々省略してるので、詳細はMS Docsをどうぞ
実装方法は割と簡単 サーバー側
Generic Host前提で
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureKestrel(options =>
{
//TCPの場合
options.Listen(IPAddress.Loopback, 50100, listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http2;
});
//パイプの場合
options.ListenNamedPipe(“(パイプ名)", listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http2;
});
});
サーバー側注意点 Windowsサービス(LocalSystem権限)からパイプを作ると、デフォルトではユー ザー権限の書き込み不可 ユーザーセッションからユーザー権限で操作したい場合は、Win32APIを使って パイプにACLを設定する必要あり CreateFileで開いてGet/SetSecurityInfoなど コードは複雑なので省略、興味のある人はこちらをどうぞ https://github.com/suusanex/sample_winservice_pipe_duplex_wcf_and_grpc/blob /master/gRPCWinServiceSample
.NET8より前の既存プロジェクトからの 移行は? WPF・クラスライブラリなど(C#) プロジェクトの「ターゲットフレームワーク」で「.NET 8.0」を選択するだけ。 もちろんファイルの<TargetFramework>を直接書き換えてもOK C++/CLI プロジェクトの「.NETターゲットフレームワーク」で「.NET 8.0」を・・・選択肢にない。 コンボボックスに、手入力で「net8.0」と書く。 Win UI 3(C#) 未サポート・・・ https://learn.microsoft.com/ja-jp/windows/apps/windows-app-sdk/stable-channel (一部引用)この問題のため、さらに .NET 8 がまだ正式にリリースされていないため、 Windows App SDK 1.4 では .NET 8 が正式にサポートされていません。 ただし、このバー ジョンの App SDK で .NET 8 のプレリリース バージョンを引き続きターゲットとしたい場 合は、次の手順をお勧めします。
まとめ PC内のプロセス間通信に、gRPCのパイプ通信を使うのは十分あり .NET8より前のプロジェクトでTCPを使っている場合の移行も割と容易 特に接続・切断を数秒より速く繰り返すケースではメリット大 当てはまる場合は.NET 8移行の動機にもなりそう WinUI 3、息してる?