Laden...

Verzögerung in der TCP Übertragung

Letzter Beitrag vor einem Jahr 14 Posts 1.144 Views
Verzögerung in der TCP Übertragung

Servus zusammen,

folgendes Problem: Ich sende per SSL-Stream Daten an einen Endpunkt. Soweit auch alles ok, wenn nun meine App (ASP Core Web-API)aber eine Zeit lang läuft, dann dauert die Übertragung der Daten teils über 30 Sekunden, zumindest macht es den Eindruck. Nach dem Neustart der API ist die Übertragung wieder wie erwartet. Ich weiß leider nicht wo genau ich bei der Fehlersuche am besten ansetze.

Hiermit sende ich, das Loggen findet also erst statt, wenn der Sendevorgang erfolgreich war.

if (shell.Stream.WriteAsync(sendBytes).IsCompletedSuccessfully)
{
	await Logger.LogTCP(shell.Endpoint, "-> " + request.ToXAML());
	shell.Stream.Flush();      
}

shell.Stream ist vom Typ System.Net.Security.SslStream

In meinen Logs steht als Sendezeit beispielsweise 12:00:00, beim Empfänger kommt die Nachricht allerdings erst 12:00:30 an.

An unterschiedlich eingestellter Zeit kann es nicht liegen, da ja wie gesagt nach einem Neustart meiner API die Übertragung innerhalb weniger Millisekunden ankommt.

Irgendwo bei mir scheint also ein Puffer oder was auch immer voll zu laufen, aber wo suche ich da am besten?

Dann solltest du mal mit Wireshark  o.ä. schauen, wann die Nachrichten verschickt werden (ob es also an deinem Programm oder am Netzwerk liegt).

Die Verwendung von async/await ist hier nicht korrekt und kann sowas auslösen.

Korrekt wäre eher

await shell.Stream.WriteAsync(sendBytes).ConfigureAwait(false);
shell.Stream.Flush();

Task.IsCompletedSuccessfully hat ein anderes Verwendungsziel; nicht sowas.

Die 30 Sek Delay werden durch die falsche Verwendung des Tasks kommen; nämlich das Task Timeout Delay.

Einstieg zu Tasks und async/await: Task-based asynchronous programming

Zitat von Th69

Dann solltest du mal mit Wireshark  o.ä. schauen, wann die Nachrichten verschickt werden (ob es also an deinem Programm oder am Netzwerk liegt).

Da bin ich schon was länger dran, hier hapert es daran, dass da wahnsinnig viel Traffic von der IP kommt und es schwierig ist den richtigen rauszufiltern. Da alles verschlüsselt ist und ich noch keine funktionierende Anleitung zum Decrypten gefunden habe, finde ich die entsprechenden Messages leider nicht.

Zitat von Abt

await shell.Stream.WriteAsync(sendBytes).ConfigureAwait(false);
shell.Stream.Flush();

Das ist nur ein weiterer Versuch das Problem zu lösen, der ursprüngliche Code war

await shell.Stream.WriteAsync();

Es sind auch nicht immer 30 Sekunden Delay. Mal sind es 12, mal über eine Minute.

Dann zeig mal Deinen vollständigen Original-Code. Ist Käse mit Try-and-Error-Snippets zu hantieren.

Zitat von Abt

Dann zeig mal Deinen vollständigen Original-Code. Ist Käse mit Try-and-Error-Snippets zu hantieren.

Das ist meine TCPServer Klasse:

    public class TCPServer
    {
        ////Logger Logger = new Logger();
        static X509Certificate serverCertificate = null;
        static TcpListener listener;

        private StringBuilder sb = new StringBuilder();
        private byte[] buffer = new byte[2048];

        public List<SslShell> endpoints = new List<SslShell>();

        public static Action<TaskSiteInformation> onStationInformationRecieved;
        public static Action<TaskSiteInformation> onNoShellFound;
        public TCPServer()
        {
            RatioTransactionHandler.onTCPMessageSend += Send;
            DataController.onPricingSend += Send;
            TaskServiceHandler.onStationInformationRequested += Send;
        }

        public async Task Listen()
        {
            await Logger.LogTcpConnections("LISTENER STARTED", " LOCAL", null);
            serverCertificate = GetServerCert();
            listener = new TcpListener(***, ***);
            listener.Start(2000);
            listener.BeginAcceptTcpClient(Listen_Callback, listener);
        }

        private async void Listen_Callback(IAsyncResult ar)
        {
            TcpClient client = new TcpClient();
            string endpoint = "unknown";
            try
            {
                listener = (TcpListener)ar.AsyncState;
                client = listener.EndAcceptTcpClient(ar);
                endpoint = client.Client.RemoteEndPoint.ToString();
                SslStream sslStream = new SslStream(client.GetStream(), false);
                SslServerAuthenticationOptions options = new SslServerAuthenticationOptions()
                {
                    ClientCertificateRequired = false,
                    RemoteCertificateValidationCallback = CertificateValidationCallback,
                    ServerCertificate = serverCertificate
                };
                try
                {
                    sslStream.AuthenticateAsServer(options);
                }
                catch (AuthenticationException e)
                {
                    await Logger.LogTCP(" LOCAL", "Exception: " + e.Message);
                    await Logger.LogTCP(" LOCAL", "Authentication failed - closing the connection.");
                    client.Close();
                    client.Dispose();
                    listener.BeginAcceptTcpClient(Listen_Callback, listener);
                    return;
                }
                await Logger.LogTcpConnections("CONNECTION ESTABLISHED", endpoint, null);

                SslShell shell = new SslShell()
                {
                    Stream = sslStream,
                    Endpoint = endpoint
                };

                shell.Stream.BeginRead(buffer, 0, buffer.Length, new AsyncCallback(SSLReadCallback), shell);
            }
            catch (Exception ex)
            {
                await Logger.LogTCP(endpoint, ex.Message);
                try
                {
                    await Logger.LogTCP(endpoint, ex.StackTrace);
                }
                catch (Exception)
                {

                }
                client.Close();
                await Logger.LogTCP(endpoint, "CONNECTION CLOSED");
                listener.BeginAcceptTcpClient(Listen_Callback, listener);
            }
            listener.BeginAcceptTcpClient(Listen_Callback, listener);
        }

        private async void SSLReadCallback(IAsyncResult ar)
        {
            **Interna**
        }

        public async void Send(UnsolicitedRequest request)
        {
            try
            {                
                Database db = new Database();
                SslShell shell = GetShellForStationID(request.CardAcceptorID).Result;

                if (shell == null)
                {
                    onStationInformationRecieved?.Invoke(new TaskSiteInformation() { ID = request.CardAcceptorID});
                    await Logger.LogTCP("?", "-> " + "ERROR: No Shell found for: " + request.CardAcceptorID);
                    return;
                }
                byte[] sendBytes = request.ToByteArray();
                await shell.Stream.WriteAsync(sendBytes);                
                await Logger.LogTCP(shell.Endpoint, "-> " + request.ToXAML());
                shell.Stream.Flush();

                string target = "";
                if (request.RequestType == RatioRequestType.ReservePump)
                {
                    target = request.RequestID;
                }
                else
                {
                    target = request.ReferenceNumber;
                }
                await db.LogCommunication("Transactions", "TND", target, request.ToXAML());
                                        
                
            }
            catch (Exception ex)
            {
                await Logger.LogTCP("ERROR SENDING", ex.Message + ex.StackTrace);
                SslShell shell = await GetShellForStationID(request.CardAcceptorID);
                endpoints.Remove(endpoints.First(x => x.Endpoint == shell.Endpoint));
            }
        }                

        private async Task<SslShell> GetShellForStationID(string ID)
        {
            try
            {
                return endpoints.First(x => x.StationID.Trim() == ID.Trim());
            }
            catch (Exception)
            {
                return null;
            }            
        }
    }
}

Ich sehe in deinem Code unmengen an Problemen.

Die Klasse solltest du als Singleton umsetzen.

buffer ist bei dir ein Klassen Feld, entsprechend rufen alle Clients gleichzeitig den Buffer ab oder verwenden diesen.

Was dann drin steht ist purer Zufall.

Hier solltest du dringend den Code überarbeiten.

Dein Code ist auch mit try/catch vollgestopft, die viel mehr machen als sie sollten.

Ebenfalls ist das try/catch in GetShellForStationID unnötig, wenn dein Code sauber ist.

Die Begin/End Methoden des TCPListener solltest du durch die Async Methoden ersetzen.

Das ist noch ein uraltes Konzept für asynchrone Programmierung aus .NET 2.0 Zeiten, ist also sehr sehr altes Zeug.

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.

Zitat von T-Virus

Ich sehe in deinem Code unmengen an Problemen.

Die Klasse solltest du als Singleton umsetzen.

Warum?

buffer ist bei dir ein Klassen Feld, entsprechend rufen alle Clients gleichzeitig den Buffer ab oder verwenden diesen.

Was dann drin steht ist purer Zufall.

Hier solltest du dringend den Code überarbeiten.

Danke für den Hinweis, habe den Code entsprechend geändert.

Dein Code ist auch mit try/catch vollgestopft, die viel mehr machen als sie sollten.

Ebenfalls ist das try/catch in GetShellForStationID unnötig, wenn dein Code sauber ist.

Da hast Du recht, Da wollte ich auf Nummer sicher gehen für die Fälle an die ich nicht gedacht habe. Was spricht denn generell gegen "zu viel" try/catch?

Die Begin/End Methoden des TCPListener solltest du durch die Async Methoden ersetzen.

Das ist noch ein uraltes Konzept für asynchrone Programmierung aus .NET 2.0 Zeiten, ist also sehr sehr altes Zeug.

Welche Methoden meinst Du genau? Die listenener.BeginAcceptTcpClient()? Dann werd ich mich da mal rein lesen, danke für den Hinweis!

Was spricht denn generell gegen "zu viel" try/catch?

Prinzipiell "nichts" in diesem Fall, aber Dein Code ist in dem Fall einfach unsauber. Man braucht es einfach nicht.
"Generell" widerspricht so eine Umsetzung mit Exception Unterdrückung den gesamten Exception Guidelines und dem Mechanismus dahinter.

Dein Code in sauber wäre:

private SslShell GetShellForStationID(string ID)
{
  string idValue = ID.Trim();
  return endpoints.FirstOrDefault(x => x.StationID.Trim() == idValue);
}

Noch sauberer wäre natürlich die Verwendung von Nullable (Nullable reference types) und der Compiler-Unterstützung; aber rein von der Logik her.

Die Methode macht aber als async trotzdem keinen Sinn, daher reicht SslShell als Rückgabetyp (da sollte auch eine Warnung vom Compiler kommen).

Absolut korrekt. In meinem Beispiel korrigiert. Man braucht die Methode eigentlich gar nicht, weil man direkt bei der Verwendung auf die Collection zugreifen kann. Mehrwert nicht gegeben.

Du solltest die Klasse als Singleton umsetzen, da du auch nur einen TCP Listener hast und diesen als static markierst.

Dadurch geht dein Server schon den Schritt Richtung Singleton aber hier nur halbgar, da du einen public Konstruktor hast.

Dadurch wäre es aktuell mögliche mehrere Instanzen zu erstellen und den Server mehrfach zu starten.

Würde zwar wegen dem listener knallen, wäre aber möglich.

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.

Also erstmal danke für die ganzen Hinweise, ich habe meinen TCP Server nun refaktorisiert und Eure Ratschläge umgesetzt.

Zwar hat es sich herausgestellt, dass das Problem an der Gegenseite lag, aber hab ja trotzdem was gelernt.