Clients Using a Servlet as a Server


Threaded TCP Clients and Server

Figure 30-1 shows the various objects in the threaded chat application.

Figure 30-1. Objects in the threaded chat system


Each client is represented by a ChatClient object and a ChatWatcher thread. ChatClient maintains the GUI, processes the user's input, and sends messages to the server over a TCP link. ChatWatcher waits for messages from the server and displays them in ChatClient's GUI text area.

ChatClient can send the following messages:


who

The server returns a string containing a list of the current chat users.


bye

The client sends this message just prior to exiting.


Any message

Other text strings sent to the server are assumed to be messages and are broadcast to all the current users.

The server can send out two types of message:


WHO$$ cliAddr1 & port1 & ... cliAddrN & portN &

This is sent back to a client in response to a who message. Each client is identified by its address and port number, which are extracted from the TCP link established when a client first connects to the server.


(cliAddr, port): message

This is a broadcast message, originally from the client with the specified address and port number.

Server messages are received by the ChatWatcher tHRead, which is monitoring incoming messages on the ChatClient's TCP link.

ChatServer is the top-level server, which spawns a ChatServerHandler thread to handle each new client. Since messages are to be transmitted between clients, the ChatServer maintains pertinent client information, accessible by all the threads. The shared data is held by a ChatGroup object, which offers synchronized methods to control the concurrency issues. A client's details, such as its input stream, are stored in a Chatter object.

Figure 30-2 gives the class diagrams for the application, showing the public methods.

Figure 30-2. Class diagrams for the threaded chat system


The Chat Client

The GUI supported by ChatClient is shown in Figure 30-3.

The text area for displaying messages occupies the majority of the window. Outgoing messages are typed in a text field and sent when the user presses enter. A who message is outputted when the Who button is pressed. A bye message is transmitted as a result of the user clicking on the window's Close box.

Figure 30-3. The ChatClient GUI


The server is contacted and the ChatWatcher thread is started in makeContact( ):

     // globals     private static final int PORT = 1234;     // server details     private static final String HOST = "localhost";         private Socket sock;     private PrintWriter out;  // output to the server             private void makeContact(  )     {       try {         sock = new Socket(HOST, PORT);         BufferedReader in  = new BufferedReader(                                    new InputStreamReader( sock.getInputStream(  ) ));         out = new PrintWriter( sock.getOutputStream(  ), true);             new ChatWatcher(this, in).start(  );    // watch for server msgs       }       catch(Exception e)       {  System.out.println(e);  }     } 

The output stream, out, is made global so messages can be sent from various methods in ChatClient. However, server input is only processed by ChatWatcher, so it is declared locally in makeContact( ) before being passed to the thread.

Sending messages is simple; for example, pressing the Who button results in:

     out.println("who"); 

ChatWatcher is passed a reference to ChatClient, so it can write into the GUI's text area, jtaMesgs. This is done via the method showMsg( ).

Showing a Message and Threads

showMsg( ) adds a message to the end of the jtaMesgs text area and is called by the ChatClient or ChatWatcher object. However, updates to Swing components must be carried out by Swing's event dispatcher thread; otherwise, synchronization problems may arise between Swing and the user threads. The SwingUtilities class contains invokeLater( ), which adds code to the event dispatcher's queue; the code must be packaged as a Runnable object:

     public void showMsg(final String msg)     {       Runnable updateMsgsText = new Runnable(  ) {         public void run(  )         { jtaMesgs.append(msg);  // append message to text area           jtaMesgs.setCaretPosition( jtaMesgs.getText(  ).length(  ) );                       // move insertion point to the end of the text         }       };       SwingUtilities.invokeLater( updateMsgsText );  // add code to queue     } // end of showMsg(  ) 

Though showMsg( ) can be called concurrently by ChatClient and ChatWatcher, there's no need to synchronize the method; multiple calls to showMsg( ) will cause a series of Runnable objects to be added to the event dispatcher's queue, where they'll be executed in sequential order.

An invokeAndWait( ) method is similar to invokeLater( ) except that it doesn't return until the event dispatcher has executed the Runnable object. Details on these methods, and more background on Swing and threads, can be found at http://java.sun.com/products/jfc/tsc/articles/threads/threads1.html.

My thanks to Rachel Struthers for pointing out the bug in the original version of showMsg( ).


Waiting for Chat Messages

The core of the ChatWatcher class is a while loop inside run( ) that waits for a server message, processes it, and repeats. The two message types are the WHO$$... response and broadcast messages from other clients:

     while ((line = in.readLine(  )) != null) {       if ((line.length(  ) >= 6) &&     // "WHO$$ "           (line.substring(0,5).equals("WHO$$")))         showWho( line.substring(5).trim(  ) );              // remove WHO$$ keyword and surrounding space       else  // show immediately         client.showMsg(line + "\n");     } 

showWho( ) reformats the WHO$$... string before displaying it.

The Chat Server

The ChatServer constructor initializes a ChatGroup object to hold client information, and then enters a loop that deals with client connections by creating ChatServerHandler threads:

     public ChatServer(  )     { cg = new ChatGroup(  );       try {         ServerSocket serverSock = new ServerSocket(PORT);         Socket clientSock;         while (true) {           System.out.println("Waiting for a client...");           clientSock = serverSock.accept(  );           new ChatServerHandler(clientSock, cg).start(  );         }       }       catch(Exception e)       {  System.out.println(e);  }     } 

Each handler is given a reference to the ChatGroup object.

The Threaded Chat Handler

The ThreadedChatServerHandler class is similar to the ThreadedScoreHandler class from Chapter 29. The main differences are the calls it makes to the ChatGroup object while processing the client's messages.

The run( ) method sets up the input and output streams to the client and adds (and later removes) a client from the ChatGroup object:

     public void run(  )     { try {         // Get I/O streams from the socket         BufferedReader in  = new BufferedReader(                                    new InputStreamReader( clientSock.getInputStream(  ) ));         PrintWriter out =            new PrintWriter( clientSock.getOutputStream(  ), true);         cg.addPerson(cliAddr, port, out);  // add client to ChatGroup             processClient(in, out);            // interact with client              // the client has finished when execution reaches here     cg.delPerson(cliAddr, port);       // remove client details         clientSock.close(  );         System.out.println("Client (" + cliAddr + ", " +                                      port + ") connection closed\n");       }       catch(Exception e)       {  System.out.println(e);  }     } 

processClient( ) checks for the client's departure by looking for a bye message. Other messages (who and text messages) are passed on to doRequest( ):

     private void doRequest(String line, PrintWriter out)     { if (line.trim(  ).toLowerCase(  ).equals("who")) {         System.out.println("Processing 'who'");         out.println( cg.who(  ) );       }       else  // use ChatGroup object to broadcast the message         cg.broadcast( "("+cliAddr+", "+port+"): " + line);     } 

Storing Chat Client Information

ChatGroup handles the addition/removal of client details, the answering of who messages, and the broadcasting of messages to all the clients. The details are stored in an ArrayList of Chatter objects (called chatPeople), one object for each client.

A single ChatGroup object is used by all the ChatServerHandler threads, so methods that manipulate chatPeople must be synchronized. An example of this is the broadcast( ) method, which sends the specified message to all the clients, including back to the sender:

     synchronized public void broadcast(String msg)     { Chatter c;       for(int i=0; i < chatPeople.size(  ); i++) {         c = (Chatter) chatPeople.get(i);         c.sendMessage(msg);       }     } 

The Chatter Class

The client details managed by a Chatter object are its address, port, and PrintWriter output stream. The address and port are employed to identify the client (a client has no name). The output stream is used to send messages to the client. For example:

     private PrintWriter out;   // global         public void sendMessage(String msg)     {  out.println(msg);  } 

Discussion

Messages can only be broadcast, and there's no capability to send private messages, though that's easily fixed, as you'll see in later examples. The communication protocol is defined by the server, so creating a new message format is simple:

     message / toName 

The implementation would require that clients be named; then ChatServerHandler could examine the toName part of the message and route the message only to that client. The delivery would use a new method in ChatGroup (e.g., void sendPrivate(String message, String toName)) to call the sendMessage( ) method of the Chatter object for toName.

Other communication patterns (e.g., periodic announcements linked to the current time) are straightforward to implement by extending the protocols supported by ChatServer. This illustrates one of the advantages of localizing communication management in the server.

Central control has its drawbacks as well, namely that if ChatServer fails, then the entire system fails. However, the nonfunctioning of a single ChatServerHandler tHRead doesn't affect the others. A likely scenario is one thread being made to wait indefinitely by a client who doesn't communicate with it.



Killer Game Programming in Java
Killer Game Programming in Java
ISBN: 0596007302
EAN: 2147483647
Year: 2006
Pages: 340

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net