.NETで名前付きパイプを試す(2) - クライアントからサーバにオブジェクトを送る

前回はクライアントからサーバに文字列を送ってみましたが、文字列だけでは少し寂しいので今回はオブジェクトを送信したいと思います。それでもまだ寂しいけど。

ichiroku11.hatenablog.jp

下準備

目的をクリアするために必要なクラスを準備していきます。

オブジェクトはバイト配列で送信することにして、まずはオブジェクトとバイト配列を変換するクラスを用意します。といってもヘルパー的なメソッドを持っている程度です。

// オブジェクトとバイト配列の変換
public class ObjectConverter<TObject> {
    private readonly IFormatter _formatter;

    public ObjectConverter(IFormatter formatter = null) {
        _formatter = formatter ?? new BinaryFormatter();
    }

    // オブジェクト=>バイト配列
    public byte[] ToByteArray(TObject obj) {
        using (var stream = new MemoryStream()) {
            _formatter.Serialize(stream, obj);
            return stream.ToArray();
        }
    }

    // バイト配列=>オブジェクト
    public TObject FromByteArray(byte[] bytes) {
        using (var stream = new MemoryStream(bytes)) {
            return (TObject)_formatter.Deserialize(stream);
        }
    }
}

バイト配列をストリームに読み書きするためにBinaryWriterとBinaryReaderを使いますが、ちょっとした拡張メソッドも用意しておきます。以下の方法がベストなのかわかりませんが、バイト配列の長さとバイト配列の順で書き込み、バイト配列の長さとバイト配列の順で読み込むようにしています。

// BinaryWriterの拡張メソッドを定義
public static class BinaryWriterExtensions {
    // オブジェクトの書き込み
    public static void WriteObject<TObject>(this BinaryWriter writer, TObject obj) {
        // オブジェクトをバイト配列に変換
        var converter = new ObjectConverter<TObject>();
        var bytes = converter.ToByteArray(obj);

        // 長さを書き込んでからバイト配列を書き込む
        writer.Write(bytes.Length);
        writer.Write(bytes);
    }
}

// BinaryReaderの拡張メソッドを定義
public static class BinaryReaderExtensions {
    // オブジェクトの読み込み
    public static TObject ReadObject<TObject>(this BinaryReader reader) {
        // 長さを読み込んでから、その長さ分のバイト配列を読み込む
        var length = reader.ReadInt32();
        var bytes = reader.ReadBytes(length);

        // バイト配列をオブジェクトに変換
        var converter = new ObjectConverter<TObject>();
        return converter.FromByteArray(bytes);
    }
}

送信する適当なオブジェクトを用意しておきます。結局は文字列という・・・サンプル力が弱いなと。

// メッセージ
[Serializable]
public class Message {
    public int Id { get; set; }
    public string Content { get; set; }

    public override string ToString() {
        return $@"{{ {nameof(Id)} = {Id}, {nameof(Content)} = ""{Content}"" }}";
    }
}

クライアント側

クライアントです。前回のサンプルで内側のusingの部分でStreamを閉じてしまっていることに気づきました。今回は閉じないようにしています。

// クライアント側
class Program {
    private static async Task Client() {
        using (var stream = new NamedPipeClientStream("testpipe")) {
            // サーバに接続
            await stream.ConnectAsync();

            // オブジェクトを送信(書き込み)
            // このusingでStreamを閉じないようにしておく
            using (var writer = new BinaryWriter(stream, Encoding.UTF8, true)) {
                Console.WriteLine($"Send");
                var message = new Message {
                    Id = 16,
                    Content = "あいうえお",
                };
                writer.WriteObject(message);
                Console.WriteLine($"Sent: {message}");
            }
        }
    }

    static void Main(string[] args) {
        Console.WriteLine("Client");

        Client().Wait();
    }
}

// 実行結果
/*
Client
Send
Sent: { Id = 16, Content = "あいうえお" }
*/

サーバ側

サーバです。

// サーバ側
class Program {
    private static async Task Server() {
        using (var stream = new NamedPipeServerStream("testpipe")) {
            // クライアントからの接続を待つ
            await stream.WaitForConnectionAsync();

            // オブジェクトを受信(読み込み)
            using (var reader = new BinaryReader(stream, Encoding.UTF8, true)) {
                Console.WriteLine("Receive");
                var message = reader.ReadObject<Message>();
                Console.WriteLine($"Received: {message}");
            }
        }
    }

    static void Main(string[] args) {
        Console.WriteLine("Server");

        Server().Wait();
    }
}

// 実行結果
/*
Server
Receive
Received: { Id = 16, Content = "あいうえお" }
*/

2つとも実行するとクライアント側からサーバ側にオブジェクトを送信できていることが確認できます。

名前付きパイプというよりオブジェクトのストリーム読み書きのサンプルみたいになった気もしますが、今回はこんなところで。