2. ソケット

2.1 ソケット

ソケットは TCP や UDP などの通信の終端である.

ソケットは, コンピュータの特定のポート番号と関連づけられる. ポート番号で識別できるため, 一台のホストで複数のソケットを同時に取り扱うことが可能.

_images/socket3.gif

ソケットを表現するクラスは次の4つが用意されている. これらのクラスはすべて java.net パッケージに含まれている.

_images/socket_intro3.gif

2.2 InetAddress クラス

InetAddress は IP アドレスを取り扱うためのクラスである.

InetAddress のサブラクスは:

1. Inet4Address
2. Inet6Address

InetAddress のインスタンスを新しく生成するには, 通常は [コンストラクタは用いない]. 次のようなクラスメソッドを使うこと:

1. static InetAddress getByAddress(byte[] address);
2. static InetAddress getByAddress(String name, byte[] address);
3. static InetAddress getByName(String name);

InetAddress は, ホスト名または IP アドレス (もしくはその両方) を指定して作成することができる. 例としては下記のようになる:

1. InetAddress adr = InetAddress.getByAddress(new byte[] {(byte)211, 125, 78, 77});
2. InetAddress adr = InetAddress.getByName("211,125,78,77");

2.3 SocketAddress/InetSocketAddress クラス

SocketAddress はソケットを識別するアドレスを表すクラス.

TCP や UDP などの TCP/IP プロトコルスイートにおけるソケットアドレスを表すには InetSocketAddress を用いる.

注意:

InetSocketAddress は SocketAddress のサブクラス

InetSocketAddress には次の3つのコンストラクタが用意されている:

1. public InetSocketAddress(String hostname, int port);
2. public InetSocketAddress(InetAddress addr, int port);
3. public InetSocketAddress(int port);

2.4 Socket クラス

Socket クラスはソケットを表すクラス.

Socket はローカルホストの特定のポートに関連づけられる. また, その Socket を実際に通信利用するには, 接続先のアドレスも指定されていなければいけない.

Socket クラスのいくつかのコンストラクタは下記のようになる:

1. Socket(InetAddress address, int port);
2. Socket(InetAddress address, int port, InetAddress localAddr, int localPort);
3. Socket(String host, int port, InetAddress localAddr, int localPort);

注意:

ローカルのアドレス/ポートを指定していないものは, 自動的に割り振られたポートポートに関連づけられる.

Socket を利用した通信は, 入出力用のストリームを介して実現される. 入出力用のストリームを取得するには下記のメソッドを用いる.

_images/input_output_stream2.gif

2.5 TCP サーバプログラム

ここのサンプルには, 2つのプログラムによって構成されている:

1. クライアントから送信された文字列を [そのまま送り返す] サーバプログラム
2. サーバに接続するクライアントプログラム

EchoServer.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
import java.net.Socket;
import java.net.ServerSocket;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.IOException;

public class EchoServer{
    public static final int ECHO_PORT = 51015;
    
    public static void main(String[] args){
	ServerSocket serverSocket = null;
	Socket socket = null;

	try{
	    serverSocket = new ServerSocket(ECHO_PORT);
	    System.out.println("EchoServer が起動した, port は: " + serverSocket.getLocalPort());
	    socket = serverSocket.accept();
	    System.out.println("接続された: " + socket.getRemoteSocketAddress());
	    BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
	    PrintWriter out = new PrintWriter(socket.getOutputStream(), true);

	    String line;
	    while( (line = in.readLine()) != null ){
		System.out.println("受信: " + line);
		out.println(line);
		System.out.println("送信:" + line);
	    }
	}catch(IOException e){
	    e.printStackTrace();
	}finally{
	    try{
		if(socket != null){
		    socket.close();
		}
	    }catch(IOException e){
		//
	    }

	    try{
		if(serverSocket != null){
		    serverSocket.close();
		}
	    }catch(IOException e){
		//
	    }
	}
    }
}

2.6 TCP クライアントプログラム

次に, クライアントアプリケーションを示す. キーボードからの入力をサーバプログラムに送信し, 送り返されてきた文字列をコンソールに出力するプログラム. 空行の入力があった時, 接続を切断して終了するようにする.

EchoClient.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
import java.net.Socket;
import java.net.ServerSocket;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.IOException;

public class EchoClient{
    public static final int ECHO_PORT = 51015;

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

	try{
	    // 新しいソケットを作成
	    socket = new Socket(args[0], ECHO_PORT);

	    // 接続されたソケットの接続先情報を表示している
	    System.out.println("接続した: " + socket.getRemoteSocketAddress());
	    /*
	      ソケットに対して入出力を行うための Reader / Writer および, キーボードからの入力を受け取るための Reader を作成している.
	    */
	    BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
	    PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
	    BufferedReader keyIn = new BufferedReader(new InputStreamReader(System.in));

	    /*
	      サーバとの通信処理を行っている. キーボードから入力された文字列をサーバに対して送信し, 送り返されてきた文字列を表示する.
	      ループによって空行が入力されるまで処理を繰り返している.
	    */
	    String input;
	    while( (input = keyIn.readLine()).length() > 0 ){
		out.println(input);
		String line = in.readLine();

		if(line != null){
		    System.out.println(line);
		}else{
		    break;
		}
	    }
	}catch(IOException e){
	    e.printStackTrace();
	}finally{ // ソケットを切断するための処理を記述している.
	    try{
		if(socket != null){
		    socket.close();
		}
	    }catch(IOException e){
		System.out.println("切断された : " + socket.getRemoteSocketAddress());
	    }
	}
    }
}

2.7 プログラムの実行

EchoClient.java の実行結果:

[wtopia]$ java EchoClient 127.0.0.1           [~/src_py/sphinx/java.net/EchoCS]
接続した: /127.0.0.1:51015
Hello-1
Hello-1
Hello-2
Hello-2
Hello-3
Hello-3
Hello-4
Hello-4
(空行)

EchoServer.java の実行結果:

[wtopia]$ java EchoServer                    [~/src_py/sphinx/java.net/EchoCS]
EchoServer が起動した, port は: 51015
接続された: /127.0.0.1:55415
受信: Hello-1
送信:Hello-1
受信: Hello-2
送信:Hello-2
受信: Hello-3
送信:Hello-3
受信: Hello-4
送信:Hello-4

注意:

[空行] を入力すると, ソケットが切断されている.

2.8 実習課題: Daytime プロトコル

インターネット上のプロトコルとして, Daytime プロトコルというものがある. Daytime プロトコルにおけるサーバは, ソケットに接続すると, 接続してきたクライアントに対して現在の時刻を送り返し, コネクションを切断する.

DaytimeClient.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
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;

public class DaytimeClient{
    public static final int DAYTIME_PORT = 13;

    public static void main(String[] args){
	Socket socket = null;
	if(args.length == 0){
	    System.out.println("引数に接続先のサーバを指定してください: ");
	    return;
	}

	try{
	    // 引数で指定したホストに接続する
	    socket = new Socket(args[0], DAYTIME_PORT);
	    System.out.println("接続した : " + socket.getRemoteSocketAddress());

	    // データ受信用のリーダ作成
	    BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
	    String line;
	    while( (line = in.readLine()) != null ){
		System.out.println(line);
	    }
	    in.close();
	    
	}catch(IOException e){
	    e.printStackTrace();
	}finally{
	    try{
		if(socket != null){
		    socket.close();
		}

	    }catch(IOException e){
		//
	    }

	    System.out.println("切断された : " + socket.getRemoteSocketAddress());
	}
    }
}

上記のプログラムの実行結果:

[wtopia Daytime]$ java DaytimeClient time.nist.gov
接続した : time.nist.gov/192.43.244.18:13

55798 11-08-25 13:23:59 50 0 0 743.9 UTC(NIST) *
切断された : time.nist.gov/192.43.244.18:13