Previous | Next | Trail Map | Custom Networking and Security | データグラムについて


データグラムのクライアントとサーバを書く

このセクションで示す例は、クライアントとサーバの 2 つのアプリケーションで構 成される。サーバはデータグラムソケットを通じて連続的にデータグラムパケットを 受け取る。サーバが連続的に受け取る個々のデータグラムパケットは、クライアント が有名な引用文を要求していることを表す。サーバがデータグラムを受け取ると、1 行の "そのときの引用文" の入ったデータグラムパケットをクライアントに返信する ことによって応答する。

この例でのクライアントアプリケーションはかなり単純である。クライアントがその ときの引用文を受け取りたいことを表す 1 つのデータグラムパケットをサーバに送 るだけである。その後、クライアントはそれに応答してサーバがデータグラムパケッ トを送信してくるのを待機する。

サーバアプリケーションを実装するのは QuoteServer と QuoteServerThread という 2 つのクラスである。クライアントアプリケーションを実装するのは QuoteClient という 1 つのクラスである。

ここでは、サーバアプリケーションの main() メソッドを含むクラス を手始めに、これらのクラスについて調べていく。

QuoteServer クラス

QuoteServer クラスがもつメソッドは 1 つだけであり、これはクォートサーバアプ リケーションの main() メソッドである。 main() メソッドは新規の QuoteServerThread オブジェクトを作成し 、開始するだけである。

class QuoteServer {
    public static void main(String[] args) {
        new QuoteServerThread().start();
    }
}

QuoteServerThread はクォートサーバのメインロジックを実装する。

QuoteServerThread クラス

QuoteServerThread はデータグラ ムソケットを通じた要求を連続的に監視する Thread である。

QuoteServerThread には 2 つのプライベイトなインスタンス変数がある。1 つは socket という名前であり、DatagramSocket オブジェクトを参照する ものである。この変数は null に初期化される。 もう 1 つは qfs という名前であり、引用文のリストを納めた ASCII テキストファイルに形式で扱われる DataInputStream オブジェクトである。 引用文の要求 がサーバに到着すると必ず、サーバはこの入力ストリームから次の行を検索する。

メインプログラムは、QuoteServerThread を作成するとき、利用可能な次のコンスト ラクタだけを使用する。

QuoteServerThread() {
    super("QuoteServer");
    try {
        socket = new DatagramSocket();
        System.out.println("QuoteServer listening on port: " + 
socket.getLocalPort());
    } catch (java.net.SocketException e) {
        System.err.println("Could not create datagram socket.");
    }
    this.openInputFile();
}

このコンストラクタの最初の行は、スーパークラス (Thread) のコンストラクタを呼 び出して、Thread を "QuoteServer" という名前で初期化している。 コードの次のセクションは QuoteServerThread コンストラクタの重要な部分、すな わち DatagramSocket を作成する部分である。 QuoteServerThread はこの DatagramSocket を使用して、クライアントからの引用文要求を監視し、応答する。

ソケットは、引数を必要としない DatagramSocket コンストラクタを使って作成され る。

socket = new DatagramSocket();

このコンストラクタを使って作成されると、新規の DatagramSocket は使用可能なロ ーカルの任意のポートに結びつけられる。DatagramSocket クラスには、新規の DatagramSocket を結びつけるポートを指定できる別のコンストラクタもある。一部 のポートは "既知の" サービスの専用であり、ユーザからは使用できないことに注意 する。すでに使用中のポートを指定すると、 DatagramSocket の作成は失敗に終わる 。

DatagramSocket が正常に作成された後、QuoteServerThread は、DatagramSocket が どのポートに結びつけられたかを示すメッセージを表示する。 QuoteClient では、このポートを宛先とするデータグラムパケットを作成するために このポート番号が必要である。このため、 QuoteClient を実行するときは、このポート番号を指定し なければならない。

QuoteServerThread コンストラクタの最後の行は、QuoteServerThread の中でプライ ベイトなメソッド、openInputFile() を呼び出し、引用文のリストを 納めた one-liners.txt という名前 のファイルを開く。このファイルにある引用文は 1 行に 1 つずつでなければならな い。

次に、 QuoteServerThread の重要な部分 である run() メソッドに移る。(run() メソッドは Thread クラスの run() を上書きし、そのスレッドのための実装を提 供する。Thread については、 スレッドのコントロール を参照する。

QuoteServerThread の run() メソッドはまず、コンストラクタにより正しい DatagramSocket が作成されたことを確認する。socket が null の場 合は、QuoteServerThread が DatagramSocket に結びつけられなかったということで ある。 ソケットがなければサーバは動作できず、したがって run() メソッド は戻る。

socket が null でなければ、run() メソッドは無限ループに入る。 この無限ループはクライアントからの要求を連続的に監視し、要求に応答していく。 このループのコードには 2 つの重要な部分がある。1 つは要求を監視する、もう 1 つは要求に応答する部分である。まず、要求を受け取る部分から見てみよう。

packet = new DatagramPacket(buf, 256);
socket.receive(packet);
address = packet.getAddress();
port = packet.getPort();

コードの 1 行目は、データグラムソケットを通じてデータグラムメッセージを受け 取るための DatagramPacket オブジェクトを新規に作成している。この新規の DatagramPacket がソケットからデータ受け取るためのものであるとわかるのは、作 成にコンストラクタが使用されているからである。このコンストラクタの必須の引数 は 2 つだけである。クライアント固有のデータをもったバイト配列と、そのバイト 配列の長さである。DatagramSocket を通じて送る DatagramPacket を作成するとき は、そのパケットの宛先のインターネットアドレスとポート番号も指定しなければな らない。これについては、サーバがクライアント要求にどのように応答するかを説明 する箇所で後述する。

上記のコード片の 2 行目は、ソケットからデータグラムを受け取っている。データ グラムメッセージの中の情報は、その前の行で作成されたパケットにコピーされる。 receive() は、パケットが受け取られるまでブロックし続ける。 パケットが受け取られなければ、サーバは先へ進まず、ひたすら待機するのである。

次の 2 行は、受け取ったデータグラムパケットからインターネットアドレスとポー ト番号を取り出している。インターネットアドレスとポート番号は、そのデータグラ ムがどこから来たのかを表す。これがつまりサーバの応答先となる。この例では、デ ータグラムパケットのバイト配列には重要な情報は入っていない。パケットの到着自 体によって、 データグラムパケットに示されたインターネットアドレスとポート番号の場所にある クライアントから要求があったことがわかるのである。

この時点で、サーバはクライアントからの引用文の要求を受け取っているので、今度 は応答しなければならない。 コードの次の 6 行は、応答を作成し、送信している。

if (qfs == null)
    dString = new Date().toString();
else
    dString = getNextQuote();
dString.getBytes(0, dString.length(), buf, 0);
packet = new DatagramPacket(buf, buf.length, address, port);
socket.send(packet);

引用文ファイルが何らかの理由で開かれていない場合、qfs は null である。 この場合は、クォートサーバは代わりに時刻を送り出す。null で ない場合は、クォートサーバはすでに開かれているファイルから次の引用文を取り出 す。if 文の後の行は、文字列をバイト配列に変換している。

コードの 3 行目は、データグラムソケットを通じてデータグラムメッセージを送信 するための DatagramPacket オブジェクトを新規に作成している。この新規の DatagramPacket がソケットを通じてデータを送信するためのものであるとわかるの は、作成にコンストラクタが使用されているからである。このコンストラクタでは 4 つの引数が必要である。初めの 2 つは、受信用のデータグラムを作成するコンスト ラクタで必須であった引数と同じで、送信側から受信側へのメッセージの入ったバイ ト配列と、その配列の長さである。次の 2 つの引数はインターネットアドレスとポ ート番号である。これら 2 つの引数はデータグラムパケットの宛先の完全なアドレ スであり、データグラムの送信側から指定されなければならない。

コードの 4 行目は DatagramPacket を送ろうとしているところである。

QuoteServerThread に関連のある最後のメソッドは finalize() であ る。このメソッドは、DatagramSocket を閉じることによって QuoteServerThread がガベージコレクトされるときに、クリーンアップ処理を行う。ポートは限りのある 資源であり、ポートに結びつけられたソケットは使用しないときは閉じるべきである 。

QuoteClient クラス

QuoteClient クラスは QuoteServer のク ライアントアプリケーションを実装する。 このアプリケーションは QuoteServer に要求を送り、応答を待つだけである。そし て、応答を受けっ取ったときに、その内容を標準出力に表示する。このコードを詳し く見てみよう。

QuoteClient クラスには、クライアントアプリケーションの main() メソッドが 1 つ入っている。main() の冒頭では、使用するいくつか のローカル変数を宣言する。

int port;
InetAddress address;
DatagramSocket socket = null;
DatagramPacket packet;
byte[] sendBuf = new byte[256];

コードの次のセクションは、QuoteClient アプリケーションの起動時に指定されたコ マンドライン引数を処理する。

if (args.length != 2) {
     System.out.println("Usage: java DatagramClient <hostname> 
<port#>");
     return;
}

QuoteClient アプリケーションでは 2 つのコマンドライン引数が必須である。1 つ は QuoteServer が動作しているマシンの名前、もう 1 つは QuoteServer が監視しているポートである。QuoteServer を開始すると、ポート番号が表示される。 QuoteClient を開始するときは、コマンド行にこのポート番号を指定しなければなら ない。

main() メソッドで次にくるのは、クライアントプログラムのメインロ ジックが入った try ブロックである。この try ブロッ クには 3 つのメインセクションが含まれており、これらは DatagramSocket を作成 するセクション、要求をサーバに送るセクション、サーバから応答を得るセクション である。

まず、DatagramSocket を作成するコードを見てみよう。

socket = new DatagramSocket();

クライアントはサーバと同じコンストラクタを使って DatagramSocket を作成する。 DatagramSocket は利用可能な任意のローカルポートに結びつけられる。

QuoteClient プログラムは次に、要求をサーバに送る。

address = InetAddress.getByName(args[0]);
port = Integer.parseInt(args[1]);
packet = new DatagramPacket(sendBuf, 256, address, port);
socket.send(packet);
System.out.println("Client sent request packet.");

コードの 1 行目は、コマンドラインで指定されたホストのインターネットアドレス を取り出している。2 行目はコマンドラインからポート番号を取り出している。 これら 2 つの情報は、そのインターネットアドレスとポート番号を宛先とする DatagramPacket の作成に使用される。インターネットアドレスとポート番号は、ユ ーザがサーバを開始したマシンと、そのサーバが待機しているポートを表すものであ る。

前のコード片の 3 行目は、データ送信のための DatagramPacket を作成している。 パケットは空のバイト配列、その長さ、およびそのパケットの宛先であるインターネ ットアドレスとポート番号から構築される。バイト配列が空なのは、このデータグラ ムパケットはサーバに情報を要求するだけだからである。サーバが応答するのに必要 なすべての情報、つまり応答先のアドレスとポート番号は、自動的にパケットに組み 入れられる。

クライアントは次に、サーバからの応答を得る。

packet = new DatagramPacket(sendBuf, 256);
socket.receive(packet);
String received = new String(packet.getData(), 0);
System.out.println("Client received packet: " + received);

サーバから応答を得るため、クライアントは受信パケット作成し、DatagramSocket の receive メソッドを使用してサーバからの応答を受け取る。 receive() メソッドは、そのクライアントを宛先とするデータグラム パケットがソケットを通り抜けてくるまでブロックする。サーバの応答が何らかの理 由で失われると、クライアントは永久にブロックしてしまうことに注意する。これは 、データグラムモデルの "保証しない" という方針に基づく動作である。通常、クラ イアントは応答を永久に待ち続けることのないよう、タイマを設定しておく。 応答が来なければ、タイマが作動し、クライアントは再送する。

クライアントがサーバから応答を受け取ると、クライアントは getData() メソッドを使ってパケットからデータを取り出す。クライ アントは次に、そのデータを文字列に変換し、表示する。

サーバを実行する

サーバプログラムとクライアントプログラムを正常にコンパイルした後は、それらを 実行することができる。実行の順序はサーバプログラムが先である。サーバが表示す るポート番号がないと、クライアントを開始できないからである。サーバが正常にそ の DatagramSocket と結びつけられると、サーバは次のようなメッセージを表示する 。

QuoteServer listening on port: portNumber

portNumber は、サーバの DatagramSocket が結びつけられ ているポートの番号である。クライアントを開始するときはこの番号を使用する。

クライアントを実行する

サーバが開始され、監視しているポートを示すメッセージを表示したら、クライアン トプログラムを実行することができる。 クライアントプログラムを実行するときは、QuoteServer が動作しているホストの名 前と、 QuoteServer が開始時に表示したポート番号の 2 つをコマンドライン引数として指 定する必要がある。

クライアントが要求を送り、サーバからの応答を受け取ると、次のような出力が表示 されるはずである。

Quote of the Moment: Life is wonderful. Without it we'd all be dead.
(そのときの引用文: 人生とは素晴らしいものだ。我々が皆いずれ死ぬのでなければ  
。)

参照

java.net.DatagramPacket
java.net.DatagramSocket


Previous | Next | Trail Map | Custom Networking and Security | データグラムについて