DatagramSocket
java.net.DatagramSocket, DatagramPacketを利用することで簡単にUDP通信を行うことが可能です。まずは例から示します。
UdpSender
public class UdpSender { public void send(byte[] data, SocketAddress address){ try (DatagramSocket socket = new DatagramSocket()) { DatagramPacket packet = new DatagramPacket(data, data.length, address); socket.send(packet); } catch (IOException e) { e.printStackTrace(); } } }
UdpReceiver
public class UdpReceiver { public void receive(int port){ try (DatagramSocket socket = new DatagramSocket(port)) { byte[] buf = new byte[10]; DatagramPacket packet = new DatagramPacket(buf, buf.length); socket.receive(packet); System.out.printf("packet %s (%d byte) was received from {%s} at %s %n", Arrays.toString(packet.getData()), packet.getLength(), packet.getSocketAddress(), port); } catch (IOException e) { e.printStackTrace(); } } }
Main
public class Main { public static void main(String... args) { new Thread(new Runnable() { @Override public void run() { UdpReceiver receiver = new UdpReceiver(); receiver.receive(10005); } }).start(); UdpSender sender = new UdpSender(); sender.send("test".getBytes(StandardCharsets.UTF_8), new InetSocketAddress("localhost", 10005)); } }
UdpReceiverは10005 portで待機し、UdpSenderはlocalhostの10005 portにUdpPacketを送信します。実行すると以下のような結果が得られます。(ちなみにstartするタイミングがスレッド依存のためたまにsendが先に走って何も表示されません)
packet [116, 101, 115, 116, 0, 0, 0, 0, 0, 0] (4 byte) was received from {/127.0.0.1:59735} at 10005
UTF_8は半角英数字1文字1byteなので4byteとなります。
buf超過のデータを受信した場合
DatagramSocket#receive()に以下のJavadoc文面があります。ただ”truncated”と言っている意味がpacketを一切受け取らないのか、切り捨てなのか分からなかったので調査しました。
If the message is longer than the packet’s length, the message is truncated.
javadoc
結論としては、bufを超過したデータを受信した場合には、bufサイズ以下まではbufに格納され、buf超過のデータは破棄されます
public static void main(String... args) { new Thread(new Runnable() { @Override public void run() { UdpReceiver receiver = new UdpReceiver(); receiver.receive(10005); } }).start(); UdpSender sender = new UdpSender(); sender.send("012345678912".getBytes(StandardCharsets.UTF_8), new InetSocketAddress("localhost", 10005)); }
これを実行すると以下です。
packet [48, 49, 50, 51, 52, 53, 54, 55, 56, 57] (10 byte) was received from {/127.0.0.1:60523} at 10005
“1”, “2”が格納されていないことが分かります。dropしたかは判別できないため、最大サイズになっている場合は超過したとみなしてエラー処理すべきです。
Portの再利用性
TCP通信の場合にはCLOSEまでの待機時間があり、bindしたアドレスは一定時間TIME_WAIT状態となり直後に通常は再利用することはできません。そのため、ServerSocketをエラーから即座に再構成したい場合にはsetReuseAddress(true)として、明示的に再利用する旨をシステムに伝える必要があります。
UDPの場合はそのような必要はないようです。
public class Main { public static void main(String... args) { new Thread(new Runnable() { @Override public void run() { while(true){ UdpReceiver receiver = new UdpReceiver(); receiver.receive(10005); } } }).start(); while(true){ UdpSender sender = new UdpSender(); sender.send("012345678912".getBytes(StandardCharsets.UTF_8), new InetSocketAddress("localhost", 10005)); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } } }
上記Mainの場合、毎回DatagramSocketを作成しなおし、setReuseAddress(true)は設定していませんが、毎回SocketException (Already Used)なしに10005ポートを再利用できています。
packet [48, 49, 50, 51, 52, 53, 54, 55, 56, 57] (10 byte) was received from {/127.0.0.1:65285} at 10005 packet [48, 49, 50, 51, 52, 53, 54, 55, 56, 57] (10 byte) was received from {/127.0.0.1:65286} at 10005 packet [48, 49, 50, 51, 52, 53, 54, 55, 56, 57] (10 byte) was received from {/127.0.0.1:65287} at 10005 packet [48, 49, 50, 51, 52, 53, 54, 55, 56, 57] (10 byte) was received from {/127.0.0.1:65288} at 10005
Datagram#getData()
この関数はbufをそのまま返しますので、そのまま表示するとデータが入っていない不必要な部分まで取得してしまいます。そのため、Datagram#getLength()で得られる実データサイズをもとにtrimするのが普通です。
UdpReceiver
public class UdpReceiver { public void receive(int port){ try (DatagramSocket socket = new DatagramSocket(new InetSocketAddress("localhost", port))) { byte[] buf = new byte[10]; DatagramPacket packet = new DatagramPacket(buf, buf.length); socket.receive(packet); System.out.printf("packet %s (%d byte) was received from {%s} at %s %n", Arrays.toString(Arrays.copyOf(packet.getData(), packet.getLength())), packet.getLength(), packet.getSocketAddress(), port); } catch (IOException e) { e.printStackTrace(); } } }