Laden...

Einfacher TCP Server/Client

Letzter Beitrag vor 3 Monaten 7 Posts 411 Views
Einfacher TCP Server/Client

Guten Morgen, ich habe mir eine kleine TCP Klassenbibliothek erstellt, leider bekomme ich bei dem lesen des Streams folgende Exception:

System.IO.IOException: "Von der Übertragungsverbindung können keine Daten gelesen werden: Ein Verbindungsversuch ist fehlgeschlagen, da die Gegenstelle nach einer bestimmten Zeitspanne nicht richtig reagiert hat, oder die hergestellte Verbindung war fehlerhaft, da der verbundene Host nicht reagiert hat."

Könnt Ihr mir einen Tipp geben, wo mein Problem liegt?

Danke im voraus.

Server.cs:

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

namespace TCP
{
    public class Server
    {
        public event MessageRecived NewMessage;
        public delegate void MessageRecived(string Message);
        bool _running;
        TcpListener Listener;
        public bool Running
        {
            get
            {
                return _running;
            }
            set
            {
                _running = value;
            }
        }
        public Server(int Port)
        {
            Listener = new TcpListener(IPAddress.Any, Port);
        }
        public async void Start()
        {
            try
            {
                Listener.Start();
                Running = true;
                while(Running)
                {
                    var client = await Listener.AcceptTcpClientAsync();
                    Task.Run(() => HandleClient(client));
                }
            }
            catch(SocketException)
            {
                throw;
            }
        }
        public void Stop()
        {
            Listener.Stop();
            Running = false;
        }

        private void HandleClient(TcpClient Client)
        {
            NetworkStream Stream = Client.GetStream();
            while(Client.Connected)
            {
                byte[] msg = new byte[1024];
                Stream.ReadTimeout = 250;
                Stream.Read(msg, 0, msg.Length);
                String message = Encoding.UTF8.GetString(msg);
                NewMessage?.Invoke(message);
            }
        }
    }
}

Client.cs:

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

namespace TCP
{
    public class Client
    {
        TcpClient client;
        IPAddress ip;
        int port;

        public Client(IPAddress IP, int Port)
        {
            this.ip = IP;
            this.port = Port;
            client = new TcpClient();
        }
        public bool SendMessage(string Message)
        {
            bool succsess = false;
            if (!client.Connected)
                connect();
            if(client.Connected)
            {
                NetworkStream stream = client.GetStream();
                byte[] send = Encoding.UTF8.GetBytes(Message);
                stream.Write(send, 0, 1024);
                stream.Flush();

                byte[] buffer = new byte[1024];
                int bytesRead = stream.Read(buffer, 0, buffer.Length);

                succsess = true;
            }
            return succsess;
        }
        void connect()
        {
            client.ConnectAsync(ip, port);            
        }

    }
}

Im Server liest du nur aus dem Stream während dein Client per Read aber auf eine Antwort wartet.
Entsprechend müsste dein Server selbst in den Stream schreiben, sonst wartet der Client bis zum Timeout auf eine Antwort.

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

Danke für die schnelle Antwort.

Ich versuche nun die empfangenen Daten direkt wieder an den Client zu senden. Leider tritt der Fehler schon beim lesen des Streams im Server auf.

Wenn ich keinen ReadTimeout definiere wird das Stream.Read nicht beendet. Fehlt hier vielleicht ein Zeichen, dass dem Read Befehl anzeigt, dass der Stream zu ende ist?

Die häufigste Ursache bei der IOException (mal abgesehen, dass Client etc nicht gestartet ist..) beim Verbindungsaufbau ist die Firewall. Geht die Connection durch eine Firewall, hast entsprechend die Incoming (Server) und Outgoing (Client) Regeln gesetzt?
Entsprechend viele Treffer findet man bei der Suche

=> https://mycsharp.de/forum/threads/95484/tcplistener-an-established-connection-was-aborted-by-the-software-in-your-host-machine 
=> https://mycsharp.de/forum/threads/15768/faq-tcpclient-einfaches-beispiel 
=> https://mycsharp.de/forum/threads/19670/tutorial-client-server-komponente-ueber-tcp-sockets 
=> https://learn.microsoft.com/en-us/dotnet/fundamentals/networking/sockets/tcp-classes

PS: Du solltest async/await korrekt umsetzen, so ist das nix. Das kann und wird ansonsten weitere (nicht dieser) Fehler verursachen.

Danke für die Links, ich habe heute morgen in der Forensuche immer "Kein Ergebnis" angezeigt bekommen.

Ich versuche momentan alles Lokal auf dem Rechner über die 127.0.0.1. Firewall sollte dann normal ja kein Thema sein, oder?

Zur Thematik async/await, ich werde den Task wohl besser in einem eigenen Thread umsetzen, da ich beim empfangen ein Event auslösen will und nicht auf eine Rückgabe der Methode warten will. Somit würde await rausfallen, da die Abfrage nach neuen Nachrichten ja kontinuierlich in der while Schleife läuft und nicht beendet wird.

Ich hoffe Ihr reißt euch jetzt nicht die Haare wegen mir aus 😄

Danke für die Links, ich habe heute morgen in der Forensuche immer "Kein Ergebnis" angezeigt bekommen.

Danke für den Hinweis, vielleicht hab ich neulich was kaputt-optimiert...

Zur Thematik async/await, ich werde den Task wohl besser in einem eigenen Thread umsetzen, da ich beim empfangen ein Event auslösen will und nicht auf eine Rückgabe der Methode warten will. Somit würde await rausfallen, da die Abfrage nach neuen Nachrichten ja kontinuierlich in der while Schleife läuft und nicht beendet wird.

Ich seh jetzt weiterhin kein Grund, dass Du den Thread brauchst (ohne keinen ganzen Kontext zu kennen) - außer Du willst das Gesamtkonstrukut auslagern; und auch das geht simpler mit einem Long-Running Task.
Aber trotzdem hast Du ein asynchrones Verhalten, zB allein durch AcceptTcpClientAsync. Und dann hast nen extra Task, mit dem Du asynchron arbeiten kannst.

Ich versuche momentan alles Lokal auf dem Rechner über die 127.0.0.1. Firewall sollte dann normal ja kein Thema sein, oder?

In Deinem Code arbeitest Du aber mit IPAddress.Any und nicht mit IPAddress.Loopback - und nur Loopback geht nicht über die Firewall. Any schon. Any ist nicht 127.0.0.1 sondern "*", also alles.
Verwendest vielleicht ein Port, den Du nicht verwenden darfst?Sowas wie 5000 is fine, alles unter 1024 ist reserviert / blockiert.


Ich hab mir dazu kurz zwei Demo Konsolen gebaut:

  • Port 20 funktioniert nicht, Port 5000 einwandfrei
  • IPAddress.Any meldet sich die Firewall, IPAddress.Loopback nicht

Server

int port = 5000;
IPAddress localAddr = IPAddress.Loopback;

TcpListener server = new(localAddr, port);

try
{
    server.Start();
    Console.WriteLine($"Server gestartet und hört auf {localAddr}:{port}...");

    while (true)
    {
        Console.WriteLine("Warte auf eine Verbindung...");
        TcpClient client = await server.AcceptTcpClientAsync();
        Console.WriteLine("Ein Client wurde verbunden.");

        // Lese Daten vom Client
        NetworkStream stream = client.GetStream();
        byte[] buffer = new byte[1024];
        int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
        string dataReceived = Encoding.UTF8.GetString(buffer, 0, bytesRead);
        Console.WriteLine($"Empfangen: {dataReceived}");

        // Antwort zurück an den Client senden
        string response = "Hallo, Client!";
        byte[] responseData = Encoding.UTF8.GetBytes(response);
        await stream.WriteAsync(responseData, 0, responseData.Length);
        Console.WriteLine("Antwort gesendet.");

        // Verbindung schließen
        client.Close();
    }
}
catch (Exception e)
{
    Console.WriteLine($"Fehler: {e.Message}");
}
finally
{
    server.Stop();
}

Client

IPAddress server = IPAddress.Loopback;
int port = 5000;

try
{
    TcpClient client = new(server.ToString(), port);
    Console.WriteLine($"Verbunden mit dem Server auf {server}:{port}");

    // Nachricht an den Server senden
    string message = "Hallo, Server!";
    byte[] data = Encoding.UTF8.GetBytes(message);

    NetworkStream stream = client.GetStream();
    await stream.WriteAsync(data, 0, data.Length);
    Console.WriteLine($"Gesendet: {message}");

    // Antwort vom Server lesen
    byte[] buffer = new byte[1024];
    int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
    string response = Encoding.UTF8.GetString(buffer, 0, bytesRead);
    Console.WriteLine($"Antwort vom Server: {response}");

    // Verbindung schließen
    client.Close();
}
catch (Exception e)
{
    Console.WriteLine($"Fehler: {e.Message}");
}

Output:

// Server
Server gestartet und hört auf 127.0.0.1:5000...
Warte auf eine Verbindung...
Ein Client wurde verbunden.
Empfangen: Hallo, Server!
Antwort gesendet.
Warte auf eine Verbindung...

// Client
Verbunden mit dem Server auf 127.0.0.1:5000
Gesendet: Hallo, Server!
Antwort vom Server: Hallo, Client!

PS: TCP Handling als Lernaufgabe ist gut; man sollte wissen, wie das alles funktioniert. Soll das jedoch ne produktive Sache werden, dann sollte man nicht das Rad neu erfinden und auf sowas wie HTTP APIs oder gRPC setzen.

Danke für die vielen Hilfen und Denkansätze.

Ich habe meinen eigentlichen Fehler gefunden.

Bei dem Client habe ich zum senden eine fixe Byte Länge angegeben

Ist:

stream.Write(send, 0, 1024);

Soll:

stream.Write(send, 0, send.Length);