簡単なサーバプログラム


先ほど導入したスレッドを使って,サーバプログラムを書いてみる.サーバ 側は,ポート番号を指定して,ServerSocket クラスのインスタンスを作成する.ServerSocketは,作られた状態では, クライアントからの接続を待っていて,接続されると acceptメソッドでその クライアントと一対一通信をおこなうためのSocketが作られる.

各クライアントを相手にするために,スレッドを作って対応するのが一般的で ある.以下に,簡単なサーバプログラムとして chat(おしゃべり)サーバを作っ てみている.

  // 入出力ストリームを使うので,java.io.* を import
import java.io.*;
  // ソケットを使うので java.net.* を import 
import java.net.*;

  // 一人のクライアントとの通信を担当するスレッド
  // スレッド上で走らせるため Runnable インタフェースを実装
class Worker implements Runnable{
    // 通信のためのソケット
  Socket sock;
    // そのソケットから作成した入出力用のストリーム
  PrintWriter out;
  BufferedReader in;
    // サーバ本体のメソッドを呼び出すために記憶
  ChatServer chatServer;
    // 担当するクライアントの番号
  int n;
    // コンストラクタ
  public Worker(int n,Socket s,ChatServer cs){ 
    this.n=n;
    chatServer=cs; 
    sock=s; 
    out=null; 
    in=null;
  }
    // 対応するスレッドが start した時に呼ばれる.
  public void run(){
    System.out.println("Thread running:"+Thread.currentThread());
    try{
        // ソケットからストリームの作成
      out = new PrintWriter(sock.getOutputStream(),true);
      in = new BufferedReader(new InputStreamReader(sock.getInputStream()));
      String s=null;
        // ソケットからの入力があったら,
      while((s=in.readLine())!=null){
          // クライアント全体に送る.
        chatServer.sendAll("["+n+"]"+s);
      }
        // 自分自身をテーブルから取り除く
      chatServer.remove(n);
        // ソケットを閉じる
      sock.close();
    }
    catch(IOException ioe){
      System.out.println(ioe);
      chatServer.remove(n);
    }
  }
    // 対応するソケットに文字列を送る
  public void send(String s){
    out.println(s);
  }
}
public class ChatServer{
    // 各クライアントを記憶する配列.
  Worker workers[];
    // コンストラクタ
  public ChatServer(){
      // ポート番号を 4444にする.同じマシンで同じポートを使うことは
      // できないので,ユーザごとに変えること(1023以下は使えない)
    int port=4444;
      // 配列を作成
    workers=new Worker[100];
    Socket sock;
    try{
        // ServerSocketを作成
      ServerSocket servsock=new ServerSocket(port);
        // 無限ループ,breakが来るまで
      while(true){
          // クライアントからのアクセスをうけつけた.
        sock=servsock.accept();
        int i;
          // 配列すべてについて
        for(i=0;i< workers.length;i++){
            // 空いている要素があったら,
          if(workers[i]==null){
              // Workerを作って
            workers[i]=new Worker(i,sock,this);
              // 対応するスレッドを走らせる
            new Thread(workers[i]).start();
            break;
          }
        }
        if(i==workers.length){
          System.out.println("Can't serve");
        }
      }
    } catch(IOException ioe){
      System.out.println(ioe);
    }
  }
  public static void main(String args[]) throws IOException{
      // インスタンスを1つだけ作る.
    new ChatServer();
  }
    // synchronized は,同期のためのキーワード.つけなくても動くことはある.
  public synchronized void sendAll(String s){
    int i;
    for(i=0;i< workers.length;i++){
        // workers[i]が空でなければ文字列を送る
      if(workers[i]!=null)
        workers[i].send(s);
    }
  }
    // クライアント n が抜けたこと記録し,他のユーザに送る.
  public void remove(int n){
    workers[n]=null;
    sendAll("quiting ["+n+"]");
  }
}
このプログラムをあるマシン(例 ux019上で)
java ChatServer
と動かしておいて,他のマシン(ux019自身でも可)で,
 telnet ux019 4444
を実行すると,chatに参加できる.

chatクライアントプログラム

上のchatサーバに接続するプログラムは javaでは以下のように書ける.
  // 入出力ストリームを使うので,java.io.* を import
import java.io.*;
  // ソケットを使うので java.net.* を import 
import java.net.*;

// ユーザのキーボード入力を受け取って, サーバに送るスレッドと 
// サーバと接続したソケットからメッセージを受け取って表示するスレッド
// を共用したクラス
class Sender implements Runnable{
  // 入力ストリーム
  BufferedReader in;
  // 出力ストリーム
  PrintWriter out;
  // コンストラクタ
  public Sender(BufferedReader in,PrintWriter out){
    // 左辺の this.inはインスタンス変数の in, 右辺の inは引数の in
    this.in=in;
    this.out=out;
  }
  // スレッドが start すると,これが呼ばれて,動き続ける.
  public void run(){
    try{
      String s;
      // 入力ストリームから一行入力
      while((s=in.readLine())!=null){
        // 出力ストリームに一行出力
	out.println(s);
      }
    }
    catch(Exception e){
      System.err.println(e);
    }
  }
}
// java ChatClient ホスト名 ポート番号
// と呼び出す
class ChatClient{
  public static void main(String args[]){
    // ホスト名
    String hostName=args[0];
    // ポート番号を文字列から整数に変換
    int port=Integer.parseInt(args[1]);
    try{
      // サーバと接続するためのソケットを作る
      Socket sock=new Socket(hostName,port);
      // ユーザからの入力のストリームの作成
      BufferedReader ui=new BufferedReader(new InputStreamReader(System.in));
      // サーバからの入力のストリームの作成
      BufferedReader si=new BufferedReader(new InputStreamReader(sock.getInputStream()));
      // サーバへの出力のストリームの作成
      PrintWriter so=new PrintWriter(sock.getOutputStream(),true);
      // ユーザへの出力のストリームの作成
      PrintWriter uo=new PrintWriter(System.out,true);
      // ユーザ入力をサーバに転送するスレッドを作成し, start
      new Thread(new Sender(ui,so)).start();
      // サーバからの入力をユーザに転送するスレッドを作成し, start
      new Thread(new Sender(si,uo)).start();
    }
    catch(IOException e){
      System.err.println(e);
    }
  }
}
上の例では,送信用スレッドと受信用スレッドに対応するクラスをSenderとい う一つのクラスにしたが,そのために System.out から PrintWriterを作ると いうこれまで使っていないテクニックを使っている.