Pergunta

Eu tenho uma matriz de bytes que representa um pacote completo TCP / IP. Para esclarecimento, a matriz de bytes é ordenado como este:

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

Eu tenho uma função Parse que aceita uma matriz de bytes e retorna um objeto TCPHeader. Parece que este:

TCPHeader Parse( byte[] buffer );

Dada a matriz de bytes original, aqui é a maneira que eu estou chamando essa função no momento.

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

Existe uma maneira conveniente para passar a matriz byte TCP, ou seja, bytes 20-39 do pacote completa TCP / IP, para a função Parse sem extraí-lo para uma nova matriz de bytes em primeiro lugar?

Em C ++, eu poderia fazer o seguinte:

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

Existe algo semelhante em C #? Eu quero evitar a criação e posterior coleta de lixo da matriz de bytes temporária se possível.

Foi útil?

Solução

Uma prática comum você pode ver no quadro NET, e que eu recomendo usar aqui, está especificando o deslocamento e comprimento. Então faça a sua função de análise também aceitar o deslocamento na matriz passada, eo número de elementos para uso.

É claro, as mesmas regras aplicam-se como se estivesse a passar um ponteiro como em C ++ - a matriz não deve ser modificado ou então ele pode resultar em um comportamento indefinido se você não tem certeza de quando será usado exatamente os dados. Mas isso não é problema se você não está mais indo para ser modificar a matriz.

Outras dicas

Gostaria de passar um ArraySegment<byte> neste caso.

Você mudaria seu método Parse a esta:

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

E então você mudaria a chamada para o seguinte:

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

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

Usando o ArraySegment<T> não vai copiar a matriz, e ele vai fazer os verificação de limites para você no construtor (de modo que você não especifica limites incorretos). Em seguida, você alterar o método de Parse para trabalhar com os limites especificados no segmento, e você deve estar ok.

Você pode até criar uma sobrecarga de conveniência que aceitará o array de bytes 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 um IEnumerable<byte> é aceitável como uma entrada em vez de byte[], e você estiver usando C # 3.0, então você poderia escrever:

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

Note que este ainda aloca casos enumerador debaixo das cobertas, para que você não escapar alocação completamente, e assim por um pequeno número de bytes que pode realmente ser mais lento do que a atribuição de uma nova matriz e copiar os bytes para ele.

Eu não me preocuparia muito com alocação e GC de pequenos arranjos temporários para ser honesto embora. O ambiente .NET lixo coletado é extremamente eficiente neste tipo de padrão de alocação, particularmente se as matrizes são de curta duração, a menos que você tenha perfilado-lo e encontrou GC a ser um problema, então eu escrevê-lo da maneira mais intuitiva e corrigir problemas de desempenho quando você sabe que tê-los.

Se você realmente precisa deste tipo de controle, você tem que olhar para recurso unsafe de C #. Ele permite que você tem um ponteiro e fixá-lo para que GC não movê-lo:

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

No entanto, esta prática não é sugerido para trabalhar com apenas código gerenciado se não há problemas de desempenho. Você poderia passar o deslocamento e comprimento como em classe Stream.

Se você pode alterar o método parse (), alterá-lo a aceitar o deslocamento em que a transformação deve começar. TCPHeader Analisar (byte [] tampão, int offset);

Você pode usar o LINQ para fazer algo como:

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

Mas System.Buffer.BlockCopy / System.Array.Copy são provavelmente mais eficiente.

Isto é como eu resolver aquilo que vem de ser um c programador para um programador C #. Eu gosto de usar MemoryStream para convertê-lo em um riacho e, em seguida, BinaryReader para quebrar o bloco de dados binário. Teve que adicionar as duas funções auxiliares converter de forma de rede para little endian. Também para a construção de um byte [] para enviar ver Existe uma maneira converter um objeto de volta para que tipo original sem especificando todos os casos? que tem uma função que permite a conversão de um array de objetos para um 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);
  }

Eu não acho que você pode fazer algo como isso em C #. Você poderia fazer uso da função Parse () um deslocamento, ou criar 3 matrizes de bytes para começar; uma para o cabeçalho IP, uma para o cabeçalho TCP e uma para a carga útil.

Não há nenhuma maneira usando código verificável para fazer isso. Se o seu método Parse pode lidar com ter um IEnumerable , então você pode usar uma expressão LINQ

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

Algumas pessoas que responderam

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

fiz tudo errado. Esta é excelente solução, mas o código deve ser semelhante a:

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

Você deve usar Skip e Take métodos em seu principal pacote e tcpbuffer não deve ser existir no código que você postou. Além disso, você não tem que usar, então System.Buffer.BlockCopy.

JaredPar foi quase correto, mas ele esqueceu o método Take

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

Mas ele não conseguiu errado com tcpbuffer . Sua última linha do seu código postado deve ser parecido:

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

Mas se você quiser usar System.Buffer.BlockCopy de qualquer maneira, em vez Ir e receber, porque talvez seja melhor em desempenho como Steven Robbins respondeu: "Mas System.Buffer.BlockCopy / System.Array.Copy são provavelmente mais eficiente" , ou a sua função de análise não pode acordo com IEnumerable<byte>, ou você está mais utilizado para System.Buffer.Block na sua pergunta postado, então eu recomendaria a simplesmente faça tcpbuffer < strong> não é local variável, mas privada ou protegido ou público ou interno e estática ou não (em outras palavras, deve ser definido e criado fora método onde o seu código postado é executado). Assim tcpbuffer será criado somente uma vez , e seus valores (bytes) será definido cada vez que você passar o código que você postou na linha System.Buffer.BlockCopy.

Desta forma, o código pode ser parecido:

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

ou simplesmente apenas a parte necessária:

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 você fez:

private byte[] tcpbuffer;

Em vez disso, então você deve em seu construtor / s adicionar a linha:

this.tcpbuffer = new byte[20];

ou

tcpbuffer = new byte[20];

Você sabe que você não tem que digitar isso. antes tcpbuffer, é opcional, mas se você definiu estático, então você não pode fazer isso. Em vez disso você terá que digitar o nome da classe e, em seguida, o ponto '', ou deixá-lo (basta digitar o nome do campo e isso é tudo).

Por que não inverter o problema e criar classes que sobrepõem o buffer em pedaços retire?

// 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 em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top