Tcp

Tcp #

The TCP (Transmission Control Protocol) is one of the foundations of the internet. It is a connection-oriented protocol, which means that a connection is established between a client and a server before any data is exchanged. TCP guarantees reliable and ordered data delivery, which makes it ideal for applications where data integrity is crucial, such as file transfers or e-mail. In .NET, TCP communication is handled by classes in the System.Net and System.Net.Sockets namespaces.

Communication using TCP in .NET is based on two classes: TcpListener on the server side and TcpClient on the client side.

Server #

The server listens for incoming connections using TcpListener. When a connection is accepted (AcceptTcpClientAsync), a TcpClient object is created to represent that connection. The server in the example is asynchronous and can handle multiple clients simultaneously. For each client, a separate task (HandleClient) is created, which reads commands and sends responses.

After establishing a connection, both sides communicate using a stream (NetworkStream), which is “wrapped” in a StreamReader and StreamWriter to facilitate text operations.

The defined “protocol” is very simple - the client sends text commands (date, time, exit), and the server sends back the appropriate message. In real-world applications, protocols are often more complex and may be based on formats like JSON or binary structures.

using System.Globalization;
using System.Net;
using System.Net.Sockets;

namespace Server;

public static class Program
{
    const int Port = 5000;
    public static async Task Main()
    {
        CancellationTokenSource cts = new CancellationTokenSource();
        var ipEndPoint = new IPEndPoint(IPAddress.Any, Port);
        using var listener = new TcpListener(ipEndPoint);
        
        var waitForAnyKey = Task.Run(() =>
        {
            Console.WriteLine("Wait any key to exit");
            Console.ReadKey(true);
            cts.Cancel();
        });

        await AcceptClients(listener, cts.Token);
        await waitForAnyKey;
    }

    private static async Task AcceptClients(TcpListener listener, CancellationToken token = default)
    {
        listener.Start(backlog: 10);
        var clients = new List<Task>();
        while (true)
        {
            try
            {
                TcpClient client = await listener.AcceptTcpClientAsync(token);
                clients.Add(HandleClient(client, token));
                clients.RemoveAll(task => task.IsCompleted);
            }
            catch (OperationCanceledException)
            {
                break;
            }
        }

        listener.Stop();
        try
        {
            await Task.WhenAll(clients);
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
        }
    }

    private static async Task HandleClient(TcpClient client, CancellationToken token = default)
    {
        Console.WriteLine("New client connected");
        try
        {
            var stream = client.GetStream();
            var reader = new StreamReader(stream);
            var writer = new StreamWriter(stream) { AutoFlush = true };
            while (await reader.ReadLineAsync(token) is { } command)
            {
                Console.WriteLine($"Received command: {command}");
                switch (command)
                {
                    case "date":
                        string message = DateTime.Now.ToString("d", CultureInfo.InvariantCulture);
                        await writer.WriteLineAsync(message.ToCharArray(), token);
                        break;
                    case "time":
                        message = DateTime.Now.ToString("t", CultureInfo.InvariantCulture);
                        await writer.WriteLineAsync(message.ToCharArray(), token);
                        break;
                    case "exit":
                        return;
                    default:
                        await writer.WriteLineAsync("Unknown command".ToCharArray(), token);
                        break;
                }
            }
        }
        catch (OperationCanceledException)
        {
            // Operation was cancelled, which is expected during shutdown.
        }
        catch (IOException)
        {
            // Client disconnected abruptly.
        }
        catch (Exception e)
        {
            Console.WriteLine($"Error handling client: {e.Message}");
        }
        finally
        {
            client.Dispose();
            Console.WriteLine("Client disconnected");
        }
    }
}

Client #

The TCP client initiates a connection with the server. It uses the TcpClient class and its ConnectAsync method, providing the server’s IP address and port. After connecting, similar to the server, it uses a stream wrapped in a StreamReader and StreamWriter to send commands and receive responses.

using System.Net;
using System.Net.Sockets;

namespace Client;

public static class Program
{
    const int Port = 5000;
    public static async Task Main()
    {
        var ipEndPoint = new IPEndPoint(IPAddress.Loopback, Port);
        using var client = new TcpClient();
        await client.ConnectAsync(ipEndPoint);
        Console.WriteLine("Client connected");
        
        var reader = new StreamReader(client.GetStream());
        var writer = new StreamWriter(client.GetStream()) { AutoFlush = true };

        while (Console.ReadLine() is { } line)
        {
            await writer.WriteLineAsync(line);
            var response = await reader.ReadLineAsync();
            if (response is null) break;
            Console.WriteLine(response);
        }
    }
}

Source Code #

comments powered by Disqus