Socket:TCP/IPによる通信プログラム

Socketおよび ServerSocketクラスを用いると、 TCP/IPの通信プログラムを非常に手軽に作成できます。
インターネットで使用されている通信のプロトコルは TCP/IP と呼ばれます。 TCP/IPによる通信へのインターフェイスを提供するために、 BSD系のUNIXで「ソケット(socket)」という概念が導入されました。 ソケットはネットワークとの入出力を、 ファイルや標準入出力と全く同じようにバイトストリームで取り扱うことを可能に してくれます。 現在では多くのシステム用にソケットライブラリと呼ばれる C言語のライブラリが 提供されています。 Javaではそれらを利用し、 しかもシステムに依存する部分を覆い隠す形でソケットを利用することが可能です。 そのために用意されているのが、 JDK java.netパッケージにある Socketと ServerSocketクラスです。
TCP/IP のプロトコルは、互いにデータの到着を確認しながら通信する信頼性の高い プロトコルです。 したがって、インターネットでの情報のやりとりに向いているわけです。 一方、LAN の中でより高速の処理が必要な場合には、 UDPプロトコルによる通信もしばしば利用されます。 JDK1.1 からは java.netパッケージに UDPプロトコルを取り扱うための DatagramSocket(およびそのサブクラス MulticastSocket),DatagramPacketクラスが 提供されています。 (DatagramSocket は Socketのサブクラスではありません。)

Socket の通信で通信相手を指定する場合に、 「ポート番号」と呼ばれる概念も重要です。 ネットワークで通信を行うマシン間では同時に複数のアプリケーションが 通信のサービスを使用することが予想されます。 そのため通信回線を擬似的に多重化して、特定のアプリケーションが回線を 独占してしまわないようにする必要があります。 そのために割り振られる番号がポート番号です。 (電話番号のように通信相手の識別番号ではなく、 むしろ道路の「何号線」というような番号だと考えればわかりやすかもしれません。) プログラムを書く上では、 ポート番号に対応する専用の通信回線があると考えればいいわけです。 Socketが通信相手として指定すべきなのは、相手のマシンとそのポート番号という 2つの情報と言うことになります。 なお通信を行うアプリケーション同士は、互いに共通のポート番号を知っていな ければなりません。 また当然のことながら、他のアプリケーションとポート番号が衝突しないように 注意が必要になります。 特にネットワークの主要なサービスが使用するポート番号は予約されているので、 新しいアプリケーションはそれらと同じ番号を利用してはいけません。

さて、具体的にプログラムの内部での取り扱いを見てみましょう。 ネットワーク通信のプログラムが他のアプリケーションと異なるのは、 単一ではなく複数のプログラムを用意する必要があるという点です。 TCP/IP の通信プログラムは「サーバー・クライアントモデル」と呼ばれる 形式で設計されるのが普通です。 「サーバー」は最初に起動され、 通信相手が接続してくるのを待つ側のプログラムです。 一歩、「クライアント」はサーバーが既に起動されていることを前提に、 そのサーバーを指定して接続を要求します。 この両者の役割の違いに応じて、 Socketクラスのオブジェクトの生成の手順が異なってきます。
最初に簡単なクライアントの方から見てみましょう。 クライアントは、サーバーのマシン名(または IPアドレス)と、アプリケーションが 使用するポート番号を指定して Socketのオブジェクトを生成します。 指定されたマシンの指定されたポート番号に対応するサーバーが起動されていれば、 これだけで通信の準備は完了です。


String serverName = "rishiri.wakhok.ac.jp";
int portNumber = 10001;
Socket socket = new Socket( serverName, portNumber );

サーバーのプログラムの場合は少し複雑になります。 実際、サーバーの側はポート番号の回線の確保、クライアントが接続してくるかの 監視など、さまざまな準備の仕事をする必要があります。 そうした面倒な処理をまとめて行ってくれるのが ServerSocketクラスです。 まず、ServerSocketのクラスのオブジェクトを生成するには、 引数に確保したいポート番号を指定します。

int portNumber = 10001;
ServerSocket serverSocket = new ServerSocket( portNumber );

ただし、これでまだ通信が始まったわけではありません。 相手がまだいないからです。 サーバーは、クライアントが接続をしてくるまで待ちつづける必要があります。 それの仕事を実行するのが ServerSocketの accept() メソッドです。 このメソッドはクライアントが接続してくるのを監視し、接続した時点で 通信用の新しい Socket のオブジェクトを生成して返値として返します。 以後の通信は、その新しい Socketのオブジェクトを通じて行われます。

Socket socket = serverSocket.accept();

Socketのオブジェクトの内部には java.io の InputStream, OutputStreamの オブジェクトが生成されます。 バイナリデータであれば、直接これらの入出力先にバイト単位で行えます。 またテキストデータを通信したいならば、 InputStreamReader,OutputStreamWriter,BufferedReader,BufferedWriter のオブジェクトを生成して利用できます。この点は Fileの入出力とほとんど同じです。 たとえば、Socketからテキストデータを1行分読み込みたいなら、次のようにします。


Socket socket = new Socket( serverName, portNumber );

BufferedReader reader = new BufferedReader( 
                             new InputStreamReader( 
                                  socket.getInputStream() ) );
String message = reader.readLine();

ソースファイル