在 C# 中使用字节数组

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/409256/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 02:14:40  来源:igfitidea点击:

Working with byte arrays in C#

c#.netnetworking.net-3.5bytearray

提问by Matt Davis

I have a byte array that represents a complete TCP/IP packet. For clarification, the byte array is ordered like this:

我有一个表示完整 TCP/IP 数据包的字节数组。为了澄清起见,字节数组的顺序如下:

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

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

I have a Parsefunction that accepts a byte array and returns a TCPHeaderobject. It looks like this:

我有一个Parse接受字节数组并返回一个TCPHeader对象的函数。它看起来像这样:

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, i.e., bytes 20-39 of the complete TCP/IP packet, to the Parsefunction 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#? I want to avoid the creation and subsequent garbage collection of the temporary byte array if possible.

C# 中有类似的东西吗?如果可能,我想避免临时字节数组的创建和随后的垃圾收集。

采纳答案by Spodi

A common practice you can see in the .NET framework, and that I recommend using here, is specifying the offset and length. So make your Parse function also accept the offset in the passed array, and the number of elements to use.

您可以在 .NET 框架中看到的一种常见做法,我建议在此处使用,是指定偏移量和长度。因此,让您的 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. But this is no problem if you are no longer going to be modifying the array.

当然,适用的规则与在 C++ 中传递指针一样 - 不应修改数组,否则如果您不确定何时确切地使用数据,则可能会导致未定义的行为。但是,如果您不再打算修改数组,这没有问题。

回答by Aistina

I don't think you can do something like that in C#. You could either make the Parse() function use an offset, or create 3 byte arrays to begin with; one for the IP Header, one for the TCP Header and one for the Payload.

我不认为你可以在 C# 中做这样的事情。您可以使 Parse() 函数使用偏移量,也可以创建 3 字节数组作为开始;一种用于 IP 标头,一种用于 TCP 标头,一种用于有效载荷。

回答by Greg Beech

If an IEnumerable<byte>is acceptable as an input rather than byte[], and you're using C# 3.0, then you could write:

如果 anIEnumerable<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. 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.

老实说,我不会太担心小型临时数组的分配和 GC。.NET 垃圾收集环境在这种类型的分配模式下非常有效,特别是如果数组是短暂的,所以除非您对其进行了分析并发现 GC 是一个问题,否则我会以最直观的方式编写它,并且当你知道你有性能问题时修复它们。

回答by Mehrdad Afshari

If you really need these kind of control, you gotta look at unsafefeature of C#. It allows you to have a pointer and pin it so that GC doesn't move it:

如果你真的需要这些控件,你得看看unsafeC# 的特性。它允许您拥有一个指针并将其固定,以便 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 Streamclass.

但是,如果没有性能问题,则不建议将此做法用于仅使用托管代码。您可以在Stream课堂上传递偏移量和长度。

回答by JaredPar

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

回答by casperOne

I would pass an ArraySegment<byte>in this case.

ArraySegment<byte>在这种情况下,我会通过一个。

You would change your Parsemethod 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). Then you change your Parsemethod to work with the bounds specified in the segment, and you should be ok.

使用ArraySegment<T>不会复制数组,它会在构造函数中为你做边界检查(这样你就不会指定不正确的边界)。然后你改变你的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)

回答by Steven Robbins

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 可能更有效。

回答by DotNET

If you can change the parse() method, change it to accept the offset where the processing should begin. TCPHeader Parse( byte[] buffer , int offset);

如果您可以更改 parse() 方法,请将其更改为接受处理应该开始的偏移量。TCPHeader Parse( byte[] buffer , int offset);

回答by headsling

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

回答by Rex Logan

This is how I solved it coming from being a c programmer to a c# programmer. I like to use MemoryStream to convert it to a stream and then BinaryReader to break apart the binary block of data. 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?which has a function that allow for converting from an array of objects to a byte[].

这就是我从 ac 程序员到 ac# 程序员的解决方法。我喜欢使用 MemoryStream 将其转换为流,然后使用 BinaryReader 来分解二进制数据块。必须添加两个辅助函数才能从网络顺序转换为小端。同样用于构建要发送的字节 [] 是否有一种方法可以将对象转换回原始类型而不指定每种情况?它具有允许从对象数组转换为字节 [] 的函数。

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