Frage

Ich habe ein Byte-Array, das eine vollständige TCP / IP-Paket darstellt. Zur Verdeutlichung wird der Byte-Array wie folgt bestellt:

(IP-Header - 20 Bytes) (TCP-Header - 20 Bytes) (Payload - X Bytes)

Ich habe eine Parse Funktion, die einen Byte-Array akzeptiert und gibt ein TCPHeader Objekt. Es sieht wie folgt aus:

TCPHeader Parse( byte[] buffer );

das ursprüngliche Byte-Array gegeben, hier ist die Art, wie ich diese Funktion im Moment bin aufrufen.

byte[] tcpbuffer = new byte[ 20 ];
System.Buffer.BlockCopy( packet, 20, tcpbuffer, 0, 20 );
TCPHeader tcp = Parse( tcpbuffer );

Gibt es eine bequeme Möglichkeit, den TCP-Byte-Array zu übergeben, das heißt das Bytes 20-39 des gesamten TCP / IP-Pakets an die Parse Funktion, ohne sie zunächst in einen neuen Byte-Array zu extrahieren?

In C ++, ich könnte wie folgt vor:

TCPHeader tcp = Parse( &packet[ 20 ] );

Gibt es etwas ähnliches in C #? Ich mag die Erstellung und anschließende Garbage Collection des temporären Byte-Array, wenn möglich zu vermeiden.

War es hilfreich?

Lösung

Eine gängige Praxis können Sie in der .NET-Framework sehen, und dass ich hier empfehlen, sind die Offset und Länge angeben. So machen Sie Ihre Parse-Funktion auch den in der übergebenen Array-Offset akzeptieren, und die Anzahl der Elemente zu verwenden.

Selbstverständlich gelten die gleichen Regeln wie wenn Sie einen Zeiger wie in C ++ passieren waren - das Array sollte nicht geändert werden, sonst kann es zu undefiniertem Verhalten führen, wenn Sie nicht sicher sind, wann genau die Daten verwendet werden. Aber das ist kein Problem, wenn Sie nicht mehr gehen das Array zu ändern.

Andere Tipps

Ich würde ein ArraySegment<byte> in diesem Fall passieren.

Sie würden Ihre Parse Methode, um dies ändern:

// Changed TCPHeader to TcpHeader to adhere to public naming conventions.
TcpHeader Parse(ArraySegment<byte> buffer)

Und dann würden Sie den Anruf dies ändern:

// Create the array segment.
ArraySegment<byte> seg = new ArraySegment<byte>(packet, 20, 20);

// Call parse.
TcpHeader header = Parse(seg);

die ArraySegment<T> Verwendung wird das Array nicht kopieren, und es wird die Überprüfung der Grenzen für Sie im Konstruktor tun (so dass Sie nicht eine falsche Grenzen angeben). Dann Sie Ihre Parse Methode ändern sich mit den Grenzen im Segment angegeben zu arbeiten, und Sie sollten in Ordnung sein.

Sie können sogar eine Bequemlichkeit Überlastung schaffen, die den gesamten Byte-Array akzeptieren wird:

// Accepts full array.
TcpHeader Parse(byte[] buffer)
{
    // Call the overload.
    return Parse(new ArraySegment<byte>(buffer));
}

// Changed TCPHeader to TcpHeader to adhere to public naming conventions.
TcpHeader Parse(ArraySegment<byte> buffer)

Wenn ein IEnumerable<byte> als Eingabe akzeptabel ist und nicht byte[], und Sie sind mit C # 3.0, dann könnte man schreiben:

tcpbuffer.Skip(20).Take(20);

Beachten Sie, dass dies immer noch enumerator Instanzen unter die Decke zuordnet, so dass Sie Zuordnung nicht ganz entziehen und so für eine kleine Anzahl von Bytes sein kann tatsächlich langsamer als ein neues Array Aufteilung und das Kopieren der Bytes hinein.

Ich würde nicht zu viele Sorgen über Zuteilung und GC von kleiner temporärer Arrays aber um ehrlich zu sein. Die .NET Garbage Collection-Umgebung ist bei dieser Art von Zuweisungsmuster sehr effizient, vor allem, wenn die Arrays von kurzer Dauer sind, so, wenn Sie es profiliert haben und GC gefunden, ein Problem zu sein, dann würde ich es in der meisten intuitiven Art und Weise schreiben und beheben Performance-Probleme, wenn Sie wissen, dass Sie sie haben.

Wenn Sie wirklich diese Art von Kontrolle benötigen, musst du Blick auf unsafe Funktion von C #. Es ermöglicht Ihnen, einen Zeiger zu haben und es zu stecken, so dass GC es bewegt sich nicht:

fixed(byte* b = &bytes[20]) {
}

Allerdings ist diese Praxis nicht für die Arbeit mit verwalteten Code nur vorgeschlagen, wenn es keine Performance-Probleme sind. Sie könnten den Offset und Länge wie in Stream Klasse übergeben.

Wenn Sie die Methode parse () ändern können, ändern Sie es um den Versatz zu akzeptieren, wenn die Verarbeitung beginnen soll. TCPHeader Parse (byte [] buffer, int offset);

Sie könnten LINQ verwenden, um so etwas wie:

tcpbuffer.Skip(20).Take(20);

Aber System.Buffer.BlockCopy / System.Array.Copy sind wahrscheinlich effizienter zu gestalten.

Dies ist, wie ich es gelöst kommen C-Programmierer zu einem c # Programmierern zu sein. Ich mag Memorystream verwenden, um es in einen Stream zu konvertieren und dann Binary zu brechen den binären Datenblock auseinander. Hätte die beiden Helferfunktionen hinzufügen von Netzwerk, um Little-Endian zu konvertieren. Auch für den Bau eines byte [] senden sehen Gibt es eine Möglichkeit, ein Objekt zurück zu seiner ursprünglichen Art werfen, ohne jeden Fall specifing? die eine Funktion hat, die von einem Array zum Umwandeln ermöglichen von Objekten zu einem byte [].

  Hashtable parse(byte[] buf, int offset )
  {

     Hashtable tcpheader = new Hashtable();

     if(buf.Length < (20+offset)) return tcpheader;

     System.IO.MemoryStream stm = new System.IO.MemoryStream( buf, offset, buf.Length-offset );
     System.IO.BinaryReader rdr = new System.IO.BinaryReader( stm );

     tcpheader["SourcePort"]    = ReadUInt16BigEndian(rdr);
     tcpheader["DestPort"]      = ReadUInt16BigEndian(rdr);
     tcpheader["SeqNum"]        = ReadUInt32BigEndian(rdr);
     tcpheader["AckNum"]        = ReadUInt32BigEndian(rdr);
     tcpheader["Offset"]        = rdr.ReadByte() >> 4;
     tcpheader["Flags"]         = rdr.ReadByte() & 0x3f;
     tcpheader["Window"]        = ReadUInt16BigEndian(rdr);
     tcpheader["Checksum"]      = ReadUInt16BigEndian(rdr);
     tcpheader["UrgentPointer"] = ReadUInt16BigEndian(rdr);

     // ignoring tcp options in header might be dangerous

     return tcpheader;
  } 

  UInt16 ReadUInt16BigEndian(BinaryReader rdr)
  {
     UInt16 res = (UInt16)(rdr.ReadByte());
     res <<= 8;
     res |= rdr.ReadByte();
     return(res);
  }

  UInt32 ReadUInt32BigEndian(BinaryReader rdr)
  {
     UInt32 res = (UInt32)(rdr.ReadByte());
     res <<= 8;
     res |= rdr.ReadByte();
     res <<= 8;
     res |= rdr.ReadByte();
     res <<= 8;
     res |= rdr.ReadByte();
     return(res);
  }

Ich glaube nicht, dass Sie so etwas wie die in C # tun können. Sie können entweder die Parse () Funktion benutzen um einen Versatz oder 3-Byte-Arrays erstellen zu beginnen; ein für die IP-Header, eines für die TCP-Header und einem für die Payload.

Es gibt keine Möglichkeit überprüfbaren Code, dies zu tun. Wenn Ihr Parse-Methode mit mit einem IEnumerable umgehen können, dann können Sie eine LINQ-Ausdruck verwenden

TCPHeader tcp = Parse(packet.Skip(20));

Einige Leute, die beantwortet

tcpbuffer.Skip(20).Take(20);

tat es falsch. Dies ist ausgezeichnete Lösung, aber der Code soll wie folgt aussehen:

packet.Skip(20).Take(20);

Sie sollten Überspringen verwenden und Nehmen Methoden auf Ihrem Haupt Paket und tcpbuffer sollte im Code nicht werden existieren Sie auf dem Laufenden. Auch Sie müssen nicht verwenden, dann System.Buffer.BlockCopy.

JaredPar war fast richtig, aber er vergaß, die Take-Methode

TCPHeader tcp = Parse(packet.Skip(20));

Aber er hat nicht falsch mit tcpbuffer . Ihre letzte Zeile Ihres geschrieben Code sollte wie folgt aussehen:

TCPHeader tcp = Parse(packet.Skip(20).Take(20));

Aber wenn Sie System.Buffer.BlockCopy verwenden sowieso statt überspringen wollen und Nehmen, denn vielleicht ist es besser, in der Leistung als Steven Robbins antwortete: „Aber System.Buffer.BlockCopy / System.Array.Copy sind wahrscheinlich effizienter“ oder Ihre Parse Funktion kann nicht behandeln IEnumerable<byte>, oder Sie sind mehr an System.Buffer.Block in Ihrer informierten Frage, dann würde ich empfehlen, auf einfach nur machen tcpbuffer < strong> nicht lokal Variable, aber privat oder geschützt oder Öffentlichkeit oder interne und statisch oder nicht (mit anderen Worten soll es außerhalb Methode, bei der geposteten Code wird ausgeführt, definiert und erstellt werden). So wird tcpbuffer nur erstellt werden, einmal , und seine Werte (Byte) wird jedes Mal, wenn Sie den Code übergeben eingestellt werden Sie bei System.Buffer.BlockCopy Zeile geschrieben.

Auf diese Weise Ihr Code aussehen kann:

class Program
{
    //Your defined fields, properties, methods, constructors, delegates, events and etc.
    private byte[] tcpbuffer = new byte[20];
    Your unposted method title(arguments/parameters...)
    {
    //Your unposted code before your posted code
    //byte[] tcpbuffer = new byte[ 20 ]; No need anymore! this line can be removed.
    System.Buffer.BlockCopy( packet, 20, this.tcpbuffer, 0, 20 );
    TCPHeader tcp = Parse( this.tcpbuffer );
    //Your unposted code after your posted code
    }
    //Your defined fields, properties, methods, constructors, delegates, events and etc.
}

oder einfach nur der notwendige Teil:

private byte[] tcpbuffer = new byte[20];
...
{
...
        //byte[] tcpbuffer = new byte[ 20 ]; No need anymore! This line can be removed.
        System.Buffer.BlockCopy( packet, 20, this.tcpbuffer, 0, 20 );
        TCPHeader tcp = Parse( this.tcpbuffer );
...
}

Wenn Sie getan haben:

private byte[] tcpbuffer;

statt, dann müssen Sie auf Ihrem Konstruktor / s fügen Sie die Zeile:

this.tcpbuffer = new byte[20];

oder

tcpbuffer = new byte[20];

Sie wissen, dass Sie nicht eingeben müssen diese. vor tcpbuffer, es ist optional, aber wenn Sie es statisch definiert, dann Sie können nicht tun. Stattdessen werden Sie die Klassennamen und dann den Punkt eingeben müssen ‚‘, oder lassen Sie es (nur den Namen des Feldes geben und das ist alles).

Warum das Problem nicht kippen und Klassen erstellen, die die Puffer überlagern Bits herausziehen?

// member variables
IPHeader ipHeader = new IPHeader();
TCPHeader tcpHeader = new TCPHeader();

// passing in the buffer, an offset and a length allows you
// to move the header over the buffer
ipHeader.SetBuffer( buffer, 0, 20 );

if( ipHeader.Protocol == TCP )
{
    tcpHeader.SetBuffer( buffer, ipHeader.ProtocolOffset, 20 );
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top