Pregunta

Tengo una matriz de bytes que representa un paquete TCP / IP completo. Para aclarar, la matriz de bytes se ordena de la siguiente manera:

(Encabezado IP - 20 bytes) (Encabezado TCP - 20 bytes) (Carga útil - X bytes)

Tengo una función Parse que acepta una matriz de bytes y devuelve un objeto TCPHeader. Se ve así:

TCPHeader Parse( byte[] buffer );

Dada la matriz de bytes original, aquí es cómo llamo a esta función en este momento.

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

¿Hay alguna manera conveniente de pasar la matriz de bytes TCP, es decir, los bytes 20-39 del paquete TCP / IP completo, a la función <=> sin extraerla primero a una nueva matriz de bytes?

En C ++, podría hacer lo siguiente:

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

¿Hay algo similar en C #? Quiero evitar la creación y posterior recolección de basura de la matriz de bytes temporal si es posible.

¿Fue útil?

Solución

Una práctica común que puede ver en el marco .NET, y que recomiendo usar aquí, es especificar el desplazamiento y la longitud. Por lo tanto, haga que su función Parse también acepte el desplazamiento en la matriz pasada y la cantidad de elementos que se utilizarán.

Por supuesto, las mismas reglas se aplican como si fuera a pasar un puntero como en C ++: la matriz no debe modificarse o, de lo contrario, puede dar lugar a un comportamiento indefinido si no está seguro de cuándo se utilizarán exactamente los datos. Pero esto no es un problema si ya no va a modificar la matriz.

Otros consejos

Le pasaría un ArraySegment<byte> en este caso.

Cambiaría su método Parse a esto:

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

Y luego cambiaría la llamada a esto:

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

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

El uso de ArraySegment<T> no copiará la matriz, y hará la comprobación de límites en el constructor (para que no especifique límites incorrectos). Luego cambia su método <=> para trabajar con los límites especificados en el segmento, y debería estar bien.

Incluso puede crear una sobrecarga de conveniencia que acepte la matriz de bytes completa:

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

Si un IEnumerable<byte> es aceptable como entrada en lugar de byte[], y está usando C # 3.0, entonces podría escribir:

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

Tenga en cuenta que esto todavía asigna instancias de enumerador debajo de las cubiertas, por lo que no escapa de la asignación por completo, por lo que para un pequeño número de bytes, en realidad puede ser más lento que asignar una nueva matriz y copiar los bytes en ella.

Para ser honesto, no me preocuparía demasiado la asignación y GC de pequeños arreglos temporales. El entorno de recolección de basura .NET es extremadamente eficiente en este tipo de patrón de asignación, particularmente si las matrices son de corta duración, por lo tanto, a menos que lo haya perfilado y haya encontrado que GC es un problema, lo escribiría de la manera más intuitiva y solucione problemas de rendimiento cuando sepa que los tiene.

Si realmente necesita este tipo de control, debe mirar la función unsafe de C #. Le permite tener un puntero y fijarlo para que GC no lo mueva:

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

Sin embargo, esta práctica no se sugiere para trabajar con código administrado solo si no hay problemas de rendimiento. Puede pasar el desplazamiento y la longitud como en Stream clase.

Si puede cambiar el método parse (), cámbielo para aceptar el desplazamiento donde debería comenzar el procesamiento. TCPHeader Parse (byte [] buffer, int offset);

Puede usar LINQ para hacer algo como:

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

Pero System.Buffer.BlockCopy / System.Array.Copy son probablemente más eficientes.

Así es como lo resolví de ser un programador en C a un programador en C #. Me gusta usar MemoryStream para convertirlo en una secuencia y luego BinaryReader para separar el bloque binario de datos. Tuve que agregar las dos funciones auxiliares para convertir del orden de red a little endian. También para construir un byte [] para enviar ver ¿Hay alguna forma de devolver un objeto al tipo original sin especificar cada caso? que tiene una función que permite la conversión de una matriz de objetos a 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);
  }

No creo que puedas hacer algo así en C #. Puede hacer que la función Parse () use un desplazamiento o crear matrices de 3 bytes para empezar; uno para el encabezado IP, uno para el encabezado TCP y uno para la carga útil.

No hay forma de usar código verificable para hacer esto. Si su método Parse puede tratar con tener un IEnumerable & Lt; byte & Gt; entonces puedes usar una expresión LINQ

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

Algunas personas que respondieron

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

lo hizo mal. Esta es una solución excelente , pero el código debería verse así:

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

Debe usar los métodos Saltar y Tomar en su paquete principal , y tcpbuffer no debe existir en el código que publicó. Además, no tiene que usar System.Buffer.BlockCopy.

JaredPar era casi correcto, pero olvidó el método Take

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

Pero no se equivocó con tcpbuffer . Su última línea de su código publicado debería verse así:

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

Pero si quieres usar System.Buffer.BlockCopy de todos modos Saltar y tomar, porque tal vez es mejor en rendimiento como Steven Robbins respondió: " Pero System.Buffer.BlockCopy / System.Array.Copy son probablemente más eficiente " ;, o su función Parse no puede tratar con IEnumerable<byte>, o está más acostumbrado a System.Buffer.Block en su pregunta publicada, entonces recomendaría simplemente haga que tcpbuffer no sea local variable, sino privado o protegido o público o interno y estático o no campo (en otras palabras, debe definirse y crearse el método fuera donde se ejecuta el código publicado ) Por lo tanto, tcpbuffer se creará solo una vez , y sus valores (bytes) se establecerán cada vez que pase el código que publicó en la línea System.Buffer.BlockCopy.

De esta manera su código puede verse así:

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 simplemente solo la parte necesaria:

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

Si lo hiciste:

private byte[] tcpbuffer;

en su lugar, debe agregar en su constructor / s la línea:

this.tcpbuffer = new byte[20];

o

tcpbuffer = new byte[20];

Sabe que no tiene que escribir esto. antes de tcpbuffer, es opcional, pero si lo definió como estático, entonces no puede hacer eso. En su lugar, tendrá que escribir el nombre de la clase y luego el punto '.', O dejarlo (simplemente escriba el nombre del campo y eso es todo).

¿Por qué no voltear el problema y crear clases que superpongan el búfer para extraer bits?

// 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 );
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top