Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A simple TCP/IP Chat client/server

0.00/5 (No votes)
3 Nov 2004 5  
An article useful to understand simple thread use, TCP/IP networking, and basic use of XML.

Sample Image - shot1.jpg

You can find updates here.

Introduction

This is a simple TCP/IP chat sample. The solution is made up of three different projects. The main project is "Listener", where you can find the core of this program. In Rete.cs, there are the main methods used to make this program work.

Please note that source code comments (//) are written in Italian.

The most important class is Poller, in rete.cs. In Main.cs is created a Poller object, that is used to connect, poll and write the socket.

In Main.cs is started a thread that has to refresh the GUI; the thread executes:

public void StartGUIRefreshThread()
{
    ThreadStart GuiRefreshDelegate=new ThreadStart(this.GUIRefreshThread);
    GUIRefresh= new Thread(GuiRefreshDelegate);
    GUIRefresh.Name="GUIRefresh";
    GUIRefresh.Start();
}

This is a simple example of using threads. Starting a new thread means running different portions of the code at "the same time". While a thread is running a loop, the "main program" which is another thread executes other instructions.

A ThreadStart object contains a reference to a method that has to be run when the thread starts. If this function does not contain a loop, the thread will stop after a while. If the referenced method contains an infinite loop, the thread will be running as long as the application.

GUIRefresh.Start(); executes the "this.GUIRefreshThread()" method, as specified in "new ThreadStart(this.GUIRefreshThread);".

My "StartGUIRefreshThread" method starts a thread which contains an infinite loop:

public void GUIRefreshThread()
{
    int localDelay, totalDelay=1200, newDataPresentIconDelay=300;

    while (true)
    {
        ...
        Thread.Sleep(localDelay);
    }
}

This checks the Poller status, and displays the appropriate output to the user.

The Poller object handles the TCP connection, using a "raw" Socket object, initialized with the "InizializzaTX()" method if the current application is running as a client, or with "InizializzaRX" if the application is running as a server (listener).

Server case:

public bool InizializzaRX()
{
    if (this.IsListen)
        this.FermaListening();

    this.ListenSocket= new Socket(AddressFamily.InterNetwork, 
                       SocketType.Stream, ProtocolType.Tcp);

    try
    {
        this.ListenSocket.Bind(LIPE);
        this.ListenSocket.Listen(10);
        this.IsListen=true;
        return true;
    }
    
    ...

This code creates a new socket (this.ListenSocket) which is bound to the listening IP address and port (LIPE).

Now the socket is properly configured, but it's not "active". We have to start it to begin listening with the <Socket>.Accept() method, which puts the socket in a "wait state" for client connections.

The propriety <Socket>.Blocking determines whether the execution will stop waiting for client connections or not (true=stop). I use blocking=true. Note that if you use blocking=true in the main thread, your application will hang until a connection is accepted.

private void PollingRX()
{
    string s="Inizio";
    while (s!=MSGTag.ConnectionStop+"\n")
    {
        if (this.UsedSocket==null&&this.ListenSocket!=null)
        {
            this.DebugRefresh("PollingRX: fermo in accept() su"+ this.LIPE.Port);
            this.IsListen=true;
            this.UsedSocket=this.ListenSocket.Accept();
            this.DebugRefresh("PollingRX: Accept() eseguita");
            this.IsConnectedAsRX=true;

            //Comunica il nome al client, come in InizializzaTX() 

            //il client comunica il suo nome al server

            this.PushString(MSGTag.UserName/*+"Server "*/+this.LocalName);
            this.RTM.RemoteIP=this.UsedScocket.RemoteEndPoint.ToString();
            this.RTM.LocalIP=this.LIPE.ToString();
            //Non uso UsedScocket.LocalEndPoint xch� non � definito l'IP locale


            this.ControlClear();
        }

        s=this.CheckAndRetrive();
        int n=0;
        if (s!=null)
            n=s.Length;

        this.DebugRefresh("PollingRX: eseguito ("+n+" bytes)");
        System.Threading.Thread.Sleep(500);
    }
    this.DebugRefresh("PollingRX: ricevuto EXIT");
    this.KillRX();
}

When a client makes a connection, the Accept() returns a new socket, connected to the client, which is used to transfer data, using my CheckAndRetrive() (read from socket) and PushString() (write to socket) methods. See below to read pertinent code.

This loop continues until a "stop string" is received from the client.

Client case:

public void InizializzaTX()
{
    if ((!this.IsConnected)) 
    {
        try
        {
            this.UsedScocket = new Socket(AddressFamily.InterNetwork, 
                               SocketType.Stream, ProtocolType.Tcp);

            this.UsedScocket.Connect(this.RIPE);
            this.IsConnectedAsTX=true;
            this.PushString(MSGTag.UserName+/*"Client "+*/this.LocalName);

            this.ControlClear();
            //E'= a RIPE

            this.RTM.RemoteIP=this.UsedScocket.RemoteEndPoint.ToString();
            this.RTM.LocalIP=this.UsedScocket.LocalEndPoint.ToString();
        }
        ...

This is the dual of RX: a new socket is created, and a connection to a remote IP and port (RIPE) is created.

private void PollingTX()
{
    string s="inizio";
    while (s!=MSGTag.ConnectionStop+"\n")
    {
        if (this.UsedScocket!=null)
        {
            s=this.CheckAndRetrive();
        }
        int n=0;
        if (s!=null)
            n=s.Length;
        this.DebugRefresh("PollingTX: eseguito("+n+" bytes)");
        System.Threading.Thread.Sleep(500);
    }
    this.DebugRefresh("PollingTX: ricevuto EXIT");
    this.KillTX();
}

Very similar to RX... once the socket is connected, communication is "symmetric".

Writing and Reading data

This is how I get a string from the socket (CheckAndRetrive() uses this function to get data from the socket):

public static string GetStringFromSocket(Socket s)
{
    Byte[] recBytes=new Byte[10001];
    int i=0; 
    string st=null;

    if (s.Available>0)
    {
        i=s.Receive(recBytes,0,s.Available,SocketFlags.None);
        char[] ac=Encoding.UTF8.GetChars(recBytes,0,i);
        foreach (char c in ac)
        {
            st+=c.ToString();
        }
    }
    return st;
}

The socket needs to be a connected socket; you have to use a byte array to put in the received data. Then you have to encode it with a encoding mechanism, the same is used to decode the data. I use UTF8 Encoding.

This reads at most a 10001 bytes block from the socket. To read more bytes, a loop is required, something like:

while (s.Available>0){...}

This is how to put a string in a socket:

public static int PutStringInSocket(Socket s, string msg)
{
    Byte[] sendBytes=Encoding.UTF8.GetBytes(Framing.IncapsulaMSG(msg));
    if (s.Connected)
        return s.Send(sendBytes);
    else return 0;
}

Framing.IncapsulaMSG() is simply a method that writes tags in a message to make it simple to interpret it, and returns a string.

Polling Threads

Inside Poller are executed other threads, responsible for receiving/checking and sending data over the TCP connection. Loops inside PollingRX() and PollingTX() run in two different threads.

public bool StartPollingTX()
{
    try
    {
        if (!this.IsRunningPollingTX)
        {
            ThreadStart TXPollingDelegate = new ThreadStart(this.PollingTX);
            TXPolling= new Thread(TXPollingDelegate);
            TXPolling.Name="TXPolling";
            TXPolling.Start();
            this.IsRunningPollingTX=true;
            this.DebugRefresh("PollingTX: (Start) eseguito");
        }
        return true;
    }
    catch
    {
        return false;
    }
}

This is an example that shows how the "Transmission" thread is started. (It runs "this.PollingTX" described above.) The Settings tab is stored in a separate DLL, and so is the config utility. Settings are stored in an XML file.

I know there are many silly things, but this was intended only as an example.

I hope you enjoyed the article.

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