Recipe 17.11 Program: A Java Chat Server


This program implements a simple chat server (Example 17-15) that works with the chat applet from Recipe Recipe 16.10. It accepts connections from an arbitrary number of clients; any message sent from one client is broadcast to all clients. In addition to ServerSockets, it demonstrates the use of threads (see Chapter 24). Since there are interactions among clients, this server needs to keep track of all the clients it has at any one time. I use an ArrayList (see Recipe 7.3) to serve as an expandable list and am careful to use the synchronized keyword around all accesses to this list to prevent one thread from accessing it while another is modifying it (this is discussed in Chapter 24).

Example 17-15. ChatServer.java
/** Simple Chat Server to go with our Trivial Chat Client.  */ public class ChatServer {     /** What I call myself in system messages */     protected final static String CHATMASTER_ID = "ChatMaster";     /** What goes between any handle and the message */     protected final static String SEP = ": ";     /** The Server Socket */     protected ServerSocket servSock;     /** The list of my current clients */     protected ArrayList clients;     /** Debugging state */     private boolean DEBUG = false;     /** Main just constructs a ChatServer, which should never return */     public static void main(String[] argv) {         System.out.println("DarwinSys Chat Server 0.1 starting...");         ChatServer w = new ChatServer(  );         w.runServer(  );            // should never return.         System.out.println("**ERROR* Chat Server 0.1 quitting");     }     /** Construct (and run!) a Chat Service */     ChatServer(  ) {         clients = new ArrayList(  );         try {             servSock = new ServerSocket(Chat.PORTNUM);             System.out.println("DarwinSys Chat Server Listening on port " +                 Chat.PORTNUM);         } catch(IOException e) {             log("IO Exception in ChatServer.<init>" + e);             System.exit(0);         }     }     public void runServer(  ) {         try {             while (true) {                 Socket us = servSock.accept(  );                 String hostName = us.getInetAddress(  ).getHostName(  );                 System.out.println("Accepted from " + hostName);                 ChatHandler cl = new ChatHandler(us, hostName);                 synchronized (clients) {                     clients.add(cl);                     cl.start(  );                     if (clients.size(  ) == 1)                         cl.send(CHATMASTER_ID,                      else {                         cl.send(CHATMASTER_ID, "Welcome! you're the latest of " +                             clients.size(  ) + " users.");                     }                 }             }         } catch(IOException e) {             log("IO Exception in runServer: " + e);             System.exit(0);         }     }     protected void log(String s) {         System.out.println(s);     }     /** Inner class to handle one conversation */     protected class ChatHandler extends Thread {         /** The client socket */         protected Socket clientSock;         /** BufferedReader for reading from socket */         protected BufferedReader is;         /** PrintWriter for sending lines on socket */         protected PrintWriter pw;         /** The client's host */         protected String clientIP;         /** String form of user's handle (name) */         protected String login;         /* Construct a Chat Handler */         public ChatHandler(Socket sock, String clnt) throws IOException {             clientSock = sock;             clientIP = clnt;             is = new BufferedReader(                 new InputStreamReader(sock.getInputStream(  )));             pw = new PrintWriter(sock.getOutputStream(  ), true);         }         /** Each ChatHandler is a Thread, so here's the run(  ) method,          * which handles this conversation.          */         public void run(  ) {             String line;             try {                 while ((line = is.readLine(  )) != null) {                     char c = line.charAt(0);                     line = line.substring(1);                     switch (c) {                     case Chat.CMD_LOGIN:                         if (!Chat.isValidLoginName(line)) {                             send(CHATMASTER_ID, "LOGIN " + line + " invalid");                             log("LOGIN INVALID from " + clientIP);                             continue;                         }                         login = line;                         broadcast(CHATMASTER_ID, login +                              " joins us, for a total of " +                              clients.size(  ) + " users");                         break;                     case Chat.CMD_MESG:       // Private message from one user to another.                         if (login == null) {                             send(CHATMASTER_ID, "please login first");                             continue;                         }                         int where = line.indexOf(Chat.SEPARATOR);                         String recip = line.substring(0, where);                         String mesg = line.substring(where+1);                         log("MESG: " + login + "-->" + recip + ": "+ mesg);                         ChatHandler cl = lookup(recip);                         if (cl == null)                             psend(CHATMASTER_ID, recip + " not logged in.");                         else                             cl.psend(login, mesg);                         break;                     case Chat.CMD_QUIT:                         broadcast(CHATMASTER_ID,                          close(  );                         return;        // The end of this ChatHandler                                              case Chat.CMD_BCAST:    // Message from one user to everybody.                         if (login != null)                             broadcast(login, line);                         else                             log("B<L FROM " + clientIP);                         break;                     default:                         log("Unknown cmd " + c + " from " + login + "@" + clientIP);                     }                 }             } catch (IOException e) {                 log("IO Exception: " + e);             } finally {                 // the sock ended, so we're done, bye now                 System.out.println(login + SEP + "All Done");                 synchronized(clients) {                     clients.remove(this);                     if (clients.size(  ) == 0) {                         System.out.println(CHATMASTER_ID + SEP +                             "I'm so lonely I could cry...");                     } else if (clients.size(  ) == 1) {                         ChatHandler last = (ChatHandler)clients.get(0);                         last.send(CHATMASTER_ID,                             "Hey, you're talking to yourself again");                     } else {                         broadcast(CHATMASTER_ID,                             "There are now " + clients.size(  ) + " users");                     }                 }             }         }         protected void close(  ) {             if (clientSock == null) {                 log("close when not open");                 return;             }             try {                 clientSock.close(  );                 clientSock = null;             } catch (IOException e) {                 log("Failure during close to " + clientIP);             }         }         /** Send one message to this user */         public void send(String sender, String mesg) {             pw.println(sender + SEP + mesg);         }         /** Send a private message */         protected void psend(String sender, String msg) {             send("<*" + sender + "*>", msg);         }                  /** Send one message to all users */         public void broadcast(String sender, String mesg) {             System.out.println("Broadcasting " + sender + SEP + mesg);             for (int i=0; i<clients.size(  ); i++) {                 ChatHandler sib = (ChatHandler)clients.get(i);                 if (DEBUG)                     System.out.println("Sending to " + sib);                 sib.send(sender, mesg);             }             if (DEBUG) System.out.println("Done broadcast");         }         protected ChatHandler lookup(String nick) {             synchronized(clients) {                 for (int i=0; i<clients.size(  ); i++) {                     ChatHandler cl = (ChatHandler)clients.get(i);                     if (cl.login.equals(nick))                         return cl;                 }             }             return null;         }         /** Present this ChatHandler as a String */         public String toString(  ) {             return "ChatHandler[" + login + "]";         }     } }

I've used this code with a number of clients connected concurrently, and no difficulties were found.

See Also

The server side of any network mechanism is extremely sensitive to security issues. It is easy for one misconfigured or poorly written server program to compromise the security of an entire network! Of the many books on network security, two stand out: Firewalls and Internet Security by William R. Cheswick, Steven M. Bellovin, and Aviel D. Rubin (Addison Wesley) and Hacking Exposed by Stuart McClure, Joel Scambray, and George Kurtz (McGraw-Hill).

This completes my discussion of server-side Java using sockets. Next, I'll return to the client side to discuss applets and some useful client-side recipes. Later, in Chapter 22, I show an alternate technology that can be used to implement both sides of the chat program in a more object-oriented manner. Finally, a chat server could also be implemented using JMS (Java Message Service), a newer API that handles store-and-forward message processing. This is beyond the scope of this book, but there's an example of such a chat server in O'Reilly's Java Message Service by Richard Monson-Haefel and David Chappell.



Java Cookbook
Java Cookbook, Second Edition
ISBN: 0596007019
EAN: 2147483647
Year: 2003
Pages: 409
Authors: Ian F Darwin

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