Tutorial 02 :
Client/Server Chat

Tutorial Advanced Screen Shots Download Section

We will use the previous tutorial to write a Chat application. This application allow a client to communicate with all the other clients connected to the server.

The communication is done by implementing a specific protocol between the server and clients. This is used to decode data received and encode datas to send.
Threads are also used for doing action automatically like reading input datas.
 

The basis of client/server was seen in Tutorial 1.
 

In the previous tutorial, a client can send datas only to the server. In a chat application, the client could send datas to anyone connected.

To send datas from a client to another, the datas can goes directly from one client to the other.
Another way is to send datas to the chat server and inform him that datas are for a particular client. We will use the second way for this chat tutorial :


Transmission of a message

The advantage of this is that the client have a single connection (to the server) and can talk with multiple clients. Moreover, the server don't transmit the IP of a client to another one (security).
 

All clients have to connect themself to the server.

The server should listen for client connection. For this, acceptConnection is called in loop in a thread (this method blocks until a connection is made).

Accept client connections

acceptClientsThread = new Thread() {
    public void run()
    {
        while(user.isConnected())
        {
            ChatServerLink client = null;
            //acceptConnection blocks until a connection is made
            if((client = user.acceptConnection() ) != null)
            {
                //A new client is connected
                ...
            }
        }
    }
};


ChatServerLink
class extends ServerLink used in previous tutorial for storing client pseudo, client authentification and a thread used to listen for datas received (this thread will be used later in this tutorial).

ChatServerLink

public class ChatServerLink extends ServerLink
{
    /** Pseudo of the client connected to the Link */
    private String pseudo = null;
    /** Client is authentified ? */
    private boolean authentified = false;

    /** Thread that listen for datas received */
    protected Thread listener = null;

    ...    //Constructor, getters & setters
}

 

To interpret data flow, datas should be encoded in a language understandable by the server and the client.
By using the same language for the client and the server, the sender is sure that his datas sended are well processed by the receiver. For example, a client can send an instruction (to the server) to transmit some datas to another.
 

Before implementing the language used by the chat application, we have to know which instructions will be transmitted from a client to another or towards the server.

This chat application will works like this :

 

We have to encode original instruction to a succession of bytes/characters. Datas encoded could be decoded to extract the original instruction.

Datas will be encoded using tags :
    <TAG>value</TAG>
TAG is a reserved word associated to an instruction.
value is the value of the instruction.

Tag used to implement the language are (in ProtocolEnums):

To send a message from a client (Pseudo1) to another (Pseudo2), the client will encode his message like this :
    <MESSAGE><FROM>Pseudo1</FROM><TO>Pseudo2</TO><BODY>Text</BODY></MESSAGE>
When the server will receive this message, by parsing the datas, he will fing the tag MESSAGE. Value of this tag is <FROM>Pseudo1</FROM><TO>Pseudo2</TO><BODY>Text</BODY>. He already know which client is the sender (because he receive the datas from him), by parsing this value he will find the addressee and the text to send him.

Encoding and decoding are done in Encoder/Decoder classes.
 

First thing is to listen if some datas are received. For this, we will use a thread that check in loop if some datas can be readed.
When some datas can be readed, with process them in processInput by implementing the Protocol interface.
 

Receiving thread

//Listen for datas received
listener = new Thread() {
    public void run()
    {
        boolean end = false;
        while(!end)
        {
            try
            {
                //Call getProtocol.processInput(...) if some datas are received
                user.checkInput();
            }
            catch(IOException e)
            {
                end = true;
            }

            try
            {
                Thread.sleep(20);
            }
            catch(InterruptedException e){}
        }
        user.disconnect();
    }
};
listener.start();


Now, we have to implement our language with the Protocol interface and attach it to our connection with connection.setProtocol(protocol)

Protocol

public interface Protocol
{
    public void processInput(InputStream datas);
}

user.setProtocol(new Protocol(
    public void processInput(InputStream in)
    {
        //Process input by implementing the protocol here
        ...
    }
));

The implementation is different for the server and the client. For example, the server ask for authentification and the client answer the request.

Here is how datas are processed by a client (in Client).
First, he read the datas into a String. Then, he parse the datas as a vector containing tags and their corresponding values. Finally, depending on tag received, we do what should be done.
 

Client side

public void processInput(InputStream in)
{
    //Read inputs
    String datas = null;
    try
    {
        while(in.available() > 0)
            datas += new String(user.readInput());
    }
    catch(IOException e)
    {
        return;
    }
    datas = datas.trim();

    //Decode datas
    Vector<TagValue> queries = decodeTag(datas);    //Using Decoder class (static import)

    //Process inputs
    for(TagValue tagValue : queries)
    {
        switch(tagValue.tag)
        {
            case REQUEST:
                if(Request.valueOf(tagValue.value) == Request.PSEUDO)
                {
                    //Server request authentification
                    ...
                }
                else if(Request.valueOf(tagValue.value) == Request.DISCONNECTION)
                {
                    //Server request disconnection
                    ...
                }
                break;
            case MESSAGE:
                //A new message
                ...
                break;
            case PSEUDO_LIST:
                //List of pseudo
                ...
                break;
            case INFO:
                //Message from the server
                ...
                break;
        }
    }
}

Now, how datas are processed by the Server (in Server).
The server manage authorizations. He blocks datas from client not authentified.
 

Server side

public void processInput(InputStream in)
{
    //Read inputs
    String datas = null;
    try
    {
        while(in.available() > 0)
            datas += new String(client.readInput());
    }
    catch(IOException e)
    {
        return;
    }
    datas = datas.trim();

    //Decode datas
    Vector<TagValue> queries = decodeTag(datas);    //Using Decoder class (static import)

    //Process inputs
    for(TagValue tagValue : queries)
    {
        if(client.isAuthentified())
        {
            switch(tagValue.tag)
            {
                case REQUEST:
                    if(Request.valueOf(tagValue.value) == Request.DISCONNECTION)
                    {
                        //Disconnection of the client
                        ...
                    }
                    break;
                case MESSAGE:
                    //Message to transmit to a client
                    ...
                    break;
            }
        }
        else
        {
            if(tag.tag == Tag.PSEUDO)
            {
                //The client send his pseudo
                ...
            }
            else
            {
                //Block it ! Client is not authentified
                ...
           }
        }
    }
}

 

I've just explain interesting part ie new 'Network' part of this tutorial.
A lot of code is for the GUI (not explained here).

In package Network.Chat :
  IntroFrame : first frame, display author informations and ask if you are the server or a client.
  SettingsFrame : second frame, ask informations like your pseudo, IP and communication port.
  IpPanel : used by SettingsFrame to enter an IP address.
  ChatFrame : GUI.
  Server : code for the server.
  Client : code for the client.

In package Network.Chat.Protocol :
  Encoder : Encode datas
  Decoder : Decode datas.
  ProtocolEnums : contains tag of the communication language.
  ChatServerLink : extends ServerLink to store user properties.

Hope that you like this tutorial !
 

Instead of defining a communication language like we have doing here, you can use RMI (Remote Method Invocation) for doing the same job.

RMI allows you to invokes methods on an object running in another JVM (Java Virtual Machine). That is to say, for examples, two differents Java applications.
 

The chat server runs here in a GUI. Generally, the server have no GUI an runs without human intervention.
Clients register themself to the chat server and have a pseudo/login. They log themself by sending their pseudo/login to the server.
 

In this tutorial, like the previous, we have only seen how to send text (characters).
But, we can send files, pictures ...

This will be done in Tutorial 3, a basic HTTP server.
 

I've run a server and 2 clients (you can connect all clients you wants). These 3 applications runs independently.
I've runs them in the same computer for these screen shots, but they can be executed under differents computers and differents OS.

Here are what the two clients sees :


Clients applications

Here is the server application :


The server application

 

Previous Tutorial

Back

Next turorial

Last modified on 01/07/2010
Copyright © 2004-2010 Jérôme JOUVIE - All rights reserved. http://jerome.jouvie.free.fr/