インターネットチャットシステムの構築

Web上に出現した多くのJavaベースのチャットシステムの1つを見たことがあるかもしれません。この記事を読むと、それらがどのように機能するかを理解し、独自の簡単なチャットシステムを構築する方法を理解できます。

クライアント/サーバーシステムのこの簡単な例は、標準APIで利用可能なストリームのみを使用してアプリケーションを構築する方法を示すことを目的としています。チャットはTCP / IPソケットを使用して通信し、Webページに簡単に埋め込むことができます。参考までに、このアプリケーションに関連するJavaネットワークプログラミングコンポーネントを説明するサイドバーを提供します。それでも速度が上がらない場合は、最初にサイドバーを確認してください。ただし、すでにJavaに精通している場合は、すぐにジャンプして、サイドバーを参照するだけで参照できます。

チャットクライアントの構築

簡単なグラフィカルチャットクライアントから始めます。接続するサーバー名とポート番号の2つのコマンドラインパラメーターが必要です。ソケット接続を行い、出力領域が大きく入力領域が小さいウィンドウを開きます。

ChatClientインターフェイス

ユーザーが入力領域にテキストを入力してReturnキーを押すと、テキストがサーバーに送信されます。サーバーは、クライアントから送信されたすべてのものをエコーバックします。クライアントは、サーバーから受信したすべてのものを出力領域に表示します。複数のクライアントが1つのサーバーに接続する場合、シンプルなチャットシステムがあります。

クラスChatClient

説明されているように、このクラスはチャットクライアントを実装します。これには、基本的なユーザーインターフェイスの設定、ユーザーインタラクションの処理、サーバーからのメッセージの受信が含まれます。

importjava.net。*; インポートjava.io. *; importjava.awt。*; public class ChatClient extends Frameimplements Runnable {// public ChatClient(String title、InputStream i、OutputStream o)... // public void run()... // public boolean handleEvent(Event e)... // public static void main(String args [])はIOExceptionをスローします...}

ChatClientクラスを拡張しFrame、これは、グラフィカルアプリケーションでは一般的です。サーバーからメッセージを受信するをRunnable開始できるように、インターフェースを実装しThreadます。コンストラクターはGUIの基本セットアップを実行し、run()メソッドはサーバーからメッセージを受信し、handleEvent()メソッドはユーザーインタラクションを処理し、main()メソッドは初期ネットワーク接続を実行します。

保護されたDataInputStreami;保護されたDataOutputStreamo;保護されたTextArea出力。保護されたTextField入力。保護されたスレッドリスナー。 public ChatClient(String title、InputStream i、OutputStream o){super(title); this.i = new DataInputStream(new BufferedInputStream(i)); this.o = new DataOutputStream(new BufferedOutputStream(o)); setLayout(new BorderLayout()); add( "Center"、output = new TextArea()); output.setEditable(false); add( "South"、input = new TextField());パック ();公演 (); input.requestFocus();リスナー=新しいスレッド(これ); listener.start(); }

コンストラクターは、ウィンドウのタイトル、入力ストリーム、および出力ストリームの3つのパラメーターを取ります。ChatClient指定されたストリームを介して通信し、バッファリングされたデータストリームiおよびoを作成して、これらのストリーム上で効率的な高レベルの通信機能を提供します。次に、TextArea出力とTextField入力で構成される単純なユーザーインターフェイスを設定します。ウィンドウをレイアウトThreadして表示し、サーバーからのメッセージを受け入れるリスナーを起動します。

public void run(){try {while(true){String line = i.readUTF(); output.appendText(line + "\ n"); }} catch(IOException ex){ex.printStackTrace(); }最後に{リスナー= null; input.hide(); 検証 (); {o.close();を試してください。} catch(IOException ex){ex.printStackTrace(); }}}

リスナースレッドがrunメソッドに入るとString、入力ストリームからsを読み取る無限ループになります。Stringが到着したら、それを出力領域に追加し、ループを繰り返します。IOExceptionサーバーへの接続が失われた場合に発生する可能性があります。その場合、例外を印刷してクリーンアップを実行します。これはEOFExceptionfromreadUTF()メソッドによって通知されることに注意してください。

クリーンアップするには、最初にこれThreadへのリスナー参照をnull;に割り当てます。これは、スレッドが終了したことをコードの残りの部分に示します。次に、入力フィールドを非表示にして呼び出しvalidate()、インターフェイスが再びレイアウトされるようにし、OutputStreamoを閉じて接続が閉じていることを確認します。

すべてのクリーンアップをfinally句で実行するため、IOExceptionここで発生するか、スレッドが強制的に停止されるかに関係なく、これが発生することに注意してください。すぐにウィンドウを閉じることはありません。接続が失われた後でも、ユーザーがセッションを読みたい場合があることを前提としています。

public boolean handleEvent(Event e){if((e.target == input)&&(e.id == Event.ACTION_EVENT)){try {o.writeUTF((String)e.arg); o.flush(); } catch(IOException ex){ex.printStackTrace(); listener.stop(); } input.setText( ""); trueを返します。} else if((e.target == this)&&(e.id == Event.WINDOW_DESTROY)){if(listener!= null)listener.stop(); 非表示(); trueを返します。} return super.handleEvent(e); }

このhandleEvent()メソッドでは、2つの重要なUIイベントをチェックする必要があります。

1つ目は、のアクションイベントですTextField。これは、ユーザーがReturnキーを押したことを意味します。このイベントをキャッチすると、メッセージを出力ストリームに書き込み、呼び出しflush()て、メッセージがすぐに送信されるようにします。出力ストリームはであるDataOutputStreamため、を使用してwriteUTF()を送信できますStringIOException発生した場合は接続に失敗しているはずなので、リスナースレッドを停止します。これにより、必要なすべてのクリーンアップが自動的に実行されます。

2番目のイベントは、ユーザーがウィンドウを閉じようとしていることです。このタスクを処理するのはプログラマーの責任です。リスナースレッドを停止し、を非表示にしFrameます。

public static void main(String args [])throws IOException {if(args.length!= 2)throw new RuntimeException( "Syntax:ChatClient"); Socket s = new Socket(args [0]、Integer.parseInt(args [1])); new ChatClient( "Chat" + args [0] + ":" + args [1]、s.getInputStream()、s.getOutputStream()); }

このmain()メソッドはクライアントを起動します。正しい数の引数が指定されていることを確認Socketし、指定されたホストとポートに対してを開きChatClient、ソケットのストリームに接続されたものを作成します。ソケットを作成すると、このメソッドを終了して表示される例外がスローされる場合があります。

マルチスレッドサーバーの構築

現在、複数の接続を受け入れることができ、任意のクライアントから読み取ったすべてのものをブロードキャストするチャットサーバーを開発しています。StringをUTF形式で読み書きすることはハードワイヤードです。

このプログラムには2つのクラスがあります。メインクラスのはChatServer、クライアントからの接続を受け入れ、それらを新しい接続ハンドラオブジェクトに割り当てるサーバーです。ChatHandlerクラスは、実際のメッセージに耳を傾け、すべての接続されたクライアントにそれらを放送する作業を行います。1つのスレッド(メインスレッド)が新しい接続を処理ChatHandlerし、クライアントごとにスレッド(クラス)があります。

すべての新しいものChatClientはに接続しChatServerます; これにより、新しいクライアントからメッセージを受信するクラスのChatServer新しいインスタンスに接続が渡されChatHandlerます。ChatHandlerクラス内では、現在のハンドラーのリストが維持されます。このbroadcast()メソッドはこのリストを使用して、接続されているすべてのにメッセージを送信しますChatClient

クラスChatServer

このクラスは、クライアントからの接続を受け入れ、それらを処理するためにハンドラースレッドを起動することに関係しています。

importjava.net。*; インポートjava.io. *; インポートjava.util。*; public class ChatServer {// public ChatServer(int port)throws IOException ... // public static void main(String args [])throws IOException ...}

このクラスは、単純なスタンドアロンアプリケーションです。クラスの実際のすべての作業を実行するコンストラクターと、main()実際にそれを開始するメソッドを提供します。

public ChatServer(int port)throws IOException {ServerSocket server = new ServerSocket(port); while(true){ソケットクライアント= server.accept(); System.out.println( "承認済み" + client.getInetAddress()); ChatHandler c =新しいChatHandler(クライアント); c.start(); }}

サーバーのすべての作業を実行するこのコンストラクターは、かなり単純です。を作成してServerSocketから、のaccept()メソッドでクライアントを受け入れるループに座りますServerSocket。接続ごとに、ChatHandlerクラスの新しいインスタンスを作成し、newをSocketパラメーターとして渡します。このハンドラーを作成したら、そのstart()メソッドから開始します。これにより、接続を処理するための新しいスレッドが開始され、メインサーバーループが新しい接続を待機し続けることができます。

public static void main(String args [])throws IOException {if(args.length!= 1)throw new RuntimeException( "Syntax:ChatServer"); 新しいChatServer(Integer.parseInt(args [0])); }

The main() method creates an instance of the ChatServer, passing the command-line port as a parameter. This is the port to which clients will connect.

Class ChatHandler

This class is concerned with handling individual connections. We must receive messages from the client and re-send these to all other connections. We maintain a list of the connections in a

static

Vector.

import java.net.*; import java.io.*; import java.util.*; public class ChatHandler extends Thread { // public ChatHandler (Socket s) throws IOException ... // public void run () ... } 

We extend the Thread class to allow a separate thread to process the associated client. The constructor accepts a Socket to which we attach; the run() method, called by the new thread, performs the actual client processing.

 protected Socket s; protected DataInputStream i; protected DataOutputStream o; public ChatHandler (Socket s) throws IOException { this.s = s; i = new DataInputStream (new BufferedInputStream (s.getInputStream ())); o = new DataOutputStream (new BufferedOutputStream (s.getOutputStream ())); } 

The constructor keeps a reference to the client's socket and opens an input and an output stream. Again, we use buffered data streams; these provide us with efficient I/O and methods to communicate high-level data types -- in this case, Strings.

protected static Vector handlers = new Vector (); public void run () { try { handlers.addElement (this); while (true) { String msg = i.readUTF (); broadcast (msg); } } catch (IOException ex) { ex.printStackTrace (); } finally { handlers.removeElement (this); try { s.close (); } catch (IOException ex) { ex.printStackTrace(); } } } // protected static void broadcast (String message) ... 

The run() method is where our thread enters. First we add our thread to the Vector of ChatHandlers handlers. The handlers Vector keeps a list of all of the current handlers. It is a static variable and so there is one instance of the Vector for the whole ChatHandler class and all of its instances. Thus, all ChatHandlers can access the list of current connections.

Note that it is very important for us to remove ourselves from this list afterward if our connection fails; otherwise, all other handlers will try to write to us when they broadcast information. This type of situation, where it is imperative that an action take place upon completion of a section of code, is a prime use of the try ... finally construct; we therefore perform all of our work within a try ... catch ... finally construct.

The body of this method receives messages from a client and rebroadcasts them to all other clients using the broadcast() method. When the loop exits, whether because of an exception reading from the client or because this thread is stopped, the finally clause is guaranteed to be executed. In this clause, we remove our thread from the list of handlers and close the socket.

protected static void broadcast (String message) { synchronized (handlers) { Enumeration e = handlers.elements (); while (e.hasMoreElements ()) { ChatHandler c = (ChatHandler) e.nextElement (); try { synchronized (c.o) { c.o.writeUTF (message); } c.o.flush (); } catch (IOException ex) { c.stop (); } } } } 

This method broadcasts a message to all clients. We first synchronize on the list of handlers. We don't want people joining or leaving while we are looping, in case we try to broadcast to someone who no longer exists; this forces the clients to wait until we are done synchronizing. If the server must handle particularly heavy loads, then we might provide more fine-grained synchronization.

この同期されたブロック内Enumerationで、現在のハンドラーを取得します。このEnumerationクラスは、のすべての要素を反復処理するための便利な方法を提供しますVector。ループは、のすべての要素にメッセージを書き込むだけEnumerationです。への書き込み中に例外が発生した場合はChatClient、クライアントのstop()メソッドを呼び出すことに注意してください。これにより、クライアントのスレッドが停止するため、ハンドラーからクライアントを削除するなど、適切なクリーンアップが実行されます。