3. サーバソケット

3.1 サーバソケット

クライアントのアプリケーションは, サーバに接続したいときにいつでも接続できる. 接続先を指定して Socket のインスタンスを作成すれば, ただちにサーバへの接続が完了していた. しかし, サーバ側は, クライアントがいつ接続を要求してくるかわからない. そのため, クライアントが接続してくるのをずっと待っていなければいけない.

接続を受け付けられる状態で待械するには, accept メソッドを用いる. accept メソッドを呼び出すと, ServerSocket は接続待ちの状態となり, クライアントが接続してくるまでプログラムはそれ以上先に進まない.

クライアントが接続してくると, accept メソッドは, そのクライアントと接続されたソケットを返して終了する. サーバアプリケーションはここで取得したソケットを利用してクライアントとの通信処理を行う.

_images/ServerSocket311.gif

EchoServer.java

serverSocket = new ServerSocket(ECHO_PORT);

待ち受けポートを指定して新しいサーバソケットを生成している.

socket = serverSocket.accept();

accept メソッドを呼び出すと, クライアントからの接続待械状態に入る. クライアントからの接続要求があると, socket にはクライアントとの通信に利用できる Socket のインスタンスが代入される.

3.2 複数の通信を受け付けるサーバ

クライアントからの複数の接続を受け付けるために, マルチスレッドを利用すること. クライアントからの接続があるたびに, 通信処理を行うスレッドを作成して起動する. クライアントとの通信処理は新しいスレッドの任せ, もともとのプログラムの処理はループによって再び accept() メソッドの実行に戻る.

_images/MultiServerSocket311.gif

MultiEchoServer.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
61
62
63
64
65
66
67
68
69
70
71
72
73
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 MultiEchoServer{
    public static final int ECHO_PORT = 51015;

    public static void main(String[] args){
	ServerSocket serverSocket = null;
	try{
	    serverSocket = new ServerSocket(ECHO_PORT);
	    System.out.println("MultiEchoServer が起動した, port = " + serverSocket.getLocalPort());
	    /*
	      ループ処理で, クライアントからの接続を受け付けて, 接続があると新しいスレッドを起動する.
	    */
	    while(true){
		Socket socket = serverSocket.accept();
		new EchoThread(socket).start();
	    }
	}catch(IOException e){
	    e.printStackTrace();
	}finally{
	    try{
		if(serverSocket != null){
		    serverSocket.close();
		}
	    }catch(IOException e){
		//
	    }
	}
    }
}

/*
  EchoThread クラスは, クライアントとの通信処理を行うスレッドである.
  コンストラクタで通信に利用する Socket を設定する.
  このクラスは Thread クラスを継承しているので, 並列に実行したい通信処理は run() メソッド内に記述する.
  run() メソッド内には, EchoServer クラスで記述した通信処理とほぼ同じコードを記述する.
*/
class EchoThread extends Thread{
    private Socket socket;

    public EchoThread(Socket socket){
	this.socket = socket;
	System.out.println("接続された: " + socket.getRemoteSocketAddress());
    }

    public void run(){
	try{
	    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(socket.getRemoteSocketAddress() + " 受信 : " + line);
		out.println(line);
		System.out.println(socket.getRemoteSocketAddress() + " 送信 : " + line);
	    }
	}catch(IOException e){
	    e.printStackTrace();
	}finally{
	    try{
		if(socket != null){
		    socket.close();
		}
	    }catch(IOException e){
		System.out.println("切断された : " + socket.getRemoteSocketAddress());
	    }
	}
    }
}

3.3 Daytime サーバ (マルチスレッド) と Daytime クライアントの実装

サンプルの構成:

1. MultiDaytimeServer.java
2. MultiDaytimeClient.java
  1. MultiDaytimeServer.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
61
import java.io.IOException;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;

public class MultiDaytimeServer{
    public static final int DAYTIME_PORT = 12345;

    public static void main(String args[]){
	ServerSocket serverSocket = null;
	try{
	    serverSocket = new ServerSocket(DAYTIME_PORT);
	    System.out.println("MultiDaytimeServer が起動した, port = " + serverSocket.getLocalPort());
	    while(true){
		Socket socket = serverSocket.accept();
		new DaytimeThread(socket).start();
	    }
	}catch(IOException e){
	    e.printStackTrace();
	}finally{
	    try{
		if(serverSocket != null){
		    serverSocket.close();
		}
	    }catch(IOException e){
		//
	    }
	}
    }
}

class DaytimeThread extends Thread{
    private Socket socket;

    public DaytimeThread(Socket socket){
	this.socket = socket;
	System.out.println("接続された : " + socket.getRemoteSocketAddress());
    }

    public void run(){
	try{
	    // データ送信用のプリントライタを作成
	    PrintWriter out = new PrintWriter(socket.getOutputStream(), true);

	    // クライアントに時間を送信する
	    out.println(new Date());
	}catch(IOException e){
	    e.printStackTrace();
	}finally{
	    try{
		if(socket != null){
		    socket.close();
		    System.out.println("切断された : " + socket.getRemoteSocketAddress());
		}
	    }catch(IOException e){
		//
	    }
	}
    }
}
  1. MultiDaytimeClient.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 MultiDaytimeClient{
    public static final int DAYTIME_PORT = 12345;

    public static void main(String[] args){
	Socket socket = null;
	try{
	    // 引数で指定したホストに接続する
	    if(args.length == 0){
		System.out.println("引数に接続のサーバを指定してください");
		return;
	    }else if(args.length == 1){
		System.out.println("デフォルトの番号を使用する");
		socket = new Socket(args[0], DAYTIME_PORT);
	    }else{
		socket = new Socket(args[0], Integer.parseInt(args[1]));
	    }

	    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();
		    System.out.println("切断された : " + socket.getRemoteSocketAddress());
		}
	    }catch(IOException e){
		//
	    }
	}
    }
}

上記のプログラムの実行結果 (サーバ側):

[wtopia]$ java MultiDaytimeServer            [~/src_py/sphinx/java.net/Daytime]
MultiDaytimeServer が起動した, port = 12345
接続された : /127.0.0.1:51375
切断された : /127.0.0.1:51375
接続された : /127.0.0.1:51378
切断された : /127.0.0.1:51378

上記のプログラムの実行結果 (クライアント側):

[wtopia]$ java MultiDaytimeClient 127.0.0.1 [~/src_py/sphinx/java.net/Daytime]
デフォルトの番号を使用する
接続した : /127.0.0.1:12345
Fri Aug 26 18:31:54 JST 2011
切断された : /127.0.0.1:12345
[wtopia]$ java MultiDaytimeClient 127.0.0.1 12345
接続した : /127.0.0.1:12345
Fri Aug 26 18:32:01 JST 2011
切断された : /127.0.0.1:12345