Laden...

LowLevel TCP Pakete empfangen

Erstellt von CRaute vor 12 Jahren Letzter Beitrag vor 12 Jahren 7.703 Views
C
CRaute Themenstarter:in
9 Beiträge seit 2012
vor 12 Jahren
LowLevel TCP Pakete empfangen

Hallo.

Meine Problemstellung ist dass ich eine große Datenmenge schnell übertragen muss.
Das Problem ist dass das Paketorientiert geschehen muss. Daher ist der Stream, den der TCPClient zurück gibt nicht geeignet. Hier geht mir die Paketgröße verloren. (auch wenn ich das mit einem eigenen Thread asynchron in einen Buffer schreibe). Ich habe zwar eine Lösung gefunden aber die ist ineffizient und verursacht mehr Traffic als nötig, also Murx.

Gibt es eine Möglichkeit in C# TCP Pakete direkt von der Netzwerkkarte zu empfangen.
Also so eine Art LowLevel Netzwerk Hook, der mir 1:1 die TCP-Pakete bzw die Nutzdaten zurück gibt?

mfg

L
95 Beiträge seit 2009
vor 12 Jahren

Schau dir doch mal den .Net Wrapper für winpcap an (Ist der Paket-sniffing-treiber den auch Wireshark verwendet)

http://www.codeproject.com/Articles/4217/Packet-Sniffing-with-Winpcap-Functions-Ported-to-a

E
180 Beiträge seit 2010
vor 12 Jahren
Alternative

ich hab sehr gute Erfahrung mit SharpPcap gemacht.

Gruß

W
872 Beiträge seit 2005
vor 12 Jahren

Ich denke, dass Du irgendetwas falsch machst, wenn TCPClient nicht mehr performant genug ist. Eigener Thread ist schon wichtig/richtig, aber vielleicht nimmst Du die falsche Struktur danach.
Socket ist noch naeher an der winapi, aber der Overhead ist minimal.
Was ist fuer Dich denn ne grosse Datenmenge? Was meinst Du mit paketorientiert?

Wenn Du auf den Transport-Layer (4) mit Pcap wechselst, dann musst Du auch anfangen, solche Dinge wie TCP Retransmit, etc. richtig implementieren, das wird sehr schnell sehr komplex.

C
CRaute Themenstarter:in
9 Beiträge seit 2012
vor 12 Jahren

Eine große Datenmenge ist relativ. Ich will keinen Flaschenhals durch eine schlechtes Netzwerkinterface. Die Übertragene Datenmenge hängt von der Rechenleistung des Systems ab. Aber 1-10gig/sek sollten schon drin sein. Also sprich ca. 100MByte Netto.

Das Problem ist dass die Pakete verschieden groß sind. Wenn jetzt zu viele Daten gesendet werden, kann ich die Pakete nicht richtig Parsen, da die Read Methode vom Stream die komplette Buffergröße zurück gibt.

So ähnlich sieht der Code aus:


                byte[] message;
                while (true)
                {
                    message = new byte[_buffSize];
                    
                    try
                    {
                        bytesRead = MessageStream.Read(message, 0, _buffSize);
                    }
                    catch (IOException a)
                    {
                        //....lost connection
                    }

                    if (bytesRead == 0)
                    {
                       //... connection end
                    }

                    Array.Resize<byte>(ref message, bytesRead);
                    BufferList.Add(message);
                }

Ein weiterer Thread löst dann die Events aus und löscht die Einträge wieder.

Lohnt es sich die Klasse neu zu schreiben aber dieses mal die Asynchron-Methoden zu verwenden?

mfg

W
872 Beiträge seit 2005
vor 12 Jahren

Array.Resize ist ein echter Flaschenhals.

This method allocates a new array with the specified size, copies elements from the old array to the new one, and then replaces the old array with the new one.

Du musst die Groesse schon selber verwalten, damit Du jedes zusaetzliche allocate und copy vermeiden kannst.
Deinen Performancenwunsch halte ich fuer extrem unrealistisch. Hast Du ueberhaupt ein lokales 1 GBit/Sekunde Netzwerk? Ein realistischer Vergleich waere gegen einen ftp Server - was schaffst Du da?

C
CRaute Themenstarter:in
9 Beiträge seit 2012
vor 12 Jahren

Gut zu wissen. Dann werde ich das nicht verwenden. Im release code wird ein neues Array mit der Länge der Empfangenen Daten erstellt und Array.Copy verwendet. Ich hoffe dass das schneller ist.

Lokal habe ich ein Gig netz. Aber da wo mein Projekt zum einsatz kommt, stehen ein LWL und Reck Server zur Verfügung.
Mein Programm ist extrem Traffic intensiv. Um zb. 10MB Nutzdaten zu übertragen sind 40MB Traffic erforderlich (frag nicht was ich mache^^)

Meine Festplatte ist zu langsam für den FTP, ich hab nur einen 5k4 drin die schafft 70Mbyte/sek.

Ich will ja nicht dauernd die volle Geschwindigkeit ausnutzen. Jedoch kommen immer wieder Bandbreitenspitzen vor. Und genau bei diesen versagt meine Schnittelle.

Also wenn ein sniffer nicht geeignet ist, was soll ich dann ändern?

mfg

W
872 Beiträge seit 2005
vor 12 Jahren

Du hast eine tolle Infrastruktur.
TCP ist das falsche Protokol fuer Deine Problemstellung.
Schau Dir mal Pragmatic General Multicast an. Es gibt Bindings fuer WCF.
Ich persoenlich wuerde auch mal so etwas wie ZeroMQ ausprobieren. Deine Erfahrungen wuerden mich sehr interessieren.
Ansonsten gilt ausprobieren und messen, wenn Du anfaengst zu messen, wirst Du merken, wieviel Zeit die Garbage Collection braucht und Du wirst das Array.Copy nochmals ueberdenken 😉
Performanter C# Code fuer solche Probleme unterscheidet sich meiner Erfahrung nach am Ende nicht mehr stark von C Code.

C
CRaute Themenstarter:in
9 Beiträge seit 2012
vor 12 Jahren

Die Infrastruktur gehört leider nicht mir. Ich habe nur ein 8-Port Gig Switch.
Zu dem PGM: Ich glaube dass das nicht zielführend ist, da richtige Reihenfolge der Pakete absolute Priorität hat. Kann ich nur Parallel senden und empfangen.

Wie gesagt das einzige das noch nicht funktioniert die Net-Klasse bei höheren traffic.

Kann ich die Performance verbessern wenn ich die Async Methoden verwende?

Das ZeroMQ sieht sehr interessant aus. Das werde ich mir auf jedenfall näher ansehen. Wenn ich es schaff wird der Quellcode sowieso hier gepostet 😉 Ja die C# Compiler sind sehr gut optimiert.

edit:

ist der code so effizienter?


                byte[] message;
                byte[] buffer;
                while (true)
                {
                    message = new byte[_buffSize];

                    try
                    {
                        bytesRead = MessageStream.Read(message, 0, _buffSize);
                        buffer = new byte[bytesRead];
                    }
                    catch (IOException a)
                    {
                        //....lost connection
                    }

                    if (bytesRead == 0)
                    {
                       //... connection end
                    }

                     for (int i = 0; i < bytesRead; i++)
                        buffer[i] = message[i];
                   BufferList.Add(message);

                }
4.221 Beiträge seit 2005
vor 12 Jahren

Nur zu meinem Verständnis..

Wäre es nicht sinnvoller mit TcpClient.Available die Datenmenge welche anliegt auszulesen und dann genau so viele Bytes aus dem Stream auszulesen...

So müssten die gelesenen Arrays immer voll sein und der Resize wäre weg.

Früher war ich unentschlossen, heute bin ich mir da nicht mehr so sicher...

C
CRaute Themenstarter:in
9 Beiträge seit 2012
vor 12 Jahren

Also das mit dem .Available funktioniert bei mir nicht. Ich erhalte immer 0 zurück.

Aber ich habs gerade zusammengebracht 😃

50000 Pakete in 4,5sek ohne Fehler.
das sind dann 1400 Bytes * 50 000 / 4,5sek = 15 Mbytes / sek und das bei 3,5Ghz

Wie kann ich die Performance noch verbessern?

hier ist der Code:


            byte[] message;
            int bytesRead = 0;
            ushort leng = 0;

            while (true)
            {
                
                _Online = true;
                try
                {
                    message = new byte[2];
                    MessageStream.Read(message, 0, 2);
                    leng = BitConverter.ToUInt16(message, 0);
                    message = new byte[leng];
                    bytesRead = MessageStream.Read(message, 0, leng);

                }
                catch (IOException a)
                {
                    _Online = false;
                    ConnectionLost(a);
                    break;
                }

                if (bytesRead == 0)
                {
                    _Online = false;
                    quitcon();
                    break;
                }

                //rais event mit message
                if (RecievMsg != null) RecievMsg(message);              
            }    
        }

mfg

W
872 Beiträge seit 2005
vor 12 Jahren

Erstmal noch zum Protokoll:

PGM provides a reliable sequence of packets to multiple recipients simultaneously, making it suitable for applications like multi-receiver file-transfer.

  • also bekommst Du die Pakete garantiert in der richtigen Reihenfolge.

Das größte Probleme bei TCP ist, wenn Du ein Paket verliert, z.B. wenn Du zu langsam liest und der Buffer Deiner Netzwerkarte voll ist, dann werden alle (!) Pakete, die nach dem verlorenen Paket sind, wieder gesendet und nicht nur das verlorene Paket.

Der letzte Code sieht sieht schon gut aus. Ansonsten mit der Stopwatch Klasse messen, wie lange etwas dauert (ElapsedTicks) und vergleichen (natuerlich ohne Debugger) - im aktuellen Fall kannst Du mit etwas Geschick mit einem Memory Stream testen.

So etwas wie das hier


   for (int i = 0; i < bytesRead; i++)
                        buffer[i] = message[i];

finde ich schon sehr arg - Buffer.BlockCopy wird ein x-faches schneller sein.

C
CRaute Themenstarter:in
9 Beiträge seit 2012
vor 12 Jahren

Ich dachte dass jedes Paket ein Timeout hat, wird innerhalb von dieser Zeit keine Übertragungsbestätigung empfangen, wird dieses Paket nochmal gesendet.

Meine Klasse ist so geschrieben dass immer nur 1 TCP/IP Paket versendet werden kann. Wenn ein Datenpaket wirklich ein anderes überholen kann und das des öfteren vorkommt, dann werde ich mich um das Problem kümmern. Aber bis jetzt sieht es so aus also würde es funktionieren.

Wieso soll der Buffer von der Netzwerkkarte Zu klein sein? Die Daten werden doch in einen MemmoryStram gespeichert. Sprich im RAM?

W
872 Beiträge seit 2005
vor 12 Jahren

Ok - ich habe in der Eile nicht ganz genau geantwortet (der Buffer ist im TCP Stack von Windows und nicht in der Netzwerkkarte) und versuche dieses mal praeziser zu sein.
Schau Dir mal den Wikipedia Artikel zum TCP Receive Window an - wenn Du nicht schnell genug abnimmst, loest Du einen Retransmit aus.

Du hast den Effekt doch schon selber beschrieben:

Lokal habe ich ein Gig netz. Aber da wo mein Projekt zum einsatz kommt, stehen ein LWL und Reck Server zur Verfügung.
Mein Programm ist extrem Traffic intensiv. Um zb. 10MB Nutzdaten zu übertragen sind 40MB Traffic erforderlich (frag nicht was ich mache^^)

Grundsaetzlich solltest Du immer an das OSI Modell denken - Dein C# Programm ist grundsaetzlich immer auf Ebene 7 - d.h. Du kannst das Verhalten von Ebene 4 Transport/TCP nicht direkt beeinflussen- haettest Du z.B. pcpap benutzt, haetten die Daten immer noch auf Ebene 7 abgearbeitet werden muessen.

C
CRaute Themenstarter:in
9 Beiträge seit 2012
vor 12 Jahren

ich denke nicht dass es zu einem Overflow kommt. Dafür ist der Algorithmus zu komplex der die Daten sendet und die ankommenden Pakete werde sofort verarbeitet. Es müsste schon ein wirklich großer Server einen kleinen Laptop zuspamen.

Die Buffergröße habe ich auf max. 1400bytes gestellt, ich dachte dass hiermit immer nur 1 TCP Paket gesendet wird. Also liege ich hier falsch?

Wie gesgat das mit dem Pcap und ZMQ schau ich mir genauer an.

W
872 Beiträge seit 2005
vor 12 Jahren

Pcap hilft Dir nur den Netzwerkverkehr mitzuschneiden, nicht den Netzwerkverkehr zu steuern.
1400 sind die Standardeinstellung fuer MTU - aber da kommt noch der Header von 20 Byte fuer TCP und 20 Byte fuer IP dazu.

C
CRaute Themenstarter:in
9 Beiträge seit 2012
vor 12 Jahren

Die genormte MTU für TCP/IP auf Ethernet ist 1500 Bytes lang. zieht man den Header noch ab bleiben 1460bytes Nutzdaten übrig. Bei ein paar Sonderfällen kann da nochmal was weg fallen, darum habe ich 1400Bytes gewählt.

So hat es mir halt mein Cisco Professor erklärt.

Ja das Pcap den Netzwerkverkehr mitsnifft habe ich schon herausgefunden. Ich muss nur noch die Pakete mit dem Zielport heraus filtern und die Daten auslesen.

Gelöschter Account
vor 12 Jahren

Mal ne blöde zwischenfrage aber warum sendest du nicht bei jeder Transaktion eine 16 bit Zahl mit, die angibt, wieviel Byte das aktuelle "Packet" enthält? Dann sparst du dir viel rechnerei und kannst einfach entsprechend viele bytes aus dem was aus dem Stream kommt lesen und dann falls noch was übrig ist nochmal dasselbe tun.