各クライアントを相手にするために,スレッドを作って対応するのが一般的で ある.以下に,簡単なサーバプログラムとして 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に参加できる.
// 入出力ストリームを使うので,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を作ると いうこれまで使っていないテクニックを使っている.