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