Domanda

Ho un array di byte che rappresenta un pacchetto TCP / IP completo. Per chiarimenti, l'array di byte è ordinato in questo modo:

(Intestazione IP - 20 byte) (Intestazione TCP - 20 byte) (Carico utile - X byte)

Ho una funzione Parse che accetta un array di byte e restituisce un oggetto TCPHeader. Sembra così:

TCPHeader Parse( byte[] buffer );

Dato l'array di byte originale, ecco come sto chiamando questa funzione in questo momento.

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

Esiste un modo conveniente per passare l'array di byte TCP, ovvero i byte 20-39 del pacchetto TCP / IP completo, alla funzione <=> senza estrarlo prima in un nuovo array di byte?

In C ++, potrei fare quanto segue:

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

C'è qualcosa di simile in C #? Voglio evitare la creazione e la successiva garbage collection dell'array di byte temporaneo, se possibile.

È stato utile?

Soluzione

Una pratica comune che puoi vedere nel framework .NET, e che ti consiglio di usare qui, è specificare l'offset e la lunghezza. Quindi fai in modo che la tua funzione Parse accetti anche l'offset nella matrice passata e il numero di elementi da usare.

Naturalmente, si applicano le stesse regole come se si dovesse passare un puntatore come in C ++ - l'array non dovrebbe essere modificato altrimenti potrebbe comportare un comportamento indefinito se non si è sicuri di quando verranno utilizzati esattamente i dati. Ma questo non è un problema se non modificherai più l'array.

Altri suggerimenti

Vorrei passare un ArraySegment<byte> in questo caso.

Dovresti cambiare il tuo Parse metodo in questo:

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

E poi cambieresti la chiamata in questo:

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

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

L'uso di ArraySegment<T> non copierà l'array e farà i controlli verificandoti nel costruttore (in modo da non specificare limiti errati). Quindi modifichi il tuo <=> metodo per lavorare con i limiti specificati nel segmento e dovresti essere a posto.

Puoi persino creare un sovraccarico di convenienza che accetti l'array di byte completo:

// 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)

Se un IEnumerable<byte> è accettabile come input anziché byte[] e stai usando C # 3.0, allora potresti scrivere:

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

Notare che questo alloca ancora le istanze di enumeratore sotto le copertine, quindi non si evita del tutto l'allocazione, e quindi per un piccolo numero di byte potrebbe effettivamente essere più lento rispetto all'allocazione di un nuovo array e alla copia dei byte in esso.

Non mi preoccuperei troppo dell'allocazione e della GC di piccoli array temporanei per essere onesti. L'ambiente .NET Garbage Collected è estremamente efficiente con questo tipo di modello di allocazione, in particolare se gli array hanno una vita breve, quindi a meno che non sia stato profilato e trovato GC un problema, lo scriverei nel modo più intuitivo e risolvi i problemi di prestazioni quando sai di averli.

Se hai davvero bisogno di questo tipo di controllo, devi guardare unsafe funzionalità di C #. Ti consente di avere un puntatore e bloccarlo in modo che GC non lo sposti:

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

Tuttavia questa pratica non è consigliata per lavorare con codice gestito solo se non ci sono problemi di prestazioni. È possibile passare l'offset e la lunghezza come nella Stream classe.

Se è possibile modificare il metodo parse (), modificarlo per accettare l'offset da cui dovrebbe iniziare l'elaborazione. TCPHeader Parse (byte [] buffer, int offset);

Puoi usare LINQ per fare qualcosa del tipo:

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

Ma System.Buffer.BlockCopy / System.Array.Copy sono probabilmente più efficienti.

Questo è il modo in cui l'ho risolto dall'essere un programmatore c a un programmatore c #. Mi piace usare MemoryStream per convertirlo in un flusso e poi BinaryReader per spezzare il blocco binario di dati. Ho dovuto aggiungere le due funzioni di supporto per convertire dall'ordine di rete a little endian. Anche per la creazione di un byte [] da inviare vedere Esiste un modo per riportare un oggetto al suo tipo originale senza specificare ogni caso? che ha una funzione che consente la conversione da un array di oggetti in un 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);
  }

Non credo che tu possa fare qualcosa del genere in C #. È possibile fare in modo che la funzione Parse () utilizzi un offset o creare array di 3 byte per cominciare; uno per l'intestazione IP, uno per l'intestazione TCP e uno per il payload.

Non è possibile utilizzare il codice verificabile per farlo. Se il tuo metodo Parse è in grado di gestire IEnumerable & Lt; byte & Gt; quindi puoi usare un'espressione LINQ

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

Alcune persone che hanno risposto

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

ha sbagliato. Questa è soluzione eccellente , ma il codice dovrebbe apparire come:

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

Dovresti usare i metodi Skip and Take sul tuo pacchetto principale e tcpbuffer non dovrebbe essere presente nel codice che hai pubblicato. Inoltre non è necessario utilizzare System.Buffer.BlockCopy.

JaredPar era quasi corretto, ma ha dimenticato il metodo Take

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

Ma non ha sbagliato con tcpbuffer . L'ultima riga del codice pubblicato dovrebbe essere simile a:

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

Ma se vuoi usare System.Buffer.BlockCopy comunque salta e prendi, perché forse è meglio in termini di prestazioni come ha risposto Steven Robbins: " Ma System.Buffer.BlockCopy / System.Array.Copy sono probabilmente " più efficace; oppure la tua funzione di analisi non può gestire IEnumerable<byte>, oppure sei più abituato a System.Buffer.Block nella tua domanda postata, quindi consiglierei di semplicemente rendi tcpbuffer non locale variabile, ma privato o protetto o pubblico o interno e statico o non campo (in altre parole, dovrebbe essere definito e creato il metodo esterno in cui viene eseguito il codice pubblicato ). Pertanto tcpbuffer verrà creato solo una volta e i suoi valori (byte) verranno impostati ogni volta che passi il codice che hai pubblicato sulla riga System.Buffer.BlockCopy.

In questo modo il tuo codice può apparire come:

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.
}

o semplicemente solo la parte necessaria:

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 );
...
}

Se lo hai fatto:

private byte[] tcpbuffer;

invece, sul tuo costruttore / i devi aggiungere la linea:

this.tcpbuffer = new byte[20];

o

tcpbuffer = new byte[20];

Sai che non devi digitare questo. prima di tcpbuffer, è facoltativo, ma se lo hai definito statico, allora non puoi farlo. Dovrai invece digitare il nome della classe e quindi il punto '.', Oppure lasciarlo (basta digitare il nome del campo e il gioco è fatto).

Perché non capovolgere il problema e creare classi che si sovrappongono al buffer per estrarre i bit?

// 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 );
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top