名前付きパイプでプロセス間の双方向通信を行う(改良版)

パイプ関連記事 もくじ
https://tera1707.com/entry/2022/02/06/144447#Pipe

やりたいこと

以前の記事で勉強した、プロセス間通信につかう「パイプ」のためのサンプルプログラムを改良したい。
(というか、元のサンプルプログラムがいろいろまずそうだったので直したい)

改良版のサンプルプログラム

using System.IO.Pipes;
using System.Security.Principal;

namespace PipeJikkenKai
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Pipe Start!");

            var pipeName = "MyPipe1";

            CreatePipeServerAsync(pipeName);

            // パイプサーバーが立ち上がるまで待つ
            Thread.Sleep(500);

            Task.Run(async () => await CreateClientAsync(pipeName, "送る文字列1")).Wait();
            Task.Run(async () => await CreateClientAsync(pipeName, "送る文字列2")).Wait();
            Task.Run(async () => await CreateClientAsync(pipeName, "送る文字列3")).Wait();

            Console.ReadLine();
        }

        // パイプから受信を行う処理
        // 送信(クライアント)側が切断(close)すると、IOExceptionが来るので再度パイプサーバー作成しなおしする。
        public static Task CreatePipeServerAsync(string pipeName)
        {
            Console.WriteLine(DateTime.Now.ToString("HH:mm:ss.fff") + " " + "Please enter a message, and then press Enter.");

            var numThreads = 10;

            return Task.Run(async () =>
            {
                NamedPipeServerStream? pipeServer = null;

                while (true)
                {
                    try
                    {
                        // 同じパイプに対しての接続は1件まで
                        using (pipeServer = new NamedPipeServerStream(pipeName, PipeDirection.InOut, numThreads))
                        {
                            // クライアントの接続待ち
                            Console.WriteLine($"受信:クライアントの接続待ち開始");
                            await pipeServer.WaitForConnectionAsync();

                            using (var reader = new StreamReader(pipeServer))
                            {
                                // 受信待ち
                                Console.WriteLine($"受信:読み込み開始");
                                var recvString = await reader.ReadLineAsync();

                                Console.WriteLine($"受信:受信文字列:{recvString ?? "null"}");
                            }
                        }
                    }
                    catch (IOException ofex)
                    {
                        // クライアントが切断
                        Console.WriteLine("受信:クライアント側が切断しました");
                        Console.WriteLine(ofex.Message);
                    }
                    finally
                    {
                        Console.WriteLine("受信:パイプ終了");
                    }
                }
            });
        }

        // パイプに対して送信を行う処理
        // 1件送信するごとに、パイプ接続→切断するタイプ。
        public static async Task CreateClientAsync(string pipeName, string writeString)
        {
            await Task.Run(async () =>
            {
                NamedPipeClientStream? pipeClient = null;

                using (pipeClient = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, PipeOptions.None, TokenImpersonationLevel.Impersonation))
                {
                    await pipeClient.ConnectAsync();

                    using (var writer = new StreamWriter(pipeClient))
                    {
                        await writer.WriteLineAsync(writeString);
                        writer.Flush();

                        Console.WriteLine(" 送信完了");
                    }
                }

                Console.WriteLine(" 送信:パイプ終了");
            });
        }
    }
}

出力

Pipe Start!
00:15:01.158 Please enter a message, and then press Enter.
受信:クライアントの接続待ち開始
受信:読み込み開始
 送信完了
 送信:パイプ終了
受信:受信文字列:送る文字列1
受信:パイプ終了
受信:クライアントの接続待ち開始
受信:読み込み開始
 送信完了
 送信:パイプ終了
受信:受信文字列:送る文字列2
受信:パイプ終了
受信:クライアントの接続待ち開始
受信:読み込み開始
 送信完了
受信:受信文字列:送る文字列3
 送信:パイプ終了
受信:パイプ終了
受信:クライアントの接続待ち開始

今回気づいた点

Microsoftの、Pipeサーバーのサンプルコードに、「IOException(例外メッセージ= pipe is broken. )」は、

  • パイプが壊れたとき
  • 接続を切断したとき

に起きるとのこと。→ こちら参照

「pipe is broken.」という、通信に重大なエラーが起きて何かが壊れた!的メッセージがでるが、実は単にサーバーがクライアントから切断されただけでもこの例外が出る様子。
ログ等でこれが出てても、重大な障害が起きた、などと変な勘違いをしないほうがよさそう。

参考

NamedPipeServerStream クラス

https://learn.microsoft.com/ja-jp/dotnet/api/system.io.pipes.namedpipeserverstream?view=net-6.0

NamedPipeClientStream クラス

https://learn.microsoft.com/ja-jp/dotnet/api/system.io.pipes.namedpipeclientstream?view=net-6.0

以前の記事。クライアントが接続を切ったときにIOException を見ずに別の例外を見てる等、うまくいってない部分があるので、以前の記事のサンプルは使わないこと。

https://qiita.com/tera1707/items/c1cee4a810bf859a88a0

参考書

WinUI3

WinUI3でアプリを作ろうと思ったときのとっかかりによかった。 msdocsに書いてある情報を、体系的に、順番に読みたいな、というときによいかも。(ただし英語)
この本で分からなかった、かゆいところに手が届かなかった部分を私は記事にしてる感じ。

C#①

表紙に書いてある通り、教科書として最適。 これからC#を勉強したいけど、ネットだけで勉強するのは効率が悪いから体系的に学べる本が欲しいときや、 ちょっとC#を勉強してコード書けるようになったけど、もう少し広く深く知りたいなというときによいと思う。
私は仕事で触れるコードを軸に、基本ネットで断片的にC#を学んだので、その知識の隙間を埋めて枝葉を広げるためにとても分かりやすかった。

C#②

C#の文法的に色々できるのは分かったが、いざ実装するときに、わかったことを使ってどう実装すればいいのか?と悩んだときに指針になりそうな本。
「プロパティ等の名前の付け方、どうすればいい?」「情報をクラス外部に見せるときに、プロパティにすべき?メソッドにすべき?」「異常だと判定したいとき、どんなときにどんな例外をスローすべき?」などなど、勉強になる部分が山ほどあった。
私のように「コードは書くけどこれであってるのか自信がない、レビューで指摘されるのが嫌だ、実装時の(心の)よりどころが欲しい」という人に最適。