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();
}
}
}
