简体   繁体   English

在C#中使用字节数组

[英]Working with byte arrays in C#

I have a byte array that represents a complete TCP/IP packet. 我有一个字节数组,代表一个完整的TCP / IP数据包。 For clarification, the byte array is ordered like this: 为了澄清起见,字节数组的排序如下:

(IP Header - 20 bytes)(TCP Header - 20 bytes)(Payload - X bytes) (IP标头-20字节)(TCP标头-20字节)(有效载荷-X字节)

I have a Parse function that accepts a byte array and returns a TCPHeader object. 我有一个Parse函数,该函数接受一个字节数组并返回一个TCPHeader对象。 It looks like this: 看起来像这样:

TCPHeader Parse( byte[] buffer );

Given the original byte array, here is the way I'm calling this function right now. 给定原始字节数组,这就是我现在调用此函数的方式。

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

Is there a convenient way to pass the TCP byte array, ie, bytes 20-39 of the complete TCP/IP packet, to the Parse function without extracting it to a new byte array first? 是否有一种简便的方法可以将TCP字节数组(即完整TCP / IP数据包的字节20-39)传递给Parse函数,而无需先将其提取到新的字节数组中?

In C++, I could do the following: 在C ++中,我可以执行以下操作:

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

Is there anything similar in C#? C#中有类似的东西吗? I want to avoid the creation and subsequent garbage collection of the temporary byte array if possible. 如果可能,我想避免临时字节数组的创建和后续的垃圾回收。

A common practice you can see in the .NET framework, and that I recommend using here, is specifying the offset and length. 您可以在.NET框架中看到的一种常见做法是,建议在此处指定偏移量和长度,并建议在此处使用。 So make your Parse function also accept the offset in the passed array, and the number of elements to use. 因此,使您的Parse函数也接受传递的数组中的偏移量以及要使用的元素数。

Of course, the same rules apply as if you were to pass a pointer like in C++ - the array shouldn't be modified or else it may result in undefined behavior if you are not sure when exactly the data will be used. 当然,适用相同的规则,就像您要像在C ++中那样传递指针一样-不应修改数组,否则,如果您不确定何时确切使用数据,则可能导致未定义的行为。 But this is no problem if you are no longer going to be modifying the array. 但这不是问题,如果您不再要修改数组。

I would pass an ArraySegment<byte> in this case. 在这种情况下,我将传递ArraySegment<byte>

You would change your Parse method to this: 您可以将Parse方法更改为:

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

And then you would change the call to this: 然后您将调用更改为:

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

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

Using the ArraySegment<T> will not copy the array, and it will do the bounds checking for you in the constructor (so that you don't specify incorrect bounds). 使用ArraySegment<T>不会复制数组,它会在构造函数中为您执行边界检查(这样就不会指定错误的边界)。 Then you change your Parse method to work with the bounds specified in the segment, and you should be ok. 然后,更改您的Parse方法以使用段中指定的边界,您应该可以。

You can even create a convenience overload that will accept the full byte array: 您甚至可以创建一个方便的重载,将接受完整的字节数组:

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

If an IEnumerable<byte> is acceptable as an input rather than byte[] , and you're using C# 3.0, then you could write: 如果IEnumerable<byte>可接受作为输入而不是byte[] ,并且您使用的是C#3.0,则可以编写:

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

Note that this still allocates enumerator instances under the covers, so you don't escape allocation altogether, and so for a small number of bytes it may actually be slower than allocating a new array and copying the bytes into it. 请注意,这仍然在幕后分配枚举数实例,因此您不会完全逃避分配,因此对于少量字节,它实际上可能比分配新数组并将字节复制到其中要慢。

I wouldn't worry too much about allocation and GC of small temporary arrays to be honest though. 老实说,我不会太担心小型临时数组的分配和GC。 The .NET garbage collected environment is extremely efficient at this type of allocation pattern, particularly if the arrays are short lived, so unless you've profiled it and found GC to be a problem then I'd write it in the most intuitive way and fix up performance issues when you know you have them. 在这种分配模式下,.NET垃圾收集环境非常有效,尤其是在数组寿命短的情况下,因此,除非您对其进行了分析并发现GC有问题,否则我将以最直观的方式编写它,并且当您知道存在性能问题时,请进行修复。

If you really need these kind of control, you gotta look at unsafe feature of C#. 如果您确实需要这些控制,则必须查看C#的unsafe功能。 It allows you to have a pointer and pin it so that GC doesn't move it: 它允许您拥有一个指针并将其固定,以使GC不会移动它:

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

However this practice is not suggested for working with managed only code if there are no performance issues. 但是,如果不存在性能问题,则建议不要将这种做法用于仅托管代码。 You could pass the offset and length as in Stream class. 您可以像在Stream类中一样传递偏移量和长度。

If you can change the parse() method, change it to accept the offset where the processing should begin. 如果可以更改parse()方法,请对其进行更改以接受应该在其中开始处理的偏移量。 TCPHeader Parse( byte[] buffer , int offset); TCPHeader Parse(byte [] buffer,int offset);

You could use LINQ to do something like: 您可以使用LINQ做类似的事情:

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

But System.Buffer.BlockCopy / System.Array.Copy are probably more efficient. 但是System.Buffer.BlockCopy / System.Array.Copy可能更有效。

This is how I solved it coming from being ac programmer to ac# programmer. 这就是我解决从ac程序员到ac#程序员的方法。 I like to use MemoryStream to convert it to a stream and then BinaryReader to break apart the binary block of data. 我喜欢使用MemoryStream将其转换为流,然后使用BinaryReader分解二进制数据块。 Had to add the two helper functions to convert from network order to little endian. 必须添加两个帮助器功能以将网络顺序转换为小端。 Also for building a byte[] to send see Is there a way cast an object back to it original type without specifing every case? 有关构建byte []进行发送的信息,请参见是否有一种方法可以在不指定每种情况的情况下将对象强制转换回其原始类型? which has a function that allow for converting from an array of objects to a byte[]. 它具有允许从对象数组转换为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);
  }

Some people who answered 有人回答

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

did it wrong. 做错了。 This is excellent solution, but the code should look like: 这是一个很好的解决方案,但是代码应如下所示:

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

You should use Skip and Take methods on your main packet , and tcpbuffer should not be exist in the code you posted. 您应该在主数据包上使用Skip and Take方法,并且tcpbuffer在发布的代码中不应存在。 Also you don't have to use then System.Buffer.BlockCopy . 另外,您也不必使用System.Buffer.BlockCopy

JaredPar was almost correct, but he forgot the Take method JaredPar几乎是正确的,但他忘记了Take方法

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

But he didn't get wrong with tcpbuffer . 但是他对tcpbuffer没错 Your last line of your posted code should look like: 您发布的代码的最后一行应如下所示:

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

But if you want to use System.Buffer.BlockCopy anyway instead Skip and Take, because maybe it is better in performance as Steven Robbins answered : "But System.Buffer.BlockCopy / System.Array.Copy are probably more efficient", or your Parse function cannot deal with IEnumerable<byte> , or you are more used to System.Buffer.Block in your posted question, then I would recommend to simply just make tcpbuffer not local variable, but private or protected or public or internal and static or not field (in other words it should be defined and created outside method where your posted code is executed). 但是,如果您还是要使用System.Buffer.BlockCopy而不是“跳过并接受”,因为如Steven Robbins回答的那样,它的性能可能更好:“但是System.Buffer.BlockCopy / System.Array.Copy可能更有效”,或者您可以解析函数无法处理IEnumerable<byte> ,或者您更习惯于已发布的问题中的System.Buffer.Block,那么我建议您使tcpbuffer 不是局部变量,而是使privateprotectedpublicinternalstatic或不是字段 (换句话说,应该在执行您发布的代码的方法之外定义和创建)。 Thus tcpbuffer will be created only once , and his values (bytes) will be set every time you pass the code you posted at System.Buffer.BlockCopy line. 因此,tcpbuffer将仅创建一次 ,并且每次您传递在System.Buffer.BlockCopy行上发布的代码时,都将设置其值(字节)。

This way your code can look like: 这样,您的代码可以如下所示:

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

or simply only the necessary part: 或仅是必要的部分:

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

If you did: 如果您这样做:

private byte[] tcpbuffer;

instead, then you must on your constructor/s add the line: 相反,那么您必须在构造函数上添加以下行:

this.tcpbuffer = new byte[20];

or 要么

tcpbuffer = new byte[20];

You know that you don't have to type this. 您知道不必键入此内容。 before tcpbuffer, it is optional, but if you defined it static, then you cannot do that. 在tcpbuffer之前,它是可选的,但是如果您将其定义为静态,则不能这样做。 Instead you'll have to type the class name and then the dot '.', or leave it (just type the name of the field and that's it all). 取而代之的是,您必须键入类名称,然后输入点“。”,或保留它(只需键入字段名称,仅此而已)。

I don't think you can do something like that in C#. 我认为您无法在C#中执行类似的操作。 You could either make the Parse() function use an offset, or create 3 byte arrays to begin with; 您可以使Parse()函数使用偏移量,也可以创建3个字节的数组作为开始。 one for the IP Header, one for the TCP Header and one for the Payload. 一种用于IP标头,一种用于TCP标头,另一种用于有效负载。

There is no way using verifiable code to do this. 无法使用可验证的代码来执行此操作。 If your Parse method can deal with having an IEnumerable<byte> then you can use a LINQ expression 如果您的Parse方法可以处理IEnumerable <byte>,则可以使用LINQ表达式

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

Why not flip the problem and create classes that overlay the buffer to pull bits out? 为什么不翻转问题并创建覆盖缓冲区的类以提取位呢?

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM