Engineering

DartでUDP通信の結果を同期的に待つ方法

  • Dart
  • UDP
  • Completer
  • Future
  • 非同期処理
  • ソケット通信

FutureCompleter を組み合わせることで、DartでUDP通信の受信結果を同期的に待機し、後続処理に値を返せます。UDP通信を同期的に扱うこと自体は推奨しませんが、ユースケースによっては必要になる場面があります。

サンプルサーバーを用意する

今回使う通信先として、以下のRubyコードを実行しておきます。

require 'socket'

socket = UDPSocket.new
socket.bind('localhost', 5001)

loop do
  data, addr = socket.recvfrom(1024)
  puts "Received: #{data} from #{addr[2]}:#{addr[1]}"

  sleep 3 # 待機

  response = "Hello, #{data}!"
  socket.send(response, 0, addr[2], addr[1])
  puts "Sent: #{response} to #{addr[2]}:#{addr[1]}"
end

リクエストデータを元に Hello, #{data}! の形式で返す簡単なサーバーです。受信から送信まで 3秒間 の待機(sleep 3)を入れています。このサーバーと通信するDartコードを実装していきます。

CompleterでUDP受信を待機する

UDP通信には RawDatagramSocket を使用します。RawDatagramSocket は送受信を非同期で扱えますが、今回は同期的に結果を待ち、値を呼び出し元に返せるように request 関数と main 関数を実装します。

import 'dart:convert';
import 'dart:async';
import 'dart:io';

Future<String> request(InternetAddress address, int port, String name) async {
  final socket = await RawDatagramSocket.bind(InternetAddress.anyIPv4, 0);
  final completer = Completer<String>();

  try {
    final data = utf8.encode(name);
    socket.send(data, address, port);

    socket.listen((RawSocketEvent event) {
      if (event == RawSocketEvent.read) {
        final datagram = socket.receive();
        if (datagram != null) {
          final message = utf8.decode(datagram.data);
          socket.close();
          completer.complete(message);
        }
      }
    });
  } catch (e) {
    print('Error: $e');
    socket.close();
    completer.completeError(e);
  }

  return completer.future;
}

Future<void> main() async {
  try {
    final result = await request(InternetAddress.loopbackIPv4, 5001, 'Bob');
    print('Received: $result');
  } catch (e) {
    print('Error: $e');
  }
}

main 関数から request 関数を呼び出し、通信先からのレスポンスを標準出力しています。

Completerの動作を読む

request 関数で同期的に待機するための処理は以下の流れです。

  1. Completer<String> を作成する。非同期処理の完了時に文字列を返すために使用する、今回の実装の要です
  2. socket.listen で受信イベントをリッスンする
  3. 受信データをデコードし、completer.complete に渡す。これでデータが Future の結果として設定される
  4. completer.futureFuture<String> を返す

main 関数側では返ってきた Futureawait するだけです。completer.complete で設定した文字列データが取得でき、実質的にUDP通信の受信を同期的に扱える状態になります。

Completerパターンの応用

Completer を使ったパターンはUDP通信に限った話ではありません。コールバックベースのAPIを Future に変換したい場面で広く使えます。