Trabalhando com matrizes de bytes em C #
-
03-07-2019 - |
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.
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
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 );
}