Javaによるサーバーアプリケーション

今回は、JavaServerSocketを使って簡単なサーバーを作ってみて、サーバープログラム開発の基本を確認してみましょう。サーバーの機能としては、接続してきたクライアントに文字列のメッセージを出し、いくつかコマンドを受け付け処理する、というものです。ただし、複数の接続や効率などは一切考えず、サーバープログラムの基本的な構造のみを作ってます。

サーバーの構造と実装

Javaに用意されているServerSocketクラスのコンストラクタでは、サーバーが使用するポートを指定できます。ここでポートを指定しておくとServerSocketは指定されたポートを監視して接続を待ち、accept()で接続してきたクライアントとの通信に使用するソケットを取得することができるようになります。
 クライアントとの通信ができるようになれば、あとは普通のソケット通信同様ソケットのストリームでデータをやり取りするだけ。そして、クライアントとの通信が終了したら再び接続を待ち、接続されたらまた同様の処理を行います。今回のサーバーは、クライアントから接続を切れるようにして、

  • ServerSocket作成
  • ループ1
  •  acceptでクライアントとの通信用ソケット取得
  •  通信用ソケットから入出力用ストリームを取得
  •  サーバーから初期メッセージを送信
  •  ループ2
  •   クライアントからのコマンド受付
  •   コマンド処理
  •  クライアントから接続終了コマンドを受けたらループ2を閉じる
  •  クライアントとの通信に使用したソケットを閉じる
  • ループ1閉じる

 という構造で作ってみます。この構造は、ループの制御用にboolean型変数go(ループ1)とlogon(ループ2)を用意し、

  サーバーソケット作成

  while(go) {

    クライアントからの接続受け入れ

    while(logon) {

      クライアントとの通信処理

    }

    接続解除

  }

  終了処理(サーバーソケットクローズ)

 という形で処理すれば良いでしょう。

 まず、サーバーソケットssの作成は、今回使用するポート7777を引数にServerSocketのコンストラクタを呼び出します。

  ss=new ServerSocket(7777);

 サーバーソケットができたら、サーバー処理本体のループに入ります。ループでは、最初にaccept()でクライアントからの接続を待ち、接続されたらソケットを取得して入出力用のストリームを作成しましょう。なお、入力ストリームはクライアントから取得した文字列をJavaの2バイト文字列に変換するためInputStreamReaderを使用しています。

  while (go) { // サーバーループ

    sc=ss.accept();

    // ストリーム取得
    os=sc.getOutputStream();
    isr=new InputStreamReader(sc.getInputStream());

 続いて、今取得したosに接続メッセージを出しましょう。

  String mes="Test Server!\r\n";

  os.write(mes.getBytes());

 これで、接続時に行う処理は終了です。次に、クライアントからコマンド入力を受けそのコマンドを処理するループに入ります。今回は、改行までの1行を一つのコマンドとして認識し、以下のコマンドを定義しました。

hostサーバーソケットの情報を取得
localクライアントソケットの情報を取得
logout接続解除
exitサーバー終了
  boolean logon=true;

  while (logon) { // コマンド処理ループ

      boolean bRead=true;
      StringBuffer sb=new StringBuffer();

      while (bRead) { // 改行までの文字列入力取得

          c=isr.read();

          if (c=='\r' || c=='\n') // 改行文字ならループ終了
              bRead=false;
          else      // 読み込んだ文字を文字バッファに追加
              sb.append((char)c);

      }

      line=sb.toString(); // 入力されたコマンド

      if (line.length()>0) { // コマンド処理

          // 入力されたコマンドを送信し返す
          mes="Command:"+line+"\r\n";
          os.write(mes.getBytes());

          if (line.equals("host")) { // hostコマンド

              mes=ss.toString()+"\r\n";
              os.write(mes.getBytes());

          }

          if (line.equals("local")) { // localコマンド

              mes=sc.toString()+"\r\n";
              os.write(mes.getBytes());

          }

          if (line.equals("logout")) // logoutコマンド
              logon=false;

          if (line.equals("exit")) { // exitコマンド

              sc.close();

              logon=false;
              go=false;

          }

      }

 }

  sc.close();

 ループ内では改行が来るまで入力文字列を加えて行き、改行が来たらそれまでに受け取った文字列をコマンドとして処理しています。接続やサーバー自体の終了は、ループ制御変数go/logonで行うようにしました。

 logoutコマンドでlogonfalseになると、コマンド処理ループから抜けてクライアントとのソケットを閉じます。そして、またサーバーループ先頭のaccept()に戻って次の接続を待ちます。

プログラム

 今回のプログラムはコンソールJavaアプリケーションです。javacでコンパイルして実行すると、サーバーが立ち上がります。次に、サーバーが動いているマシンに対しtelnetでポート7777に接続してください。同じマシンから接続する場合は

  telnet localhost 7777

 とします。

 接続したら、コマンドを受け付ける状態になるのでhost/local/logout/exitコマンドを実行してみましょう。logoutするとサーバーとの接続が切れますので、そのまま再度接続してサーバーがクライアントと接続・切断を繰り返す様子を確認してみてください。さらに、すでにクライアントが接続している状態で新たな接続を要求するとどうなるか、その接続しているクライアントがlogoutするとどうなるか、見てみましょう。

 サーバーを終了するには、クライアントからexitコマンドを送ります。

プログラムソース表示

今回のサーバープログラムでサーバー側プログラム開発の流れをつかめたら、独自のプロトコルを実装してみてください。


プログラミング資料庫 > ネットワークプログラミング実験室