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 ) { }
}
}