Simple Threaded TCP Server in C#

By FoxLearn 1/16/2025 3:36:00 AM   60
In this tutorial, we'll walk through building a simple threaded TCP server in C#.

Building a Threaded TCP Server in C#

Our goal today is to create a TCP server that accepts client connections, sends and receives data, and spawns a separate thread for each client. While in theory, the server can accept as many connections as needed, the practical limitation is the number of threads the system can handle before performance starts to degrade.

Setting Up the Server

We’ll begin by setting up a basic server class. This class will use TcpListener to handle the socket communication and a separate Thread to listen for incoming client connections.

using System;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using System.Net;

namespace TCPServerTutorial
{
    class Server
    {
        private TcpListener tcpListener;
        private Thread listenThread;

        public Server()
        {
            // Initializes the TcpListener to listen on all IP addresses, on port 3000
            this.tcpListener = new TcpListener(IPAddress.Any, 3000);
            // Creates a new thread to handle client connections
            this.listenThread = new Thread(new ThreadStart(ListenForClients));
            this.listenThread.Start();
        }
    }
}

In this setup:

  • TcpListener is used to listen for incoming client connections on port 3000.
  • A separate thread, listenThread, is started to manage the client connections.

Listening for Clients

The ListenForClients method will continuously listen for incoming client connections. When a connection is established, the server spawns a new thread to handle communication with the client.

private void ListenForClients()
{
    // Start listening for incoming client connections
    this.tcpListener.Start();

    while (true)
    {
        // Blocks until a client connects
        TcpClient client = this.tcpListener.AcceptTcpClient();

        // Create a thread to handle communication with the connected client
        Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClientComm));
        clientThread.Start(client);
    }
}

In this example:

  • tcpListener.Start() begins listening for client connections.
  • AcceptTcpClient() blocks the main thread until a client connects.
  • Once a connection is made, the server creates a new thread (clientThread) to handle the interaction with the client. The HandleClientComm method will process this interaction.

Handling Client Communication

The HandleClientComm method is responsible for reading data from the connected client. It runs in a loop, continuously receiving messages until the client disconnects or an error occurs.

private void HandleClientComm(object client)
{
    TcpClient tcpClient = (TcpClient)client;
    NetworkStream clientStream = tcpClient.GetStream();

    byte[] message = new byte[4096];
    int bytesRead;

    while (true)
    {
        bytesRead = 0;

        try
        {
            // Blocks until a client sends data
            bytesRead = clientStream.Read(message, 0, 4096);
        }
        catch
        {
            // If an error occurs, break the loop
            break;
        }

        if (bytesRead == 0)
        {
            // If no data is read, the client has disconnected
            break;
        }

        // Successfully received data; process it
        ASCIIEncoding encoder = new ASCIIEncoding();
        System.Diagnostics.Debug.WriteLine(encoder.GetString(message, 0, bytesRead));
    }

    // Clean up after client disconnects
    tcpClient.Close();
}

In this example:

  • The method reads data from the NetworkStream, which represents the stream of bytes sent by the client.
  • The loop continues as long as data is being received. If the client disconnects, the loop breaks.
  • If data is successfully received, it’s converted to a string and printed to the debug console.

Sending Data to the Client

Once we’ve handled reading data from the client, let’s see how to send data back to the client.

NetworkStream clientStream = tcpClient.GetStream();
ASCIIEncoding encoder = new ASCIIEncoding();
byte[] buffer = encoder.GetBytes("Hello Client!");

clientStream.Write(buffer, 0, buffer.Length);
clientStream.Flush();

In this example:

  • We get the NetworkStream from the TcpClient object.
  • The data (in this case, the string “Hello Client!”) is converted to a byte array using ASCIIEncoding.
  • The Write method sends the byte array to the client, and Flush ensures the data is sent immediately.

Creating a Simple TCP Client

Though this tutorial focuses on the server, let's briefly look at how you can create a simple client that connects to the server and sends data.

TcpClient client = new TcpClient();

IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 3000);

client.Connect(serverEndPoint);

NetworkStream clientStream = client.GetStream();

ASCIIEncoding encoder = new ASCIIEncoding();
byte[] buffer = encoder.GetBytes("Hello Server!");

clientStream.Write(buffer, 0, buffer.Length);
clientStream.Flush();

You've built a basic threaded TCP server that accepts client connections, handles communication in separate threads, and sends data back to clients.

While the mechanics of setting up a threaded server are straightforward, a real-world application will need a protocol for structured communication between clients and the server.