C# Chat Application Over Asynchronous UDP Sockets – Part 1, The Server

by Dave on August 3, 2009

in Articles,Internet

Chat ServerIn this two-part article series I am going to show you how to implement a simple client-server chat application using asynchronous UDP sockets. In this first article of the series I will be focusing on the server of our chat application. The client will be discussed in part two.

What is UDP?

UDP, or User Datagram Protocol, is a connectionless protocol that runs on top of IP networks. UDP does not have an inbuilt mechanism to make sure that all the data being sent is received and received in order – instead such logic must be implemented in the application using the protocol.

UDP is typically used for streaming data such as video or audio, it is also used in VoIP, online gaming, and for broadcasting data over a network.

Application Infrastructure

The infrastructure we are going to use for this chat application is a simple client-server infrastructure as can be seen below:

Client-Server Infrastructure

The server will be the central point which all clients connect to, and the communication between server and clients will be done using a custom data packet which will contain the data being sent over UDP.

The Custom Communication Data Packet

For the server and the client to understand the data they receive from each other, they must communicate using a common data structure – or data packet. This data packet is nothing complicated – it’s just a bunch of data formatted into a structure which can be read and understood by the receiver.

In this example, our data packet must contain the name of the client connecting, the message sent by the client, the length of the name and the length of the message, and finally a field which we’ll call the data identifier, which will tell the client or the server what the received data contains. Below is a diagram which visually explains what the data packet will look like:

Chat Application Communication Protocol

So every time the client and server communicate they do so by sending this data packet over UDP. For example when a client wants to connect to the server it sends a packet with the Data Identifier set to LogIn, the Name set to the user’s name, the Name Length set to the length in bytes of the user’s name, the Message set to null and the Message Length set to 0.

The Packet Class

So now that we know what the custom communication data packet is, we can implement it. Let’s create a class called Packet as shown below:

using System;
using System.Collections.Generic;
using System.Text;

namespace ChatApplication
{
    // ----------------
    // Packet Structure
    // ----------------

    // Description   -> |dataIdentifier|name length|message length|    name   |    message   |
    // Size in bytes -> |       4      |     4     |       4      |name length|message length|

    public enum DataIdentifier
    {
        Message,
        LogIn,
        LogOut,
        Null
    }

    public class Packet
    {
        #region Private Members
        private DataIdentifier dataIdentifier;
        private string name;
        private string message;
        #endregion

        #region Public Properties
        public DataIdentifier ChatDataIdentifier
        {
            get { return dataIdentifier; }
            set { dataIdentifier = value; }
        }

        public string ChatName
        {
            get { return name; }
            set { name = value; }
        }

        public string ChatMessage
        {
            get { return message; }
            set { message = value; }
        }
        #endregion

        #region Methods

        // Default Constructor
        public Packet()
        {
            this.dataIdentifier = DataIdentifier.Null;
            this.message = null;
            this.name = null;
        }

        public Packet(byte[] dataStream)
        {
            // Read the data identifier from the beginning of the stream (4 bytes)
            this.dataIdentifier = (DataIdentifier)BitConverter.ToInt32(dataStream, 0);

            // Read the length of the name (4 bytes)
            int nameLength = BitConverter.ToInt32(dataStream, 4);

            // Read the length of the message (4 bytes)
            int msgLength = BitConverter.ToInt32(dataStream, 8);

            // Read the name field
            if (nameLength > 0)
                this.name = Encoding.UTF8.GetString(dataStream, 12, nameLength);
            else
                this.name = null;

            // Read the message field
            if (msgLength > 0)
                this.message = Encoding.UTF8.GetString(dataStream, 12 + nameLength, msgLength);
            else
                this.message = null;
        }

        // Converts the packet into a byte array for sending/receiving
        public byte[] GetDataStream()
        {
            List dataStream = new List();

            // Add the dataIdentifier
            dataStream.AddRange(BitConverter.GetBytes((int)this.dataIdentifier));

            // Add the name length
            if (this.name != null)
                dataStream.AddRange(BitConverter.GetBytes(this.name.Length));
            else
                dataStream.AddRange(BitConverter.GetBytes(0));

            // Add the message length
            if (this.message != null)
                dataStream.AddRange(BitConverter.GetBytes(this.message.Length));
            else
                dataStream.AddRange(BitConverter.GetBytes(0));

            // Add the name
            if (this.name != null)
                dataStream.AddRange(Encoding.UTF8.GetBytes(this.name));

            // Add the message
            if (this.message != null)
                dataStream.AddRange(Encoding.UTF8.GetBytes(this.message));

            return dataStream.ToArray();
        }

        #endregion
    }
}

This class, when instantiated, will contain a packet of data in a structured format, which will either be received by the server or sent by the server. The class is quite straightforward – it contains properties for accessing each private field, a constructor for populating the fields, an enumeration which defines the Data Identifier – either Message, LogIn, LogOut, or Null, and a method called GetDataStream which returns the packet as a byte array.

The Server

The job of the server is to allow clients to connect to it and then broadcast each message sent by the client. This means that every message a client sends will be received by all other connected clients. This chat application is like a chat room where everyone chats together. It does not support one to one chat.

When the server starts up it will automatically start listening for incoming connections on port 30,000. The code for starting up the server is below:

private void Server_Load(object sender, EventArgs e)
{
    try
    {
        // Initialise the ArrayList of connected clients
        this.clientList = new ArrayList();

        // Initialise the delegate which updates the status
        this.updateStatusDelegate = new UpdateStatusDelegate(this.UpdateStatus);

        // Initialise the socket
        serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

        // Initialise the IPEndPoint for the server and listen on port 30000
        IPEndPoint server = new IPEndPoint(IPAddress.Any, 30000);

        // Associate the socket with this IP address and port
        serverSocket.Bind(server);

        // Initialise the IPEndPoint for the clients
        IPEndPoint clients = new IPEndPoint(IPAddress.Any, 0);

        // Initialise the EndPoint for the clients
        EndPoint epSender = (EndPoint)clients;

        // Start listening for incoming data
        serverSocket.BeginReceiveFrom(this.dataStream, 0, this.dataStream.Length, SocketFlags.None, ref epSender, new AsyncCallback(ReceiveData), epSender);

        lblStatus.Text = "Listening";
    }
    catch (Exception ex)
    {
        lblStatus.Text = "Error";
        MessageBox.Show("Load Error: " + ex.Message, "UDP Server", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
}

Once the server picks up an incoming request, it will asynchronously call the ReceiveData method below:

private void ReceiveData(IAsyncResult asyncResult)
{
    try
    {
        byte[] data;

        // Initialise a packet object to store the received data
        Packet receivedData = new Packet(this.dataStream);

        // Initialise a packet object to store the data to be sent
        Packet sendData = new Packet();

        // Initialise the IPEndPoint for the clients
        IPEndPoint clients = new IPEndPoint(IPAddress.Any, 0);

        // Initialise the EndPoint for the clients
        EndPoint epSender = (EndPoint)clients;

        // Receive all data
        serverSocket.EndReceiveFrom(asyncResult, ref epSender);

        // Start populating the packet to be sent
        sendData.ChatDataIdentifier = receivedData.ChatDataIdentifier;
        sendData.ChatName = receivedData.ChatName;

        switch (receivedData.ChatDataIdentifier)
        {
            case DataIdentifier.Message:
                sendData.ChatMessage = string.Format("{0}: {1}", receivedData.ChatName, receivedData.ChatMessage);
                break;

            case DataIdentifier.LogIn:
                // Populate client object
                Client client = new Client();
                client.endPoint = epSender;
                client.name = receivedData.ChatName;

                // Add client to list
                this.clientList.Add(client);

                sendData.ChatMessage = string.Format("-- {0} is online --", receivedData.ChatName);
                break;

            case DataIdentifier.LogOut:
                // Remove current client from list
                foreach (Client c in this.clientList)
                {
                    if (c.endPoint.Equals(epSender))
                    {
                        this.clientList.Remove(c);
                        break;
                    }
                }

                sendData.ChatMessage = string.Format("-- {0} has gone offline --", receivedData.ChatName);
                break;
        }

        // Get packet as byte array
        data = sendData.GetDataStream();

        foreach (Client client in this.clientList)
        {
            if (client.endPoint != epSender || sendData.ChatDataIdentifier != DataIdentifier.LogIn)
            {
                // Broadcast to all logged on users
                serverSocket.BeginSendTo(data, 0, data.Length, SocketFlags.None, client.endPoint, new AsyncCallback(this.SendData), client.endPoint);
            }
        }

        // Listen for more connections again...
        serverSocket.BeginReceiveFrom(this.dataStream, 0, this.dataStream.Length, SocketFlags.None, ref epSender, new AsyncCallback(this.ReceiveData), epSender);

        // Update status through a delegate
        this.Invoke(this.updateStatusDelegate, new object[] { sendData.ChatMessage });
    }
    catch (Exception ex)
    {
        MessageBox.Show("ReceiveData Error: " + ex.Message, "UDP Server", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
}

This method is the bulk of the server application. The above code is receiving the incoming data packet from the client and parsing it into a Packet object. Then the packet is read and the server determines what to do with the received data by reading the Data Identifier, which in the above code is receivedData.ChatDataIdentifier. The switch statement decides what should be done with the data. If the data is a Message packet, the message will be inserted into the sendData packet to be broadcasted to all the connected clients. If the data is a LogIn packet, the server will store the details of the newly connected client, and if the data is a LogOut packet the server will remove the client from the list of connected clients.

After processing the packet the server will start listening for incoming connections again. This whole process will be repeated for every incoming message from each client.

And that’s it for the server. In part two of this article we will be looking at the client part of our chat application.

I hope you found this article interesting. Please stay tuned for part two and remember to leave your comments below. :) You can also download the full source code listing for the server below – it requires Microsoft Visual Studio 2008.

Dave

Download sourceDownload Chat Server source – 14.8 KB



{ 16 comments… read them below or add one }

Priya April 23, 2010 at 05:42

very nice and helpful article..no comparisons..excellent

Reply

Dave April 23, 2010 at 10:20

I’m glad you found it helpful.

Reply

Valmir April 25, 2010 at 18:56

The server it’s not responding. Please can you help me.

I start the server first and then the client form. But it’s not working.
1.User name:xxx
2.Server IP: ANY
3.Connect

Server (rtxtStatus part) displaying nothing
ClientForm (rtxtConversation) Displaying nothing

Reply

Dave April 26, 2010 at 02:33

The server IP must be the IP address of the pc running the server, so it cannot be just any IP address. Also make sure your firewall is configured to allow communication between the client and server.

Reply

Valmir April 26, 2010 at 22:57

Thank you very mach. It was helpful.

I want to ask you something… I have to do a project “Client/Server (Chat room) with encryption. How can i do . Please can you help me.

Reply

Dave April 27, 2010 at 11:17

To add encryption all you have to do is encrypt the message part of the communication data packet when building the packet. You would also have to store the encrypted message’s length in the message length part of the data packet. Then when reading the packet you would have to decrypt the message to turn it back to readable text. Check out the System.Security.Cryptography namespace for supported encryption methods.

Reply

Mohab June 15, 2010 at 18:58

Dear Dave,
actually i have a question. I am now developing a chatting application using the UDP protocol to send and receive messages. Now i want to send requests like the ordinary applications (eg. msn , yahoo messenger). For example if i want to send a request from my PC, i want in the next side to get a form including “you received a request from the ip …….. do you want to add this IP address to your list?”. So i cannot get the IP……. , and i cannot find a way to solve this out.
Do you have any recommendation, or even answer?

Reply

Mohab June 15, 2010 at 18:59

sorry, i forgot to tell you that i am using C#….

Reply

takaza July 20, 2010 at 10:10

Dear Mr.Dave
How to create chat (with udp) app without server .
client can chat broadcast to other client . Please help me!
because I develop app on window mobile i want app same your app.
thank a lot

Reply

Dave July 20, 2010 at 19:37

To eliminate the server from your chat application will complicate things a bit. You need to do some research on peer-to-peer protocols to help you implement what you want. And also it is probably better if you start a new project from scratch instead of trying to modify this one to work for peer-to-peer.

Reply

takaza July 21, 2010 at 11:35

thank for answer Dr.DAVE

Reply

Miliscent July 23, 2010 at 12:19

Great article Dave. Not many articles focus on keeping sockets open and how to receive messages as they come through. Glad you did :)

Reply

Dave July 23, 2010 at 20:42

Thanks, glad you found it useful.

Reply

Alex July 24, 2010 at 13:19

Hey mate,
Great tutorial however, I found a problem with this code.

If you connect two clients to the server and kill one of them without it actually sending a logout notification, it destroys the serer socket.So when u try to send data from a client that’s still connected, it wont work.

Reply

Dave July 24, 2010 at 23:01

Thanks for pointing that out – I hadn’t noticed it. The code should definitely be made to handle that situation – maybe I’ll try and fix it and post an update. Did you find a workaround yourself?

Reply

Alex July 25, 2010 at 15:47

The only way i found a work around this, which by all means is far from perfect, is to tell server to listen again in the catch part of the code.

Reply

Leave a Comment

{ 1 trackback }

Previous post:

Next post: