WisdomSoft - for your serial experiences.

10.6 ソケット

ServerSocket クラスと Socket クラスを用いてサーバーとクライアントをそれぞれ作成し、ネットワークを介したソケットによる通信を実現します。

10.6.1 サーバー

URL クラスは物理的なネットワークの存在を隠蔽してくれましたが、URL クラスに頼った通信は Java の実装が提供する http などの限られたプロトコルしか使用することができません。 独自のプロトコルを作ったり、何らかの作業に特化したネットワーク・ソフトウェアを開発する場合、これよりは低水準なネットワーク API を利用する必要があります。

ネットワーク上のコンピュータと通信を行う方法は、UNIX で普及したソケットと呼ばれる技術が使われています。情報の入出力は、ハードウェアを隠蔽して、統一したインターフェイスによって行われるべきであるということを、入出力ストリームで説明しました。ソケットのやり取りも同じです。java.net パッケージのソケット通信も、最終的には java.io パッケージの入出力ストリームとして制御することができるのです。

まず、最初にサーバーの建築方法を説明します。サーバーとは、ネットワークを使って何らかのサービスを提供するコンピュータのことです。これに対し、サーバーに接続してサービスを利用するコンピュータをクライアントと呼びます。サーバーは、クライアントからの接続を待ち、アクセスがあれば返答を行います。

サーバーソケットは java.net.ServerSocket クラスを使います。サーバソケットは、ネットワークを介して要求が送られてくるのを待ち、その要求に基づいて処理を行い、場合によっては要求元に結果を返します。

表1 ServerSocketクラス
コンストラクタ 解説
ServerSocket() アンバウンドのサーバソケットを作成します。
ServerSocket(int port) 指定されたポート上にサーバソケットを作成します。
ServerSocket(int port, int backlog) サーバソケットを作成し、指定されたバックログで指定されたローカルポート番号にバインドします。
ServerSocket(int port, int backlog, InetAddress bindAddr) 指定されたポート、待機するバックログおよびローカル IP アドレスを使用して、サーバを作成します。
メソッド
Socket accept() このソケットに対する接続要求を待機し、それを受け取ります。
void bind(SocketAddress endpoint) ServerSocket を特定のアドレス (IP アドレスおよびポート番号) にバインドします。
void bind(SocketAddress endpoint, int backlog) ServerSocket を特定のアドレス (IP アドレスおよびポート番号) にバインドします。
void close() このソケットを閉じます。
ServerSocketChannel getChannel() このソケットに関連する固有の ServerSocketChannel オブジェクトを返します (存在する場合)。
InetAddress getInetAddress() このサーバソケットのローカルアドレスを返します。
int getLocalPort() このソケットが接続を待機中のポートを返します。
SocketAddress getLocalSocketAddress() このソケットがバインドされている端点のアドレスを返します。
int getReceiveBufferSize() この ServerSocket で使われる SO_RCVBUF オプションの値を取得します。
boolean getReuseAddress() SO_REUSEADDR が有効かどうかを調べます。
int getSoTimeout() SO_TIMEOUT の設定を取得します。
protected void implAccept(Socket s) ServerSocket のサブクラスでは、このメソッドを使用してソケットの独自のサブクラスを返すように accept() をオーバーライドできます。
boolean isBound() ServerSocket のバインディング状態を返します。
boolean isClosed() ServerSocket の閉じた状態を返します。
void setReceiveBufferSize(int size) この ServerSocket から受け入れたソケットに対して SO_RCVBUF オプションのデフォルトの推奨値を設定します。
void setReuseAddress(boolean on) SO_REUSEADDR ソケットオプションを有効または無効にします。
static void setSocketFactory(SocketImplFactory fac) アプリケーションのサーバソケット実装ファクトリを設定します。
void setSoTimeout(int timeout) 指定されたタイムアウト (ミリ秒単位) を使用して SO_TIMEOUT を有効または無効にします。

Java でサーバーを走らせるには、まず ServerSocket クラスのインスタンスを生成しなければなりません。このクラスのコンストラクタでは、引数 port からポート番号を指定することができます。ポート番号に 0 を指定した場合は、開いているポートから適当に選択されます。開いたポート番号が何番かを知りたいならば getLocalPort() メソッドから得ることができます。

コンストラクタの backlog パラメータには接続要求可能な登録数を指定することができます。これはキューになっていて、サーバーは要求をキューから引き抜いて順に解決します。キューとは、待ち行列とも呼ばれ、順番待ちをしている人の列のような状態です。サービスを受けるために並び、先に並んでいた人が先にサービスを受けることができます。サーバーが何らかの処理を行っている間にクライアントが接続すると、順番待ちのためにキューに入れられるのです。

接続要求が backlog パラメータに指定した値を超えるとサーバーは接続拒否します。ただし、OS が許可している最大キューサイズ以上を指定しても意味はありません。bindAddr パラメータは、複数の IP を持つコンピュータでサービスを提供する場合、接続要求を受け入れるローカル IP アドレスを指定することができます。

ServerSocket オブジェクトを生成できれば、次はクライアントからの要求を待機しなければなりません。accept() メソッドを呼び出すと、プログラムはクライアントからの接続を待機します。accept() は処理をブロックするため、要求があるまで制御を返しません。クライアントからの接続が成立すると、このメソッドは java.net.Socket クラスのオブジェクトを返します。Socket クラスは、接続しているコンピュータへの入出力ストリームを管理しています。

表2 Socketクラス
コンストラクタ 解説
Socket() システムでデフォルトになっているタイプの SocketImpl を使用して、接続されていないソケットを作成します。
Socket(InetAddress address, int port) ストリームソケットを作成し、指定された IP アドレスの指定されたポート番号に接続します。
Socket(InetAddress address, int port, InetAddress localAddr, int localPort) ソケットを作成し、指定されたリモートポート上の指定されたリモートアドレスに接続します。
protected Socket(SocketImpl impl) ユーザが指定した SocketImpl を使用して、接続されていないソケットを作成します。
Socket(String host, int port) ストリームソケットを作成し、指定されたホスト上の指定されたポート番号に接続します。
Socket(String host, int port, InetAddress localAddr, int localPort) ソケットを作成し、指定されたリモートポート上の指定されたリモートホストに接続します。
メソッド
void bind(SocketAddress bindpoint) ソケットをローカルアドレスにバインドします。
void close() このソケットを閉じます。
void connect(SocketAddress endpoint) このソケットをサーバに接続します。
void connect(SocketAddress endpoint, int timeout) 指定されたタイムアウト値を使用してこのソケットをサーバに接続します。
SocketChannel getChannel() このソケットに関連する固有の SocketChannel オブジェクトを返します (存在する場合)。
InetAddress getInetAddress() ソケットの接続先のアドレスを返します。
InputStream getInputStream() このソケットの入力ストリームを返します。
boolean getKeepAlive() SO_KEEPALIVE が有効かどうかを調べます。
InetAddress getLocalAddress() ソケットのバインド先のローカルアドレスを取得します。
int getLocalPort() このソケットのバインド先のローカルポートを返します。
SocketAddress getLocalSocketAddress() このソケットがバインドされている端点のアドレスを返します。
boolean getOOBInline() OOBINLINE が有効かどうかを調べます。
OutputStream getOutputStream() このソケットの出力ストリームを返します。
int getPort() このソケットの接続先のリモートポートを返します。
int getReceiveBufferSize() この Socket で使われる SO_RCVBUF オプションの値を取得します。
SocketAddress getRemoteSocketAddress() このソケットが接続されている端点のアドレスを返します。
boolean getReuseAddress() SO_REUSEADDR が有効かどうかを調べます。
int getSendBufferSize() この Socket で使われる SO_SNDBUF オプションの値を取得します。
int getSoLinger() SO_LINGER の設定を返します。
int getSoTimeout() SO_TIMEOUT の設定を返します。
boolean getTcpNoDelay() TCP_NODELAY が有効かどうかを調べます。
int getTrafficClass() このソケットから送信されるパケットの IP ヘッダのトラフィッククラスまたはサービスタイプを取得します。
boolean isBound() ソケットのバインディング状態を返します。
boolean isClosed() ソケットの閉じた状態を返します。
boolean isConnected() ソケットの接続状態を返します。
boolean isInputShutdown() ソケット接続の読み込み側の半分が閉じているかどうかを返します。
boolean isOutputShutdown() ソケット接続の書き込み側の半分が閉じているかどうかを返します。
void sendUrgentData(int data) ソケット上の 1 バイトの緊急データを送信します。
void setKeepAlive(boolean on) SO_KEEPALIVE を有効または無効にします。
void setOOBInline(boolean on) OOBINLINE (TCP 緊急データの受信) を有効または無効にします。
void setReceiveBufferSize(int size) SO_RCVBUF オプションを、この Socket に指定された値に設定します。
void setReuseAddress(boolean on) SO_REUSEADDR ソケットオプションを有効または無効にします。
void setSendBufferSize(int size) SO_SNDBUF オプションを、この Socket に指定された値に設定します。
static void setSocketImplFactory(SocketImplFactory fac) アプリケーションのクライアントソケット実装ファクトリを設定します。
void setSoLinger(boolean on, int linger) 指定された遅延時間 (秒単位) で、SO_LINGER を有効または無効にします。
void setSoTimeout(int timeout) 指定されたタイムアウト (ミリ秒単位) を使用して、SO_TIMEOUT を有効または無効にします。
void setTcpNoDelay(boolean on) TCP_NODELAY を有効または無効にします (Nagle のアルゴリズムの有効、無効の切り替え)。
void setTrafficClass(int tc) この Socket から送信されるパケットの IP ヘッダのトラフィッククラスまたはサービスタイプ octet を設定します。
void shutdownInput() このソケットの入力ストリームを「ストリームの最後」に配置します。
void shutdownOutput() このソケットの出力ストリームを無効にします。

Socket オブジェクトをコンストラクタから生成するのはクライアントです。今回は ServerSocket クラスが accept() メソッドから返してくれるため、コンストラクタを呼び出す必要はありません。Socket オブジェクトを取得することができれば、ソケットの入出力ストリームからデータを送受信するだけです。データを受信するには getInputStream() メソッドを、データを送信するには getOutputStream() メソッドを使います。 これらのメソッドは java.io パッケージの InputStream オブジェクトと OutputStream オブジェクトを返してくれるので、ここから先はファイルにデータを書き込む方法と同じです。

サーバーがクライアントの要求に答え、処理を終了したのであれば、クライアントとのソケットを切断して新しく accept() メソッドを呼び出す必要があります。ソケットは close() メソッドを使って切断することができます。

コード1
import java.net.*;
import java.io.*;

class Server {
	public static void main(String args[]) throws Exception {
		ServerSocket server = new ServerSocket(5893);
		System.out.println("port : " + server.getLocalPort());

		while(true) {
			Socket socket = server.accept();
			System.out.println(socket);
			OutputStreamWriter out =
				new OutputStreamWriter(socket.getOutputStream());
			out.write("Kitty on your lap");
			out.flush();
			socket.close();
		}
	}
}
実行結果
>java Server
port : 5893
Socket[addr=/127.0.0.1,port=1129,localport=1128]
Socket[addr=/192.168.0.1,port=2051,localport=1128]

コード1の Server.java は、クライアントからの要求を待つ単純なサーバープログラムです。サーバーはクライアントと接続が確立されると、文字列を送信してソケットを切断します。その後、再びクライアントからの接続を待ち続けるため、プログラムは無限ループします。サーバーを終了させるには、システムから強制終了させてください。

プログラムを起動すると、最初に ServerSocket コンストラクタが割り当てたポート番号 5893 が出力されます。このポート番号にアクセスすればプログラムにアクセスすることができます。実行結果は、Telnet からこのサーバーにアクセスしたものです。サーバーを起動したコンピュータ(127.0.0.1)からアクセスした結果と、LAN 上の他のコンピュータ(192.168.0.1)からアクセスした結果が出力されています。アクセスしたクライアントコンピュータには "Kitty on your lap" という文字列が送信されるでしょう。

Telnet など、サーバーにアクセスするソフトウェアが無い、または利用方法がわからない場合はコード2の Client.java を使ってください。このプログラムは Server.java に接続する専用のクライアント・プログラムです。

10.6.2 クライアント

Java ソフトウェアからサーバーにアクセスするには、クライアントソケットを作成する必要があります。クライアントソケットはサーバーとの接続を確立して、入出力ストリームを通して通信することができます。クライアントソケットとは、単純に Socket() コンストラクタから生成したオブジェクトです。

サーバーでは ServerSocket クラスの accept() メソッドが Socket オブジェクトを返しました。Socket クラスのコンストラクタでホスト名とポート番号を指定すれば、そのサーバーに接続したクライアントソケットとして利用することができます。サーバーとの接続が確立できれば、後の操作はサーバーと同じです。Socket クラスの getInputStream() メソッドgetOutputStream() メソッドから入出力ストリームを取得して通信できます。

コード2
import java.net.*;
import java.io.*;

class Client {
	public static void main(String args[]) throws Exception {
		Socket sock = new Socket(args[0] , 5893);
		BufferedReader in = new BufferedReader(
			new InputStreamReader(sock.getInputStream()));
		String msg = in.readLine();
		in.close();
		sock.close();

		System.out.println(msg);
	}
}
実行結果
>java Client localhost
Kitty on your lap

コード2の Client.java は Server.java に接続するクライアント・プログラムです。最初に Socket() コンストラクタからサーバーにアクセスしようと試みています。このプログラムを実行する場合 Server.java を起動している必要があります。コマンドライン引数には、Server.java を実行しているコンピュータのホスト名を指定してください。Server.java を実行しているコンピュータが同じコンピュータの場合 localhost を指定します。

このプログラムは、コマンドラインから指定された名前のコンピュータの 5893 ポートに接続を要求します。この時点で Server.java が実行されている場合は accept() メソッドが制御を返してサーバーが処理を開始します。その結果、文字列 "Kitty on your lap" がクライアントに送信されるのです。Client.java はサーバーと接続が確立すると、サーバーが送信した "Kitty on your lap" という文字列を受けるために Socket から入力ストリームを開きます。そして BufferedReader クラスを利用して文字列を読み取り、標準出力に出力しているのです。実行結果を見れば、正しくサーバーに接続し、文字列を取得していることが確認できます。

もちろん、指定したコンピュータが存在しなかったり、サーバーが起動していなかったり、ポート番号が違っていれば接続することはできません。この場合、Socket() コンストラクタは例外を発生させます。