.NETでUDPを使って通信する(UdpClientを使う)

前回はTCPを使って通信してみたので今回はUDPを使って通信するサンプルを書いてみました。

ichiroku11.hatenablog.jp

.NETでUDPを使って通信するにはUdpClientを使います。Socketを使う方法はまたそのうち…というフラグ。

UdpClient クラス (System.Net.Sockets)

UDPコネクションレスTCPよりシンプルなプロトコルなので、UdpClientを使ったコードもTCPに比べるとシンプルでわかりやすいかなと思います。TcpListenerのようなクラスもないですし、NetworkStreamも使いません。言ってしまえばバイト配列を送信する、受信するだけです。

クライアント

UDPのサンプルもHTTPのようにクライアントとサーバでリクエストとレスポンスのメッセージをやりとりする動きにしました。まずはクライアントから見ていきましょう。

クライアントの動き

  1. サーバにリクエストを送信
  2. サーバからレスポンスを受信

書くまでもない感じですね。

クライアントのサンプルコード

SendAsyncで一方的にリクエストを送信して、ReceiveAsyncでレスポンスを受信しています。

// クライアント
public class Client<TRequest, TResponse> {
    // 送信先のエンドポイント
    private readonly IPEndPoint _endpoint;

    public Client(IPEndPoint endpoint) {
        _endpoint = endpoint;
    }

    // クライアントを実行する
    // リクエストを送信してレスポンスを受信する
    public async Task<TResponse> SendAsync(TRequest request) {
        // UdpClientを作成するときにはエンドポイントを指定しない
        using (var client = new UdpClient()) {
            // 1. リクエストを送信
            // (送信先のエンドポイントを指定して)
            Console.WriteLine($"Client send {nameof(request)}: {request}");
            var requestBytes = new ObjectConverter<TRequest>().ToByteArray(request);
            await client.SendAsync(requestBytes, requestBytes.Length, _endpoint);

            // 2. レスポンスを受信
            var result = await client.ReceiveAsync();

            // ここで受信した結果のリモートエンドポイントが送信先かをチェックした方がいい気がする

            var responseBytes = result.Buffer;
            var response = new ObjectConverter<TResponse>().FromByteArray(responseBytes);
            Console.WriteLine($"Client receive {nameof(response)}: {response}");

            return response;
        }
    }
}

レスポンスを受信したタイミングで、送信したエンドポイントから返ってきたレスポンスかどうか、UdpReceiveResult.RemoteEndPointを確認した方がいい気がします。上記のサンプルでは書いていませんが。

サーバ

続いてサーバ。

サーバの動き

  1. クライアントからリクエストを受信する
  2. リクエストを処理してレスポンスを作る
  3. クライアントにレスポンスを送信する

サーバのサンプルコード

2と3は複数のクライアントからのリクエストを並列に処理することを考えてTaskを使いました。ただサンプルではTaskを管理していません。投げっぱなしは良くないので管理する必要があるとは思いますが、どうするのがいいのかなと。

// サーバ
public class Server<TRequest, TResponse> {
    // リクエストを受信するエンドポイント
    private readonly IPEndPoint _endpoint;

    public Server(IPEndPoint endpoint) {
        _endpoint = endpoint;
    }

    // サーバを実行する
    public async Task RunAsync(Func<TRequest, TResponse> processor) {
        // リクエストを受信するエンドポイントを指定してUdpClientを作成
        using (var client = new UdpClient(_endpoint)) {
            while (true) {
                // 1. リクエストを受信
                var result = await client.ReceiveAsync();
                var requestBytes = result.Buffer;
                var request = new ObjectConverter<TRequest>().FromByteArray(requestBytes);
                Console.WriteLine($"Server receive {nameof(request)}: {request}");

                var sender = Task.Run(async () => {
                    // 2. リクエストからレスポンスを作成
                    var response = processor(request);

                    // 3. リクエストの送信元にレスポンスを送信
                    Console.WriteLine($"Server send {nameof(response)}: {response}");
                    var responseBytes = new ObjectConverter<TResponse>().ToByteArray(response);
                    await client.SendAsync(responseBytes, responseBytes.Length, result.RemoteEndPoint);
                });

                // Taskの管理やエラー処理は省略
            }
        }
    }
}

クライアントとサーバを実行してみる

そして実行してみると。

class Program {
    // サーバの作成
    public static Server<string, string> Server(IPEndPoint endpoint)
        => new Server<string, string>(endpoint);

    // クライアントの作成
    public static Client<string, string> Client(IPEndPoint endpoint)
        => new Client<string, string>(endpoint);

    // 文字列を並びを反対にする
    private static string Reverse(string original)
        => new string(original.Reverse().ToArray());

    static void Main(string[] args) {
        var endpoint = new IPEndPoint(IPAddress.Loopback, 54321);

        // サーバを実行
        var server = Task.Run(() => Server(endpoint).RunAsync(Reverse));

        // クライアントを実行
        Task.WaitAll(
            Client(endpoint).SendAsync("あいうえお"),
            Client(endpoint).SendAsync("かきくけこ"));

        // サーバのTaskをきれいに終了するにはどうしたらいいのか...
    }
}
// 実行結果
/*
Client send request: あいうえお
Server receive request: あいうえお
Client send request: かきくけこ
Server receive request: かきくけこ
Server send response: おえういあ
Server send response: こけくきか
Client receive response: おえういあ
Client receive response: こけくきか
*/

TCPのサンプルを書いたときもそうでしたが、サーバの終了処理(キャンセル処理?)はどう書くのがいいかまだよく分からずです。

ソース全体はこちら。

.NETでUDP(UdpClient) · GitHub