Download ChatServ.java source file
// The "Coffee Talk" server, also known as "Java chat" server. // This program is a chat server program that shows multi-threaded // network application. Each time a chat "client" tries to connect // to this chat server, a separate thread is created to handle // just that connection. The design of each thread is simple: // Wait for incoming data, then broadcast it to all connected // clients, then wait for more incoming data. The tricky parts // are to allow users to "login" (or attach) to the server, to logout // (or hang up, or quit the chat session), and to switch chat rooms // (a feature I plan to add later, which will change the protocol). // Another future addition is a MaxConnections to limit the chat server // to a reasonable number of simultaneous clients. // // The protocol I invented between the server and the client is as follows: // CLIENT SERVER // opens a socket connection // to server // returns "Connected to JavaChat\nUser: " // sends name (e.g. "Joe") // returns "NAME: Joe" (Maybe "Joe2" or "Joe3") // (If name is missing it is set to "Anonymous") // broadcasts "NEW: Joe", waits // sends message ("MSG: Hi world") // broadcasts "FROM: Joe\t Hi World", waits // sends message ("WHO:") // returns "USERS: Joe\tWayne\t ..." // ... // // sends message "BYE:" (or disconnect) // broadcasts "EXIT: Joe", closes connection // // Note that the client code is not included here. It is expected that the // client program will also be multi-threaded, and support this same protocol. // // There is also a timeout feature to close a socket after 5 minutes. This time // is short for demonstration purposes. A Windows bug prevents sockets from // closing completely, so after awhile the chat server will run out of memory // and probably crash. // // This program was inspired by "The Chatter System", a similar program developed // by Judy Bishop in her fine book "Java Gently", 2nd Ed. Addison-Wesley 1998, // ISBN 0-201-34297-9. Pages 417-421. // // (C) 1999 Wayne Pollock, Tampa Florida USA. All Rights Reserved. import java.io.*; import java.net.*; import java.util.*; // for Vector and StringTokenizer; public class ChatServ { private static Vector clientSocketList = new Vector(); private static Vector clientNameList = new Vector(); private static final int defaultPort = 8181; // Any unused int > 1024 is OK. public static void main (String[] args) { int port = 8181; // Note: 0 would mean to use the first available port. if ( args.length > 0 ) // Assume first arg is port number to use. { try { port = Integer.parseInt( args[0] ); } catch (Exception e) { port = defaultPort; } } try { ServerSocket listener = new ServerSocket( port ); listener.setSoTimeout( 0 ); // Disable timeout. // A bug prevents getInetAddr() from working on Windows; use // "winipcfg.exe" from Start-->Run... to determine IP addr of server. System.out.println( "Chat server initialized and listening on port " + port + ", at: " + listener.getInetAddress() ); System.out.println( "Hit CONTROL+C to stop server." ); // Wait for some chat client to connect: for (;;) // Repeat forever until program is killed. { Socket clientSocket = listener.accept(); // Wait here. addSock( clientSocket ); new ChatHandler( clientSocket ).start(); // Start new thread. } } catch ( IOException ioe ) { System.err.println( "Chat Server initialization at port " + port + " failed: " + ioe ); System.exit( -1 ); } } // broadcast sends a message from a user to every chat client: static synchronized void broadcast ( String msgHeader, String user, String msg ) { Socket s; PrintWriter p; for ( int i = 0; i < clientSocketList.size(); ++i ) { s = (Socket) clientSocketList.elementAt( i ); try { p = new PrintWriter( s.getOutputStream(), true ); // true: auto-flush p.println( msgHeader + user + "\t" + msg ); } catch ( IOException ignored ) { } } } // remove deletes the Socket from the Vector clientSocketList. Note // that this can be slow for Vectors, and probably shouldn't be used // in a synchronized method. In Java 1.2 there is a java.util.List // class, which would be a better choice for this project: static synchronized void remove ( Socket sock, String clientName ) { clientSocketList.removeElement( sock ); if ( clientName != null ) clientNameList.removeElement( clientName ); } static synchronized void addSock ( Socket sock ) { clientSocketList.addElement( sock ); } static synchronized String addName ( String userName ) { String users = ChatServ.listUsers().toLowerCase(); int n = 0; while ( users.indexOf( userName.toLowerCase() ) > -1 ) { ++n; userName = userName + n; } clientNameList.addElement( userName ); return userName; } static synchronized String listUsers () { Enumeration e = clientNameList.elements(); StringBuffer users = new StringBuffer(); while ( e.hasMoreElements() ) users.append( (String) e.nextElement() ).append( '\t' ); return users.toString().trim(); // remove trailing tab. } } // One ChatHandler object is created per client, each its own thread, so // the instance data is separate for each thread. Using static variables // here would cause all threads to share the same variables! // Each client implements the server side of the chat protocol. // The Thread exits when we exit the run() method. class ChatHandler extends Thread { private static final int timeoutInterval = 1000 * 60 * 5; // 5 minutes private BufferedReader in; private PrintWriter out; private Socket client; private String userName; ChatHandler ( Socket sock ) { client = sock; } public void run () { try { in = new BufferedReader( new InputStreamReader( client.getInputStream() ) ); out = new PrintWriter( client.getOutputStream(), true ); // First, complete the "hand-shake" to join the chat: out.println( "Connected to JavaChat" ); out.print( "User: " ); out.flush(); userName = in.readLine(); // Check for duplicate or missing user names, // create unique name if necessary (e.g. "Jane2"): StringTokenizer st = new StringTokenizer( userName ); if ( ! st.hasMoreTokens() ) userName = "Anonymous"; else userName = st.nextToken(); // Strips the whitespace. userName = ChatServ.addName( userName ); out.println( "NAME: " + userName ); // Tell client their name ChatServ.broadcast( "NEW: ", userName, "" ); // All set! Now we wait for an incoming message and respond // to it, according to our protocol. But first, set a timeout // in case the user just walks away, so the connection can be closed: client.setSoTimeout( timeoutInterval ); while ( true ) // an alternative to "for (;;)" { String packet = in.readLine(); if ( packet.startsWith( "MSG: " ) ) ChatServ.broadcast( "FROM: ", userName, packet.substring(5) ); else if ( packet.startsWith( "BYE:" ) ) break; else if ( packet.startsWith( "WHO:" ) ) { String users = ChatServ.listUsers(); out.println( "USERS: " + users ); } else ; // do nothing, ignore unknown message types } } catch ( Exception ignored ) { } // Get to here on error (or disconnect) // Clean up conversation: if ( userName != null && ! userName.equals( "Anonymous" ) ) ChatServ.broadcast( "EXIT: ", userName, "" ); ChatServ.remove( client, userName ); try { client.close(); } catch ( IOException ignored ) { } } }