Laden...

TCP-Performance Optimieren / Probleme beim zu schnellen Lesen

Erstellt von SwitzerChees vor 10 Jahren Letzter Beitrag vor 10 Jahren 2.148 Views
S
SwitzerChees Themenstarter:in
3 Beiträge seit 2014
vor 10 Jahren
TCP-Performance Optimieren / Probleme beim zu schnellen Lesen

Guten Tag

Ich bin neu in diesem Forum und habe mir einmal gedacht da ich hier schon für einige Probleme wirklich gute Hilfestellungen bekommen habe, bitte ich einmal hier um eure Vorschläge.

Ich suche einen Weg, möglichst performant Daten über eine TCP-Verbindung zu empfangen. Ich habe dafür um ein paar Versuche zu unternehmen einen Server und eine Client geschrieben der Dummydaten sendet.

Hier einmal die Methode vom Server welche Daten empfängt:

          using (Stream stream = client.GetStream())
            {
                bool first = false; //Bestimmt ob es das erste Paket ist welches die Paketlänge enthält
                int streamlänge = 0; //Streamlänge die das Paket hat (wird im ersten Paket mit geschickt)
                int ausgelesen = 0; //Wie viele von diesem Paket bereits ausgelesen wurden
                byte[] finalmessage = new byte[0]; //zusammengebaute finale Nachricht
                while (LÄUFT) //Bis listening = false
                {
                    byte[] buffer = new byte[4096];
                    int ausgelesenAktuell = 0; //Anzahl der im Aktuellen Paket enthalten bytes
                    //===========>>>>>>>>>>ERSTES PAKET (LÄNGE) ========<<<<<<<<<=======
                    if (first == false) //Wenn First noch nicht ausgelesen
                    {
                        byte[] länge = new byte[4]; //Byte Länge der Längeninformation
                        stream.Read(länge, 0, länge.Length); //Länge auslesen
                        streamlänge = BitConverter.ToInt32(länge, 0); //Convertieren in Int
                        finalmessage = new byte[streamlänge]; //neue Message definieren mit Streamlänge
                        if (streamlänge != 0) //Wenn Stream informationen enthält
                            first = true; //Erste Paket gelesen
                    }
                    //===========>>>>>>>>>>ERSTES PAKET (LÄNGE) ========<<<<<<<<<=======
                    //===========>>>>>>>>>>RESTLICHE PAKETE========<<<<<<<<<=======
                    else
                    {
                        int differenz = streamlänge - ausgelesen;
                        if (differenz < buffer.Length)
                            ausgelesenAktuell = stream.Read(buffer, 0, differenz);
                        else
                            ausgelesenAktuell = stream.Read(buffer, 0, buffer.Length);
                    }
                    //===========>>>>>>>>>>RESTLICHE PAKETE========<<<<<<<<<=======
                    Buffer.BlockCopy(buffer, 0, finalmessage, ausgelesen, ausgelesenAktuell);//Kopiert die angekommenen bytes in der FinalMessage
                    ausgelesen = ausgelesen + ausgelesenAktuell; //der bereits ausgelesene Index wird erhöht
                    if (ausgelesen == streamlänge && streamlänge != 0) //Wenn alle zu diesem Paket gehörigen bytes gelesen deserialize
                    {
                        first = false;
                        ausgelesen = 0;
                        //ClientMessage clientmessage;
                        ////Deserialisieren der Resultate
                        //using (MemoryStream memorystream = new MemoryStream(finalmessage))
                        //{
                        //    BinaryFormatter binaryformater = new BinaryFormatter();
                        //    binaryformater.Binder = new AllowAllAssemblyVersionsDeserializationBinder();
                        //    clientmessage = (ClientMessage)binaryformater.Deserialize(memorystream);
                        //}
                        //finalmessage = new byte[0];
                        //if (String.IsNullOrEmpty(HeadBenutzername) == true)
                        //    SetHeaderInfos(clientmessage);
                        //else if (clientmessage.pingDate.ToString() != HeadpingDate)
                        //{
                        //    HeadpingDate = clientmessage.pingDate.ToString();
                        //    SetActualPing(HeadpingDate);
                        //}
                    }
                }
            }

Und hier der Code des Clients der die Daten sendet:


using (TcpClient client = new TcpClient("10.18.61.182", 4000))
            {
                using(Stream stream = client.GetStream())
                {
                    ClientMessage clientmessage = new ClientMessage();
                    while(true)
                    {
                        byte[] buffer;
                        using (MemoryStream memorystream = new MemoryStream())
                        {
                            BinaryFormatter binaryforamter = new BinaryFormatter();
                            binaryforamter.Serialize(memorystream, clientmessage);
                            buffer = memorystream.ToArray();
                        }
                        byte[] messagelänge = BitConverter.GetBytes(buffer.Length);
                        byte[] finalmessage = new byte[buffer.Length + 4];
                        Buffer.BlockCopy(messagelänge, 0, finalmessage, 0, 4);
                        Buffer.BlockCopy(buffer, 0, finalmessage, 4, buffer.Length);
                        stream.Write(finalmessage, 0, finalmessage.Length);
                        stream.Flush();
                        }
                    }
                }
            }

Das Senden von Daten funktioniert schon sehr gut und schnell hier ein paar Statistiken:

Zeit: 1.332
Anz Durchläufe/Pakete: 50001
Anz bytes: 22450449
Durchschn. Durchläufe / Sek: 37538 Durchläufe/Pakete
Durchschn. anz Bytes / Sek: 16459.66KB

Allerdings bekomme ich nach dem Senden von einigen 1000 Paketen meist einen Fehler (System.OutOfMemoryException).
Der Fehler liegt daran, dass an dieser Stelle vom Server an dem eine neue finalmessage instanziert wird, die Falsche länge angegeben wird. Also anstatt der Länge von ca 455 bytes steht dann eine Zahl von mehreren 100K bytes. Also geht irgend was beim Auslesen der Paketlänge schief da meine Applikation mit den ersten 4 bytes immer vorausschickt, wie lange das Paket ist welche empfangen wird und damit der Server dann an der richtigen Stelle aufhört zu lesen.
Wenn ich allerdings an der Stelle an der der Kommentar (Deserialisieren der Resultate) steht, ein Thread.Sleep(1) angebe kommt der Fehler nicht mehr vor. Ich habe also das Gefühl ich lese zu schnell Daten aus dem Stream oder kann mir hier jemand helfen, wieso dieser Fehler kommt wenn ich zu schnell lese?.

Abschliessend noch einmal meine 2 Fragen:

  1. Wieso funktioniert meine Read Methode nicht mehr wenn ich zu schnell Pakete empfange?
  2. Wie kann ich das Empfangen von Paketen noch weiter optimieren?

Ich danke schon einmal im Voraus für Tipps und/oder Infos.

T
2.223 Beiträge seit 2008
vor 10 Jahren

@SwitzerChees
Dein Code ist sehr unschön und sollte im Idealfall auch auf Umlaute verzichten.
Die Kommentare machen das Lesen ebenfall nicht einfacher.

Ansonsten würde ich mal vermuten, dass deine Verarbeitung hier nicht sauber ist.
Hast du mal versucht zu Debuggen welche Daten vom Server empfangen werden?
Stimmt hier ggf. die Reihenfolge der empfangenen Daten nicht(Network Byte Order)?

Ich hatte ein diesen Fehler auch häufiger bei einigen Verarbeitungen.
Hast du auch geprüft ob alle Daten in der richtigen Reihenfolge gesendet werden vom Client?
Ggf. fehlt hier ein Paket und er liest dann das falsche aus.

Ansonsten macht Thread.Sleep nur wenig Sinn.
Darauf sollte man auch verzichten können.

Anbei ist die Art wie du den Stream ausliest auch Suboptimal.
Lies alle Bytes mit einmal aus und splitte den Block dann auf die Pakete.
Aktuell schreibst du einmal den Block im Client und liest mehrfach beim Server.
Kann auch eine Fehlerquelle sein.

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.

W
872 Beiträge seit 2005
vor 10 Jahren

TCP garantiert eine Reihenfolge, schnelles Lesen ist nur wichtig, damit der Buffer nicht voll wird, wenn viele Daten kommen, nicht um die Reihenfolge einzuhalten.
Du hast in deinem Code eine Magic Number von 4K beim Lesen, wo irgendwo dann etwas schief läuft, da Du in 4k Blöcken liest.
Das TCP Receive Window ist mittlerweile bis zu 16 MB groß, insofern sind die 4k eher kontraproduktiv.
Dein Server Programm ist auch kompliziert, weil Du nur eine while schleife statt zwei Schleifen (eine aussen für listening/eine innere für Lesen der Message) benutzt.
Ich würde an Deiner Stelle erstmal mit einfachen Read/Write anfangen, wenn Deine Nachrichten kleiner als 16 MB sind.

849 Beiträge seit 2006
vor 10 Jahren

Hallo,

ausserdem würde ich noch nen Pre & Suffix + crc drüber ziehen.. das macht zwar das tcp protokoll schon, aber so kannst dir wenigstens Sicher sein das deine Implementation von Server und client auch übernander passt.

Ausserdem würde ich schauen ob wcf + net.tcp binding nicht auch deinen Ansprüchen genügt. Man muss ja nicht alles neu erfinden.

S
SwitzerChees Themenstarter:in
3 Beiträge seit 2014
vor 10 Jahren

Hallo Leute

Danke erstmal für eure Anmerkungen. Der Code ist nicht sauber das weiss ich werde den auch noch einmal sauber schreiben wenn ich ihn dann brauche. Wie gesagt ist das erst ein Testaufbau.

Ich habe es noch einmal versucht, diesmal aus dem Stream zu lesen bis nichts mehr kommt dann das Empfangene zu verarbeiten. Das Funktioniert auch wieder gut für eine bestimmte Anzahl von Paketen und irgend einmal kommt es wieder zum gleichen Problem. Die bytes die ausgelesen werden sind nicht mehr diese welche für das Bestimmen der Paketlänge bestimmt sind.
Ich habe auf der Clientseite überprüft die Daten werden in der richtigen Reihenfolge gesendet. Die Möglichkeit, dass Pakete verloren gehen habe ich in Betracht gezogen allerdings weiss ich nicht, wie ich das ermitteln soll denn es passiert ja meist erst nach ein paar Tausend Paketen da habe ich keine Ahnung wie ich das Debuggen soll.

Leider brauche ich für das was ich machen will, einen permanenten Objectstream also WCF fällt da weg.

Ich verstehe einfach nicht, wo diese Verwechslung oder diese fehlerhafte Verschiebung zustande kommt, dass plötzlich die bytes welche für die Paketlänge zuständig sind nicht mehr an der richtigen stelle sind. Und das passiert ja nur, wenn ich zu schnell aus dem Stream lese.

Mein aktueller Servercode sieht jetzt so aus wenn jemand noch eine Idee hätte wie dieses Problem zustande kommt, wäre ich froh.


public void ClientReader()
        {
            using (Stream stream = client.GetStream())
            {
                byte[] lastPaket = new byte[0];
                int lastPaketLenght = 0;
                int zähler = 0;
                while (LÄUFT) //Bis listening = false
                {
                    byte[] buffer = new byte[16777216];
                    int lenght = 0;
                    lenght = stream.Read(buffer, 0, buffer.Length);
                    int index = 0;
                    byte[] finalBuffer = new byte[lastPaket.Length + lenght];
                    int finalStartIndex = 0;
                    Buffer.BlockCopy(lastPaket, 0, finalBuffer, 0, lastPaket.Length);
                    finalStartIndex = lastPaket.Length;
                    Buffer.BlockCopy(buffer, 0, finalBuffer, finalStartIndex, finalBuffer.Length-finalStartIndex);
                    lenght += lastPaket.Length;
                    while (index < lenght)
                    {
                        int packageLength = 0;
                        if (lastPaketLenght == 0)
                        {
                            byte[] packageLengthBytes = new byte[4];
                            for (int i = 0; i < 3; i++)
                            {
                                packageLengthBytes[i] = finalBuffer[index + i];
                            }
                            index += 4;
                            packageLength = BitConverter.ToInt32(packageLengthBytes, 0); //Convertieren in Int
                        }
                        else
                        {
                            packageLength = lastPaketLenght;
                            lastPaketLenght = 0;
                        }
                        if ((finalBuffer.Length - index) < packageLength)
                        {
                            lastPaket = new byte[finalBuffer.Length - index];
                            for (int i = 0; i < lastPaket.Length; i++)
                            {
                                lastPaket[i] = finalBuffer[i + index];
                            }
                            lastPaketLenght = packageLength;
                            break;
                        }
                        else
                        {
                            byte[] package = new byte[packageLength];
                            Buffer.BlockCopy(finalBuffer, index, package, 0, package.Length);
                            index += packageLength;
                            zähler++;
                        }
                    }
                }
            }

16.830 Beiträge seit 2008
vor 10 Jahren

Der Code ist nicht sauber das weiss ich werde den auch noch einmal sauber schreiben wenn ich ihn dann brauche.

Nichts hält sich so lange wie provisorischer Code.

Du musst bedenken: Du erwartest Hilfe von den Leuten.
Dann strukturier und dokumentiert doch Dein Code ein wenig mehr, sodass die Leute nicht erst 30 Minuten brauchen, bis sie verstehen, was Du tust / tun willst.
Wenns so komplizierter und unübersichtlicher Code einfach hingeklatscht wird, lesen sich 98% der Leute (mir inklusive) nicht mal Deinen Text (richtig) durch.

Ist also ein gut gemeinter Rat, wenn Du Hilfe erwartest und auch noch in 4 Wochen verstehen willst, was Du da an Code verbrochen hast.

S
SwitzerChees Themenstarter:in
3 Beiträge seit 2014
vor 10 Jahren

Ja da hast du recht ich sollte den zweiten Code ein wenig besser kommentieren.

Allerdings hat der bei meinem Problem auch keine Verbesserung gebracht da ist mein erster Code den ich gepostet habe noch um einige besser und dieser sollte eigentlich genug gut kommentiert und dokumentiert sein.

Ich habe gehofft, jemand hatte das gleich Problem auch schon einmal und könnte mir erklären, warum es zu diesem "Paketverlust" kommt wenn es wirklich einer ist denn der Code an sich ist nicht fehlerhaft er macht was er soll allerdings nur solange die Geschwindigkeit nicht zu hoch ist mit der er seine Aufgabe erledigen soll.