Internet Programming with Java Course

1.9 : chat /

 

Multithreaded Chat Client/Server Design Diagram

 

Multithreaded Chat Server Implementation Nakov Chat Server

 

/**

* Nakov Chat Server

* (c) Svetlin Nakov, 2002

* http://www.nakov.com

*

* Nakov Chat Server is multithreaded chat server. It accepts multiple clients

* simultaneously and serves them. Clients can send messages to the server.

* When some client send a message to the server, this 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 a messages and sends arrived messages 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 ClientInfo object is also created to contain the

* information about the client. Also the ClientInfo object 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.

*

*

* NakovChatServer class is entry point for the program. It opens a server

* socket, starts the dispatcher thread and infinitely accepts client connections,

* creates threads for handling them and starts these threads.

*/

 

import java.net.*;

import java.io.*;

 

public class NakovChatServer

{

public static final int LISTENING_PORT = 2002;

 

public static void main(String[] args)

{

// Open server socket for listening

ServerSocket serverSocket = null;

try {

serverSocket = new ServerSocket(LISTENING_PORT);

System.out.println("NakovChatServer started on port " + LISTENING_PORT);

} catch (IOException se) {

System.err.println("Can not start listening on port " + LISTENING_PORT);

se.printStackTrace();

System.exit(-1);

}

 

// Start ServerDispatcher thread

ServerDispatcher serverDispatcher = new ServerDispatcher();

serverDispatcher.start();

 

// Accept and handle client connections

while (true) {

try {

Socket socket = serverSocket.accept();

ClientInfo clientInfo = new ClientInfo();

clientInfo.mSocket = socket;

ClientListener clientListener =

new ClientListener(clientInfo, serverDispatcher);

ClientSender clientSender =

new ClientSender(clientInfo, serverDispatcher);

clientInfo.mClientListener = clientListener;

clientInfo.mClientSender = clientSender;

clientListener.start();

clientSender.start();

serverDispatcher.addClient(clientInfo);

} catch (IOException ioe) {

ioe.printStackTrace();

}

}

}

 

}

 

 

/**

* Nakov Chat Server

* (c) Svetlin Nakov, 2002

*

* ServerDispatcher class is purposed to listen for messages received

* from clients and to dispatch them to all the clients connected to the

* chat server.

*/

 

import java.net.*;

import java.util.*;

 

public 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(ClientInfo aClientInfo)

{

mClients.add(aClientInfo);

}

 

/**

* Deletes given client from the server's client list

* if the client is in the list.

*/

public synchronized void deleteClient(ClientInfo aClientInfo)

{

int clientIndex = mClients.indexOf(aClientInfo);

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(ClientInfo aClientInfo, String aMessage)

{

Socket socket = aClientInfo.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.

*/

private synchronized void sendMessageToAllClients(String aMessage)

{

for (int i=0; i<mClients.size(); i++) {

ClientInfo clientInfo = (ClientInfo) mClients.get(i);

clientInfo.mClientSender.sendMessage(aMessage);

}

}

 

/**

* Infinitely reads messages from the queue and dispatch 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

}

}

 

}

 

 

/**

* Nakov Chat Server

* (c) Svetlin Nakov, 2002

*

* ClientInfo class contains information about a client, connected to the server.

*/

 

import java.net.Socket;

 

public class ClientInfo

{

public Socket mSocket = null;

public ClientListener mClientListener = null;

public ClientSender mClientSender = null;

}

 

 

/**

* Nakov Chat Server - (c) Svetlin Nakov, 2002

*

* ClientListener class is purposed to listen for client messages and

* to forward them to ServerDispatcher.

*/

 

import java.io.*;

import java.net.*;

 

public class ClientListener extends Thread

{

private ServerDispatcher mServerDispatcher;

private ClientInfo mClientInfo;

private BufferedReader mIn;

 

public ClientListener(ClientInfo aClientInfo, ServerDispatcher aServerDispatcher)

throws IOException

{

mClientInfo = aClientInfo;

mServerDispatcher = aServerDispatcher;

Socket socket = aClientInfo.mSocket;

mIn = 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()) {

String message = mIn.readLine();

if (message == null)

break;

mServerDispatcher.dispatchMessage(mClientInfo, message);

}

} catch (IOException ioex) {

// Problem reading from socket (communication is broken)

}

 

// Communication is broken. Interrupt both listener and sender threads

mClientInfo.mClientSender.interrupt();

mServerDispatcher.deleteClient(mClientInfo);

}

 

}

 

 

/**

* Nakov Chat Server - (c) Svetlin Nakov, 2002

*

* Sends messages to the client. Messages 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.

*/

 

import java.io.*;

import java.net.*;

import java.util.*;

 

public class ClientSender extends Thread

{

private Vector mMessageQueue = new Vector();

 

private ServerDispatcher mServerDispatcher;

private ClientInfo mClientInfo;

private PrintWriter mOut;

 

public ClientSender(ClientInfo aClientInfo, ServerDispatcher aServerDispatcher)

throws IOException

{

mClientInfo = aClientInfo;

mServerDispatcher = aServerDispatcher;

Socket socket = aClientInfo.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 called by other threads (ServeDispatcher).

*/

public synchronized void sendMessage(String aMessage)

{

mMessageQueue.add(aMessage);

notify();

}

 

/**

* @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

mClientInfo.mClientListener.interrupt();

mServerDispatcher.deleteClient(mClientInfo);

}

 

}

Client For Nakov Chat Server

 

/**

* Nakov Chat Client

* (c) Svetlin Nakov, 2002

* http://www.nakov.com

*

* NakovChatClient connects to Nakov Chat Server and prints all the messages

* received from the server. It also allows the user to send messages to the

* server. NakovChatClient thread reads messages and print them to the standard

* output. Sender thread reads messages from the standard input and sends them

* to the server.

*/

 

import java.io.*;

import java.net.*;

 

public class NakovChatClient

{

public static final String SERVER_HOSTNAME = "localhost";

public static final int SERVER_PORT = 2002;

 

public static void main(String[] args)

{

BufferedReader in = null;

PrintWriter out = null;

try {

// Connect to Nakov Chat Server

Socket socket = new Socket(SERVER_HOSTNAME, SERVER_PORT);

in = new BufferedReader(

new InputStreamReader(socket.getInputStream()));

out = new PrintWriter(

new OutputStreamWriter(socket.getOutputStream()));

System.out.println("Connected to server " +

SERVER_HOSTNAME + ":" + SERVER_PORT);

} catch (IOException ioe) {

System.err.println("Can not establish connection to " +

SERVER_HOSTNAME + ":" + SERVER_PORT);

ioe.printStackTrace();

System.exit(-1);

}

 

// Create and start Sender thread

Sender sender = new Sender(out);

sender.setDaemon(true);

sender.start();

 

try {

// Read messages from the server and print them

String message;

while ((message=in.readLine()) != null) {

System.out.println(message);

}

} catch (IOException ioe) {

System.err.println("Connection to server broken.");

ioe.printStackTrace();

}

 

}

}

 

class Sender extends Thread

{

private PrintWriter mOut;

 

public Sender(PrintWriter aOut)

{

mOut = aOut;

}

 

/**

* Until interrupted reads messages from the standard input (keyboard)

* and sends them to the chat server through the socket.

*/

public void run()

{

try {

BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

while (!isInterrupted()) {

String message = in.readLine();

mOut.println(message);

mOut.flush();

}

} catch (IOException ioe) {

// Communication is broken

}

}

}