4. データグラム

UDP 通信プログラムに必要な2つのクラス:

1. DatagramSocket
2. DatagramPacket

4.1 データグラム通信

TCP と UDP の大きな違いは:

TCP は固定的な接続を確立しなければならない
UDP は固定的な接続が必要ない

図で表すと,

_images/TCPvsUDP4.gif

TCP 通信は, 電話のように事前のコネクションを確立し, 入出力をストリームとして取り扱う.

UDP 通信は, 個々の封筒 (パケット) に毎回宛先を書いてデータを送信する.

4.2 DatagramSocket クラス

UDP 通信では固定的なコネクションを必要としないため, ローカルのポートにさえバインドされていれば通信を行うことができる.

ローカルホストのポートにバインドされた DatagramSocket のインスタンスを作成するには, 次のコンストラクタを用いる:

1. public DatagramSocket(); // 自動的に割り振られたアドレスにバインドされる
2. public DatagramSocket(int port);
3. public DatagramSocket(int port, InetAddress laddr);
4. public DatagramSocket(SocketAddress bindaddr);

4.3 DatagramPacket クラス

DatagramPacket は, DatagramSocket を通して送受信される UDP パケットを表すクラス.

DatagramPacket は主に次のような情報を保持している:

1. データの通信相手のソケットアドレス (IP アドレス + ポート番号)
2. データのバイト列

DatagramPacket クラスには,

送信用のパケットを作成するためのコンストラクタは次の4つがある:

1. public DatagramPacket(byte buf[], int length, InetAddress addr, int port);
2. public DatagramPacket(byte buf[], int offset, int length, InetAddress addr, int port);
3. public DatagramPacket(byte buf[], int length, SocketAddress addr);
4. public DatagramPacket(byte buf[], int offset, int length, SocketAddress addr);

受信用のパケットを作成するためのコンストラクタは次の2つがある:

1. public DatagramPacket(byte buf[], int length);
2. public DatagramPacket(byte buf[], int offset, int length);

4.4 データグラム通信プログラム

UDP を利用した通信プログラムを作成しよう. ここでは, 一方向 にメッセージを送信するだけのプログラム.

まず送信側のプログラムを示す.

DatagramSender.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import java.net.DatagramSocket;
import java.net.DatagramPacket;
import java.net.InetSocketAddress;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;

public class DatagramSender{
    public static final int SERVER_PORT = 51015;
    public static final int PACKET_SIZE = 1024;

    public static void main(String[] args){
	DatagramSocket socket = null;
	/*
	  パケットの宛先に利用するソケットアドレスを設定している.
	  引数: IP アドレス, 51015
	*/
	InetSocketAddress remoteAddress = new InetSocketAddress(args[0], SERVER_PORT);

	try{
	    BufferedReader keyIn = new BufferedReader(new InputStreamReader(System.in));
	    /*
	      パケットの送信に利用する DatagramSocket を作成している.
	      ポート番号は自動的に割り振られる.
	    */
	    socket = new DatagramSocket();

	    String message;
	    while( (message = keyIn.readLine()).length() > 0 ){
		byte[] buf = message.getBytes();

		/*
		  送信するパケットを作成している.
		  まず, キーボードから入力された文字列 byte 型の配列に変換し,
		  そのデータを用いてパケットを作成している.
		*/
		DatagramPacket packet = new DatagramPacket(buf, buf.length, remoteAddress);
		/*
		  作成したパケットを送信する.
		*/
		socket.send(packet);
	    }
	}catch(IOException e){
	    e.printStackTrace();
	}finally{
	    if(socket != null){
		socket.close();
	    }
	}
    }
}

次は受信側のプログラムを示す.

DatagramReceiver.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import java.net.DatagramSocket;
import java.net.DatagramPacket;
import java.io.IOException;

public class DatagramReceiver{
    public static final int SERVER_PORT = 51015;
    public static final int PACKET_SIZE = 1024;

    public static void main(String args[]){
	DatagramSocket socket = null;

	/*
	  受信パケットの入れ物を作成している.
	  まず, byte 型の配列を準備し, その配列を引数にして DatagramPacket のコンストラクタを呼び出している.
	*/
	byte[] buf = new byte[PACKET_SIZE];
	DatagramPacket packet = new DatagramPacket(buf, buf.length);

	try{
	    /*
	      パケットの送受信に用いるソケットを作成している.
	      送信側と同じ 51015 ポートを利用している.
	    */
	    socket = new DatagramSocket(SERVER_PORT);
	    System.out.println("DatagramReceiver が起動した, port = " + socket.getLocalPort());
	    while(true){
		/*
		  パケットの到着を待っている.
		*/
		socket.receive(packet);
		/*
		  受信したパケットから文字列を取り出している.
		  byte 配列から文字列を作成する String クラスのコンストラクタを利用している.
		*/
		String message = new String(buf, 0, packet.getLength());
		/*
		  受信した文字列を, 送信元のアドレスとともに表示している.
		  送信元のアドレスは, DatagramPacket クラスの getSocketAddress メソッドで取得することができる.
		 */
		System.out.println(packet.getSocketAddress() + " 受信: " + message);
	    }
	}catch(IOException e){
	    e.printStackTrace();
	}finally{
	    if(socket != null){
		socket.close();
	    }
	}
    }
}

/*
  注意:
  このプログラムは前節のMultiEchoServer のようにマルチスレッド
  にしていないが, 複数のクライアントからの接続を同時に処理する
  ことができる.
  DatagramSocket は固定的な接続を必要としないため, ソケット自体
  にはなにも変更を加えることなく, 1つのソケットで複数の相手と
  通信することができる.
*/

4.5 プログラムの実行

DatagramSender.java (送信側のプログラムの実行結果):

[wtopia]$ java DatagramSender 127.0.0.1
hello_1
hello_2
hello_3

DatagramReceiver.java (受信側のプログラムの実行結果):

[wtopia]$ java DatagramReceiver       [~/src_py/sphinx/java.net/SenderReceiver]
DatagramReceiver が起動した, port = 51015
/127.0.0.1:57110 受信: hello_1
/127.0.0.1:57110 受信: hello_2
/127.0.0.1:57110 受信: hello_3

4.6 Daytime サーバとクライアントを UDP を利用したもの

サンプルの構成:

1. DatagramDaytimeServer.java
2. DatagramDaytimeClient.java
  1. DatagramDaytimeServer.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.util.Date;

public class DatagramDaytimeServer{
    public static final int DAYTIME_PORT = 51015;
    public static final int PACKET_SIZE = 1024;

    public static void main(String[] args){
	DatagramSocket socket = null;
	byte[] buf = new byte[PACKET_SIZE];
	DatagramPacket packet = new DatagramPacket(buf, buf.length);

	try{
	    socket = new DatagramSocket(DAYTIME_PORT);
	    System.out.println("DatagramDaytimeServer が起動した, port = " + socket.getLocalPort());

	    while(true){
		socket.receive(packet);

		// 時刻を求める
		String daytime = (new Date()).toString();
		byte[] bufs = daytime.getBytes();

		// 送信データグラムインスタンスの作成
		DatagramPacket sendPacket = new DatagramPacket(bufs, bufs.length, packet.getSocketAddress());

		// データグラムパケットの送信
		socket.send(sendPacket);
	    }
	}catch(IOException e){
	    e.printStackTrace();
	}finally{
	    if(socket != null){
		socket.close();
	    }
	}
    }
}
  1. DatagramDaytimeClient.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.DatagramPacket;
import java.net.InetSocketAddress;
import java.net.SocketTimeoutException;

public class DatagramDaytimeClient{
    public static final int DAYTIME_PORT = 51015;
    public static final int PACKET_SIZE = 1024;

    public static void main(String[] args){
	DatagramSocket socket = null;
	InetSocketAddress remoteAddress;

	// 受信データグラムインスタンスの作成
	byte[] buf = new byte[PACKET_SIZE];
	DatagramPacket packet = new DatagramPacket(buf, buf.length);
	// 引数で指定したサーバに接続する
	if(args.length == 0){
	    System.out.println("引数に接続先のサーバを指定してください");
	    return;
	}else if(args.length == 1){
	    System.out.println("デフォルトのポート番号を使用する");
	    remoteAddress = new InetSocketAddress(args[0], DAYTIME_PORT);
	}else{
	    remoteAddress = new InetSocketAddress(args[0], Integer.parseInt(args[1]));
	}

	try{
	    socket = new DatagramSocket();
	    // 5 秒以内に反応がない場合エラー出力する
	    socket.setSoTimeout(5000);
	    // パケットを送る
	    byte[] bufs = "".getBytes();
	    DatagramPacket sendPacket = new DatagramPacket(bufs, bufs.length, remoteAddress);

	    // データグラムパケットの送信
	    socket.send(sendPacket);

	    // データグラムパケットの受信
	    socket.receive(packet);

	    // 受信データグラムパケットの内容
	    String daytime = new String(buf, 0, packet.getLength());
	    System.out.println(daytime);
	}catch(SocketTimeoutException e){
	    System.out.println("サーバから反応がない");
	}catch(IOException e){
	    e.printStackTrace();
	}finally{
	    if(socket != null){
		socket.close();
	    }
	}
    }
}

DatagramDaytimeServer.java の実行結果:

[wtopia]$ java DatagramDaytimeServer
DatagramDaytimeServer が起動した, port = 51015

DatagramDaytimeClient.java の実行結果:

[wtopia UDP]$ java DatagramDaytimeClient
引数に接続先のサーバを指定してください
[wtopia UDP]$ java DatagramDaytimeClient 127.0.0.1
デフォルトのポート番号を使用する
Sat Aug 27 15:40:28 JST 2011
[wtopia UDP]$ java DatagramDaytimeClient 127.0.0.1
デフォルトのポート番号を使用する
Sat Aug 27 15:40:36 JST 2011