/** 
 * Nakov Chat Server 
 * (c) Svetlin Nakov, 2002 
 * http://www.nakov.com 
 * 
 * This program is an example from the book "Internet 
 * programming with Java" by Svetlin Nakov. It is freeware. 
 * For more information: http://www.nakov.com/books/inetjava/ 
 * 
 * Nakov Chat Server is multithreaded chat server. It accepts 
 * multiple clients simultaneously and serves them. Clients are 
 * able to send messages to the server. When some client sends 
 * a message to the server, the message is dispatched to all 
 * the clients connected to the server. 
 * 
 * The server consists of two components - "server core" and 
 * "client handlers". 
 * 
 * The "server core" consists of two threads: 
 *   - NakovChatServer - accepts client connections, creates 
 * client threads to handle them and starts these threads 
 *   - ServerDispatcher - waits for messages and when some 
 * message arrive sends it to all the clients connected to 
 * the server 
 * 
 * The "client handlers" consist of two threads: 
 *   - ClientListener - listens for message arrivals from the 
 * socket and forwards them to the ServerDispatcher thread 
 *   - ClientSender - sends messages to the client 
 * 
 * For each accepted client, a ClientListener and ClientSender 
 * threads are created and started. A Client object is also 
 * created to maintain the information about the client and is 
 * added to the ServerDispatcher's clients list. When some 
 * client is disconnected, is it removed from the clients list 
 * and both its ClientListener and ClientSender threads are 
 * interrupted. 
 */ 
 
import java.net.*; 
import java.io.*; 
import java.util.Vector; 
 
/** 
 * NakovChatServer class is the entry point for the server. 
 * It opens a server socket, starts the dispatcher thread and 
 * infinitely accepts client connections, creates threads for 
 * handling them and starts these threads. 
 */ 
public class NakovChatServer { 
    public static final int LISTENING_PORT = 2002; 
    public static String KEEP_ALIVE_MESSAGE = "!keep-alive"; 
    public static int CLIENT_READ_TIMEOUT = 5*60*1000; 
    private static ServerSocket mServerSocket; 
 
    private static ServerDispatcher mServerDispatcher; 
 
    public static void main(String[] args) { 
        // Start listening on the server socket 
        bindServerSocket(); 
 
        // Start the ServerDispatcher thread 
        mServerDispatcher = new ServerDispatcher(); 
        mServerDispatcher.start(); 
 
        // Infinitely accept and handle client connections 
        handleClientConnections(); 
    } 
 
    private static void bindServerSocket() { 
        try { 
            mServerSocket = new ServerSocket(LISTENING_PORT); 
            System.out.println("NakovChatServer started on " + 
                "port " + LISTENING_PORT); 
        } catch (IOException ioe) { 
            System.err.println("Can not start listening on " + 
                "port " + LISTENING_PORT); 
            ioe.printStackTrace(); 
            System.exit(-1); 
        } 
    } 
 
    private static void handleClientConnections() { 
        while (true) { 
            try { 
                Socket socket = mServerSocket.accept(); 
                Client client = new Client(); 
                client.mSocket = socket; 
                ClientListener clientListener = new 
                    ClientListener(client, mServerDispatcher); 
                ClientSender clientSender = 
                    new ClientSender(client, mServerDispatcher); 
                client.mClientListener = clientListener; 
                clientListener.start(); 
                client.mClientSender = clientSender; 
                clientSender.start(); 
                mServerDispatcher.addClient(client); 
            } catch (IOException ioe) { 
                ioe.printStackTrace(); 
            } 
        } 
    } 
} 
 
 
/** 
 * ServerDispatcher class is purposed to listen for messages 
 * received from the clients and to dispatch them to all the 
 * clients connected to the chat server. 
 */ 
class ServerDispatcher extends Thread { 
    private Vector mMessageQueue = new Vector(); 
    private Vector mClients = new Vector(); 
 
    /** 
     * Adds given client to the server's client list. 
     */ 
    public synchronized void addClient(Client aClient) { 
        mClients.add(aClient); 
    } 
 
    /** 
     * Deletes given client from the server's client list if 
     * the client is in the list. 
     */ 
    public synchronized void deleteClient(Client aClient) { 
        int clientIndex = mClients.indexOf(aClient); 
        if (clientIndex != -1) 
            mClients.removeElementAt(clientIndex); 
    } 
 
    /** 
     * Adds given message to the dispatcher's message queue and 
     * notifies this thread to wake up the message queue reader 
     * (getNextMessageFromQueue method). dispatchMessage method 
     * is called by other threads (ClientListener) when a 
     * message is arrived. 
     */ 
    public synchronized void dispatchMessage( 
            Client aClient, String aMessage) { 
        Socket socket = aClient.mSocket; 
        String senderIP = 
            socket.getInetAddress().getHostAddress(); 
        String senderPort = "" + socket.getPort(); 
        aMessage = senderIP + ":" + senderPort + 
            " : " + aMessage; 
        mMessageQueue.add(aMessage); 
        notify(); 
    } 
 
    /** 
     * @return and deletes the next message from the message 
     * queue. If there is no messages in the queue, falls in 
     * sleep until notified by dispatchMessage method. 
     */ 
    private synchronized String getNextMessageFromQueue() 
    throws InterruptedException { 
        while (mMessageQueue.size()==0) 
            wait(); 
        String message = (String) mMessageQueue.get(0); 
        mMessageQueue.removeElementAt(0); 
        return message; 
    } 
 
    /** 
     * Sends given message to all clients in the client list. 
     * Actually the message is added to the client sender 
     * thread's message queue and this client sender thread 
     * is notified to process it. 
     */ 
    private void sendMessageToAllClients( 
            String aMessage) { 
        for (int i=0; i<mClients.size(); i++) { 
            Client client = (Client) mClients.get(i); 
            client.mClientSender.sendMessage(aMessage); 
        } 
    } 
 
    /** 
     * Infinitely reads messages from the queue and dispatches 
     * them to all clients connected to the server. 
     */ 
    public void run() { 
        try { 
            while (true) { 
                String message = getNextMessageFromQueue(); 
                sendMessageToAllClients(message); 
            } 
        } catch (InterruptedException ie) { 
            // Thread interrupted. Stop its execution 
        } 
    } 
} 
 
 
/** 
 * Client class contains information about a client, 
 * connected to the server. 
 */ 
class Client { 
    public Socket mSocket = null; 
    public ClientListener mClientListener = null; 
    public ClientSender mClientSender = null; 
} 
 
 
/** 
 * ClientListener class listens for client messages and 
 * forwards them to ServerDispatcher. 
 */ 
class ClientListener extends Thread { 
    private ServerDispatcher mServerDispatcher; 
    private Client mClient; 
    private BufferedReader mSocketReader; 
 
    public ClientListener(Client aClient, ServerDispatcher 
            aSrvDispatcher) throws IOException { 
        mClient = aClient; 
        mServerDispatcher = aSrvDispatcher; 
        Socket socket = aClient.mSocket; 
        socket.setSoTimeout( 
            NakovChatServer.CLIENT_READ_TIMEOUT); 
        mSocketReader = new BufferedReader( 
            new InputStreamReader(socket.getInputStream()) ); 
    } 
 
    /** 
     * Until interrupted, reads messages from the client 
     * socket, forwards them to the server dispatcher's 
     * queue and notifies the server dispatcher. 
     */ 
    public void run() { 
        try { 
            while (!isInterrupted()) { 
                try { 
                    String message = mSocketReader.readLine(); 
                    if (message == null) 
                        break; 
                    mServerDispatcher.dispatchMessage( 
                        mClient, message); 
                } catch (SocketTimeoutException ste) { 
                    mClient.mClientSender.sendKeepAlive(); 
                } 
            } 
        } catch (IOException ioex) { 
            // Problem reading from socket (broken connection) 
        } 
 
        // Communication is broken. Interrupt both listener and 
        // sender threads 
        mClient.mClientSender.interrupt(); 
        mServerDispatcher.deleteClient(mClient); 
    } 
} 
 
/** 
 * Sends messages to the client. Messages waiting to be sent 
 * are stored in a message queue. When the queue is empty, 
 * ClientSender falls in sleep until a new message is arrived 
 * in the queue. When the queue is not empty, ClientSender 
 * sends the messages from the queue to the client socket. 
 */ 
class ClientSender extends Thread { 
    private Vector mMessageQueue = new Vector(); 
 
    private ServerDispatcher mServerDispatcher; 
    private Client mClient; 
    private PrintWriter mOut; 
 
    public ClientSender(Client aClient, ServerDispatcher 
            aServerDispatcher) throws IOException { 
        mClient = aClient; 
        mServerDispatcher = aServerDispatcher; 
        Socket socket = aClient.mSocket; 
        mOut = new PrintWriter( 
            new OutputStreamWriter(socket.getOutputStream()) ); 
    } 
 
    /** 
     * Adds given message to the message queue and notifies 
     * this thread (actually getNextMessageFromQueue method) 
     * that a message is arrived. sendMessage is always called 
     * by other threads (ServerDispatcher). 
     */ 
    public synchronized void sendMessage(String aMessage) { 
        mMessageQueue.add(aMessage); 
        notify(); 
    } 
 
    /** 
     * Sends a keep-alive message to the client to check if 
     * it is still alive. This method is called when the client 
     * is inactive too long to prevent serving dead clients. 
     */ 
    public void sendKeepAlive() { 
        sendMessage(NakovChatServer.KEEP_ALIVE_MESSAGE); 
    } 
 
    /** 
     * @return and deletes the next message from the message 
     * queue. If the queue is empty, falls in sleep until 
     * notified for message arrival by sendMessage method. 
     */ 
    private synchronized String getNextMessageFromQueue() 
            throws InterruptedException { 
        while (mMessageQueue.size()==0) 
            wait(); 
        String message = (String) mMessageQueue.get(0); 
        mMessageQueue.removeElementAt(0); 
        return message; 
    } 
 
    /** 
     * Sends given message to the client's socket. 
     */ 
    private void sendMessageToClient(String aMessage) { 
        mOut.println(aMessage); 
        mOut.flush(); 
    } 
 
    /** 
     * Until interrupted, reads messages from the message queue 
     * and sends them to the client's socket. 
     */ 
    public void run() { 
        try { 
            while (!isInterrupted()) { 
                String message = getNextMessageFromQueue(); 
                sendMessageToClient(message); 
            } 
        } catch (Exception e) { 
            // Commuication problem 
        } 
 
        // Communication is broken. Interrupt both listener 
        // and sender threads 
        mClient.mClientListener.interrupt(); 
        mServerDispatcher.deleteClient(mClient); 
    } 
}
Back to Internet Programming with Java books's web site