Click here to Skip to main content
15,881,424 members
Articles / Programming Languages / C#
Article

A Chat Application Using Asynchronous TCP Sockets

Rate me:
Please Sign up or sign in to vote.
4.90/5 (64 votes)
28 Dec 20063 min read 473.2K   33.1K   244   113
This article will discuss a chat application using asynchronous TCP sockets, in C#.

Image 1

Introduction

In this article, I will discuss a chat application using asynchronous TCP sockets in C#. In the next part of this article, I will present an asynchronous UDP socket based chat application.

TCP Asynchronous Sockets

TCP asynchronous sockets have a Begin and End appended to the standard socket functions, like BeginConnect, BeginAccept, BeginSend, and BeginReceive. Let's take a look at one of them:

C#
IAsyncResult BeginAccept(AsyncCallback callback, object state);

The AsyncCallback function is called when the function completes. Just as events can trigger delegates, .NET also provides a way for methods to trigger delegates. The .NET AsyncCallback class allows methods to start an asynchronous function and supply a delegate method to call when the asynchronous function completes.

The state object is used to pass information between the BeginAccept and the corresponding AsyncCallback function.

The following shows an implementation of it:

C#
Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream,
                         ProtocolType.Tcp);
IPEndPoint iep = new IPEndPoint(IPAddress.Any, 9050);
sock.Bind (iep);
sock.Listen (5);
sock.BeginAccept (new AsyncCallback(CallAccept), sock);

private void CallAccept(IAsyncResult iar)
{
    Socket server = (Socket)iar.AsyncState;
    Socket client = server.EndAccept(iar);
}

The original sock object is retrieved in CallAccept by using the AsyncState property of IAsyncResult. Once we have the original socket (on which the operation completed), we can get the client socket and perform operations on it.

All other asynchronous socket functions work like this, you should look on MSDN for further details. There are a few nice articles explaining the details on the net, so kindly search for them as well.

Getting Started

For exchange of messages between the client and the server, both of them use the following trivial commands:

C#
//The commands for interaction between the server and the client
enum Command
{
    //Log into the server
    Login,      
    //Logout of the server
    Logout,
    //Send a text message to all the chat clients     
    Message,    
    //Get a list of users in the chat room from the server
    List
}

The data structure used to exchange data between the client and the server is shown below. Sockets transmit and receive data as an array of bytes; the overloaded constructor and the ToByte member function performs this conversion.

C#
//The data structure by which the server and the client interact with 
//each other
class Data
{
    //Default constructor
    public Data()
    {
        this.cmdCommand = Command.Null;
        this.strMessage = null;
        this.strName = null;
    }

    //Converts the bytes into an object of type Data
    public Data(byte[] data)
    {
        //The first four bytes are for the Command
        this.cmdCommand = (Command)BitConverter.ToInt32(data, 0);

        //The next four store the length of the name
        int nameLen = BitConverter.ToInt32(data, 4);

        //The next four store the length of the message
        int msgLen = BitConverter.ToInt32(data, 8);

        //Makes sure that strName has been passed in the array of bytes
        if (nameLen > 0)
            this.strName = Encoding.UTF8.GetString(data, 12, nameLen);
        else
            this.strName = null;

        //This checks for a null message field
        if (msgLen > 0)
            this.strMessage = Encoding.UTF8.GetString(data, 
                              12 + nameLen, msgLen);
        else
            this.strMessage = null;
    }

    //Converts the Data structure into an array of bytes
    public byte[] ToByte()
    {
        List<byte> result = new List<byte>();

        //First four are for the Command
        result.AddRange(BitConverter.GetBytes((int)cmdCommand));

        //Add the length of the name
        if (strName != null)
            result.AddRange(BitConverter.GetBytes(strName.Length));
        else
            result.AddRange(BitConverter.GetBytes(0));

        //Length of the message
        if (strMessage != null)
            result.AddRange(BitConverter.GetBytes(strMessage.Length));
        else
            result.AddRange(BitConverter.GetBytes(0));

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

        //And, lastly we add the message text to our array of bytes
        if (strMessage != null)
            result.AddRange(Encoding.UTF8.GetBytes(strMessage));

        return result.ToArray();
    }

    //Name by which the client logs into the room
    public string strName;
    //Message text
    public string strMessage;
    //Command type (login, logout, send message, etc)
    public Command cmdCommand;
}

TCP Server

The server application listens on a particular port and waits for the clients. The clients connect to the server and join the room. The client then sends a message to the server, the server then sends this to all the users in the room.

The server application has the following data members:

C#
//The ClientInfo structure holds 
//the required information about every
//client connected to the server
struct ClientInfo
{
    //Socket of the client
    public Socket socket;
    //Name by which the user logged into the chat room
    public string strName;
}

//The collection of all clients logged 
//into the room (an array of type ClientInfo)
ArrayList clientList;

//The main socket on which the server listens to the clients
Socket serverSocket;

byte[] byteData = new byte[1024];

Here is how it starts listening for the clients and accepts the incoming requests:

C#
private void Form1_Load(object sender, EventArgs e)
{
    try
    {
        //We are using TCP sockets
        serverSocket = new Socket(AddressFamily.InterNetwork, 
                                  SocketType.Stream, 
                                  ProtocolType.Tcp);
        
        //Assign the any IP of the machine and listen on port number 1000
        IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Any, 1000);

        //Bind and listen on the given address
        serverSocket.Bind(ipEndPoint);
        serverSocket.Listen(4);

        //Accept the incoming clients
        serverSocket.BeginAccept(new AsyncCallback(OnAccept), null);
    }
    catch (Exception ex)
    { 
        MessageBox.Show(ex.Message, "SGSserverTCP", 
            MessageBoxButtons.OK, MessageBoxIcon.Error); 
    }
}

With IPAddress.Any, we specify that the server should accept client requests coming on any interface. To use any particular interface, we can use IPAddress.Parse (“192.168.1.1”) instead of IPAddress.Any. The Bind function then bounds the serverSocket to this IP address. The Listen function makes the server wait for the clients, the value passed to Listen specifies how many incoming client requests will be queued by the operating system. If there are pending client requests and another comes in, then it will be rejected.

Below is the corresponding OnAccept function:

C#
private void OnAccept(IAsyncResult ar)
{
    try
    {
        Socket clientSocket = serverSocket.EndAccept(ar);

        //Start listening for more clients
        serverSocket.BeginAccept(new AsyncCallback(OnAccept), null);

        //Once the client connects then start 
        //receiving the commands from her
        clientSocket.BeginReceive(byteData, 0, 
            byteData.Length, SocketFlags.None, 
            new AsyncCallback(OnReceive), clientSocket);
    }
    catch (Exception ex)
    { 
        MessageBox.Show(ex.Message, "SGSserverTCP", 
            MessageBoxButtons.OK, MessageBoxIcon.Error); 
    }
}

EndAccept returns the clientSocket object of the connecting client. We then again start listening for more incoming client requests by making a call to serverSocket.BeginAccept; this is essential as the server won’t be able to process any other incoming client request without it.

With BeginReceive we start receiving the data that will be sent by the client. Note that we pass clientSocket as the last parameter of BeginReceive; the AsyncCallback OnReceive gets this object via the AsyncState property of IAsyncResult, and it then processes the client requests (login, logout, and send messages to the users). Please see the code attached to understand the implementation of OnReceive.

TCP Client

Some of the data members used by the client are:

C#
//The main client socket
public Socket clientSocket;
//Name by which the user logs into the room
public string strName;

private byte[] byteData = new byte[1024];

The client firstly connects to the server:

C#
try
{
    //We are using TCP sockets
    clientSocket = new Socket(AddressFamily.InterNetwork,
                   SocketType.Stream, ProtocolType.Tcp);

    IPAddress ipAddress = IPAddress.Parse(txtServerIP.Text);
    //Server is listening on port 1000
    IPEndPoint ipEndPoint = new IPEndPoint(ipAddress, 1000);

    //Connect to the server
    clientSocket.BeginConnect(ipEndPoint, 
        new AsyncCallback(OnConnect), null);
}
catch (Exception ex)
{ 
    MessageBox.Show(ex.Message, "SGSclient", 
                    MessageBoxButtons.OK, 
                    MessageBoxIcon.Error); 
}

Once connected, a login message is sent to the server. And after that, we send a list message to get the names of clients in the chat room.

C#
//Broadcast the message typed by the user to everyone
private void btnSend_Click(object sender, EventArgs e)
{
    try
    {
        //Fill the info for the message to be send
        Data msgToSend = new Data();
        
        msgToSend.strName = strName;
        msgToSend.strMessage = txtMessage.Text;
        msgToSend.cmdCommand = Command.Message;

        byteData = msgToSend.ToByte();

        //Send it to the server
        clientSocket.BeginSend (byteData, 0, byteData.Length, 
            SocketFlags.None, 
            new AsyncCallback(OnSend), null);

        txtMessage.Text = null;
    }
    catch (Exception)
    {
        MessageBox.Show("Unable to send message to the server.", 
                        "SGSclientTCP: " + strName, 
                        MessageBoxButtons.OK, 
                        MessageBoxIcon.Error);
    }  
}

The message typed by the user is send as a command message to the server which then sends it to all other users in the chat room.

Upon receiving a message, the client processes it accordingly (depending on whether it’s a login, logout, command, or a list message). The code for this is fairly straightforward, kindly see the attached project.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionThank you! Pin
Member 1462743625-Mar-20 14:50
Member 1462743625-Mar-20 14:50 
QuestionHow you know when Converts the bytes into an object of type Data is four byte? Pin
Member 138579515-Jun-18 1:17
Member 138579515-Jun-18 1:17 
Questionhelp Pin
Member 132376233-Jun-17 11:54
Member 132376233-Jun-17 11:54 
AnswerRe: help Pin
Ravi Bhavnani3-Jun-17 12:31
professionalRavi Bhavnani3-Jun-17 12:31 
QuestionNeed to done Private chat Functionality here Pin
Member 1229371610-Feb-17 6:15
Member 1229371610-Feb-17 6:15 
PraiseThanks a lot Pin
suriva4-Oct-16 15:29
suriva4-Oct-16 15:29 
Questionhelp me Pin
Member 1254161024-May-16 7:22
Member 1254161024-May-16 7:22 
Questionindividual chat with multiple user Pin
Member 110777043-Mar-16 19:50
Member 110777043-Mar-16 19:50 
Bugexception cross threading Pin
Member 437697020-Oct-15 0:23
Member 437697020-Oct-15 0:23 
GeneralRe: exception cross threading Pin
Member 41803404-Dec-16 0:30
Member 41803404-Dec-16 0:30 
GeneralRe: exception cross threading Pin
Member 137140318-Aug-18 10:08
Member 137140318-Aug-18 10:08 
QuestionWhy? Pin
Member 115689151-Apr-15 19:39
Member 115689151-Apr-15 19:39 
AnswerRe: Why? Pin
Orhan Mamedov9-Apr-15 3:06
Orhan Mamedov9-Apr-15 3:06 
GeneralRe: Why? Pin
Lợi4-Jan-16 4:48
Lợi4-Jan-16 4:48 
GeneralRe: Why? Pin
Member 1084467019-Jan-16 20:58
Member 1084467019-Jan-16 20:58 
QuestionIndex and count must refer to a location within the buffer. Parameter name: bytes Pin
Cool Smith17-Mar-15 7:03
Cool Smith17-Mar-15 7:03 
I am getting the error

Index and count must refer to a location within the buffer. Parameter name: bytes


at the line

_Message = Encoding.UTF8.GetString(data, 12 + nameLen, msgLen) <-- error line


VB
'Converts the bytes into an object of type Data
Public Sub New(ByVal data As Byte())
Try
    'The first four bytes are for the Command
    _Command = DirectCast(BitConverter.ToInt32(data, 0), Commands)

    'The next four store the length of the name
    Dim nameLen As Integer = BitConverter.ToInt32(Data, 4)

    'The next four store the length of the message
    Dim msgLen As Integer = BitConverter.ToInt32(Data, 8)

    'This check makes sure that ClientName has been passed in the array of bytes
    If nameLen > 0 Then
        _ClientName = Encoding.UTF8.GetString(Data, 12, nameLen)
    Else
        _ClientName = Nothing
    End If

    'This checks for a null message field
    If msgLen > 0 Then
        _Message = Encoding.UTF8.GetString(data, 12 + nameLen, msgLen) <--  error line
    Else
        _Message = Nothing
    End If
Catch ex As Exception
    Throw
End Try


what is the problem?
Questionthe server adress please Pin
Member 1046055015-Jan-15 4:04
Member 1046055015-Jan-15 4:04 
AnswerRe: the server adress please Pin
PIEBALDconsult15-Jan-15 4:34
mvePIEBALDconsult15-Jan-15 4:34 
GeneralRe: the server adress please Pin
Member 1046055015-Jan-15 12:45
Member 1046055015-Jan-15 12:45 
GeneralRe: the server adress please Pin
Mr.Indefatigable3-Jan-16 14:36
Mr.Indefatigable3-Jan-16 14:36 
QuestionProgram Pin
Member 113620618-Jan-15 7:45
Member 113620618-Jan-15 7:45 
AnswerRe: Program Pin
Member 1220576714-Dec-15 22:33
Member 1220576714-Dec-15 22:33 
Questioncheck processing elements based on this demo code Pin
Member 1050906815-Jan-14 3:55
Member 1050906815-Jan-14 3:55 
QuestionThank you very much Pin
le ba quy29-Sep-13 23:55
le ba quy29-Sep-13 23:55 
QuestionReconnect to server Pin
wiswalld3-Apr-13 6:04
wiswalld3-Apr-13 6:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.