Tcp

Tcp #

Protokół TCP (Transmission Control Protocol) jest jednym z fundamentów internetu. Jest to protokół połączeniowy, co oznacza, że przed wymianą danych nawiązywane jest połączenie między klientem a serwerem. TCP gwarantuje niezawodne i uporządkowane dostarczanie danych, co czyni go idealnym do zastosowań, gdzie integralność danych jest kluczowa, jak np. przesyłanie plików, poczta e-mail. W .NET komunikacja TCP jest obsługiwana przez klasy w przestrzeni nazw System.Net i System.Net.Sockets.

Komunikacja za pomocą TCP w .NET opiera się na dwóch klasach: TcpListener po stronie serwera i TcpClient po stronie klienta.

Serwer #

Serwer nasłuchuje na przychodzące połączenia za pomocą TcpListener. Kiedy połączenie jest akceptowane (AcceptTcpClientAsync), tworzony jest obiekt TcpClient reprezentujący to połączenie. Serwer w przykładzie jest asynchroniczny i może obsługiwać wielu klientów jednocześnie. Dla każdego klienta tworzone jest osobne zadanie (HandleClient), które odczytuje komendy i wysyła odpowiedzi.

Po nawiązaniu połączenia, obie strony komunikują się za pomocą strumienia (NetworkStream), który jest “opakowany” w StreamReader i StreamWriter dla ułatwienia operacji tekstowych.

Zdefiniowany “protokół” jest bardzo prosty - klient wysyła tekstowe komendy (date, time, exit), a serwer odsyła odpowiednią wiadomość. W rzeczywistych aplikacjach protokoły są często bardziej złożone i mogą opierać się na formatach takich jak JSON lub na strukturach binarnych.

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");
        }
    }
}

Klient #

Klient TCP inicjuje połączenie z serwerem. Używa do tego klasy TcpClient i jej metody ConnectAsync, podając adres IP i port serwera. Po połączeniu, podobnie jak serwer, używa strumienia opakowanego w StreamReader i StreamWriter do wysyłania poleceń i odbierania odpowiedzi.

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);
        }
    }
}

Kod źródłowy #

comments powered by Disqus