UDP通信

概要

UDP

UDPはUser Datagram Protocolの頭字語です。ソケットという用語を使っていますが、コネクションレスのプロトコルです。TCPに比べてオーバーヘッドが少なく効率のよい通信ですが、送信できるサイズに制約があり、トラフィック状況に合わせた送信調整や順序化、再送といった制御がありません。

JavaでUDPを扱うAPI

Javaでは、UDP特有のAPIとしてjava.netパッケージで提供されるもの、java.nioパッケージで提供されるものと大きく2種類があります。 基本は、Javaの当初のバージョンから提供されているjava.netパッケージです。世の中の大半のソケット・プログラミングの解説もこちらのパッケージについてがほとんどです。 java.netパッケージでは、以下のクラスが提供されています。

  • java.net.DatagramSocket
  • java.net.DatagramPacket 一方、JDK1.4から新たにjava.nio(Java New I/O)パッケージが搭載されました。これにより、従来のjava.netパッケージの機能に加え、バッファ管理、ノンブロッキング・モードと多重化などを提供するスケーラブルなI/O、文字セットなどの高度な機能が利用できるようになりました。

java.nioパッケージでは、以下のクラスが提供されています。

  • java.nio.channels.DatagramChannel
  • java.nio.ByteBuffer

UDPの送受信

UDP送信

基本的な手順は以下です。

  • DatagramSocketの生成
  • InetAddressまたはInetSocketAddressの生成
  • DatagramPacketの生成
  • DatagramSocketのsendメソッドでDatagramPacketを送信

  • DatagramSocketの生成

1
2
3
4
<pre>
<code class="java">
socket = new DatagramSocket();
</code></pre>

UDPの送信では、宛先アドレス・ポート番号はsocketではなくpacketに設定します。

  • InetSocketAddressの生成
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<pre>
<code class="java">
String address = "Adelphi";
int port = 1234;
InetSocketAddress address = new InetSocketAddress(address, port);
</code></pre>
<pre>
<code class="java">
InetAddress broadcastAddr= InetAddress.getByName("255.255.255.255");
</code></pre>

InetSocketAddressは、ホスト名とポート番号を保持するクラスです。InetAddressの場合はポート番号を持たないので、後述のDatagramPacket生成時にポート番号を指定する必要があります。 ホスト名には、実行するマシンでIPアドレス解決可能な文字列、あるいはIPアドレスの文字列表現を指定することができます。IPv6アドレスも対応しています。

  • DatagramPacketの生成
1
2
3
4
5
<pre>
<code class="java">
byte[] sendData = ... 
DatagramPacket packet = new DatagramPacket(sendData, 0, sendData.length, address);
</code></pre>

sendDataはbyte[]型です。byte[]に直接データを詰めるのは大変なので、ByteArrayOutputStreamやByteBufferを使用するのが便利です。

  • DatagramSocketの送信
1
2
3
4
<pre>
<code class="java">
socket.send(packet);
</code></pre>

UDP受信

UDP受信の基本的な手順は以下です

  • DatagramSocketの生成
  • DatagramPacketの生成
  • DatagramSocketのreceiveメソッドでDatagramPacketに受信データを詰める

  • DatagramSocketの生成

1
2
3
4
5
<pre>
<code class="java">
int port = 1234;
socket = new DatagramSocket(port);
</code></pre>

受信で使用する場合は、ソケット生成時にポート番号を指定します。 複数のアドレス(ネットワーク・インタフェース)を持つマシンでは、すべてのアドレスで上記ポート番号でリッスンします。特定のアドレスだけで受信させるようにすることも可能です。その場合には、コンストラクタでローカルのアドレスを指定します。

  • DatagramPacketの生成

1
2
3
4
5
<pre>
<code class="java">
byte[] array = ...
DatagramPacket packet = new DatagramPacket(array, array.length);
</code></pre>
受信データを詰めるためのbyte配列を指定します。この配列より大きいサイズのデータを受信したときは、越えたデータは捨てられます。そこで、最大サイズ+1の配列を用意します。

  • DatagramSocketの受信
1
2
3
4
<pre>
<code class="java">
socket.receive(packet);
</code></pre>

UDPパケットを受信します。受信するまでブロックします。

データの作成

送信するデータは、byte型の配列で用意する必要があります。受信データはbyte型の配列に格納されます。 しかし、通常送受信するデータは文字列や数値型などであり、byte型の配列に格納するのは少々面倒です。

文字列(String)をbyte配列へ詰める

StringクラスのgetBytesメソッドが利用できます。 引数に文字セット(例:"UTF-8")を指定する必要があります。 送受信する両者で同一の文字セットを使用します。 文字セットとして指定できる一覧は、java.nio.charset.CharsetクラスのメソッドavailableCharsetsで取得できます。

整数型(int)をbyte配列へ詰める

ByteArrayOutputStreamとDataOutputStreamを組み合わせて使用する方法、 ByteBufferを使用する方法、BigIntegerを使用する方法などがあります。 DataOutputStreamはintの格納はBig Endianのみです。 ByteBufferはデフォルトはBig EndianですがorderメソッドでLittle Endianに変更することができます。 BigIntegerはBig Endianのみです。

文字列をbyte配列に詰める

1
2
3
4
5
<pre>
<code class="java">
String message = "EHLO pisa\r\n";
byte[] array = message.getBytes("UTF-8");
</code></pre>

int型をbyte配列に詰める~ByteArrayOutputStreamとDataOutputSteam

1
2
3
4
5
6
7
8
<pre>
<code class="java">
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
dos.writeInt(value);
dos.flush();
byte[] array = baos.toByteArray();
</code></pre>

int型をbyte配列に詰める~ByteBuffer

1
2
3
4
5
6
<pre>
<code class="java">
ByteBuffer buffer = ByteBuffer.allocate(1024); // 最大サイズ 1024byte
buffer.putInt(value);
byte[] array = buffer.array();
</code></pre>

int型をbyte配列に詰める~BigInteger

1
2
3
4
5
<pre>
<code class="java">
BigInteger intValue = BigInteger.valueOf(value);
byte[] array = intValue.toByteArray();
</code></pre>