Future と Completer を組み合わせることで、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 関数で同期的に待機するための処理は以下の流れです。
Completer<String>を作成する。非同期処理の完了時に文字列を返すために使用する、今回の実装の要ですsocket.listenで受信イベントをリッスンする- 受信データをデコードし、
completer.completeに渡す。これでデータがFutureの結果として設定される completer.futureでFuture<String>を返す
main 関数側では返ってきた Future を await するだけです。completer.complete で設定した文字列データが取得でき、実質的にUDP通信の受信を同期的に扱える状態になります。
Completerパターンの応用
Completer を使ったパターンはUDP通信に限った話ではありません。コールバックベースのAPIを Future に変換したい場面で広く使えます。