我有一个表示完整TCP / IP数据包的字节数组。为了澄清,字节数组的排序如下:
(IP标头 - 20字节)(TCP标头 - 20字节)(有效负载 - X字节)
我有一个Parse
函数,它接受一个字节数组并返回一个TCPHeader
对象。它看起来像这样:
TCPHeader Parse( byte[] buffer );
给定原始字节数组,这就是我现在调用此函数的方式。
byte[] tcpbuffer = new byte[ 20 ];
System.Buffer.BlockCopy( packet, 20, tcpbuffer, 0, 20 );
TCPHeader tcp = Parse( tcpbuffer );
是否有方便的方法将TCP字节数组(即完整TCP / IP数据包的字节20-39)传递给Parse
函数,而不将其首先提取到新的字节数组?
在C ++中,我可以执行以下操作:
TCPHeader tcp = Parse( &packet[ 20 ] );
C#中有类似的东西吗?我希望尽可能避免临时字节数组的创建和后续垃圾收集。
答案 0 :(得分:24)
您可以在.NET框架中看到并且我建议在此处使用的常见做法是指定偏移量和长度。因此,使您的Parse函数也接受传递数组中的偏移量以及要使用的元素数。
当然,同样的规则也适用于传递类似C ++的指针 - 不应修改数组,否则如果您不确定何时使用数据,则可能导致未定义的行为。但是,如果你不再修改数组,那就没问题了。
答案 1 :(得分:22)
在这种情况下我会传递ArraySegment<byte>
。
您可以将Parse
方法更改为:
// Changed TCPHeader to TcpHeader to adhere to public naming conventions.
TcpHeader Parse(ArraySegment<byte> buffer)
然后你会改变对此的调用:
// Create the array segment.
ArraySegment<byte> seg = new ArraySegment<byte>(packet, 20, 20);
// Call parse.
TcpHeader header = Parse(seg);
使用ArraySegment<T>
不会复制数组,它会在构造函数中为您进行边界检查(这样就不会指定不正确的边界)。然后你改变你的Parse
方法来处理段中指定的边界,你应该没问题。
您甚至可以创建一个方便的重载来接受完整的字节数组:
// 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)
答案 2 :(得分:4)
如果IEnumerable<byte>
可以作为输入而不是byte[]
,并且您使用的是C#3.0,那么您可以写:
tcpbuffer.Skip(20).Take(20);
请注意,这仍然会在封面下分配枚举器实例,因此您不会完全转义分配,因此对于少量字节,它实际上可能比分配新数组并将字节复制到其中要慢。
但是,我不会过分担心小型临时阵列的分配和GC。 .NET垃圾收集环境在这种类型的分配模式下非常有效,特别是如果数组是短暂的,所以除非你已经对它进行了分析并发现GC是个问题,否则我会以最直观的方式编写它并且当你知道有这些问题时,请解决性能问题。答案 3 :(得分:3)
如果你真的需要这种控制,你必须看看C#的unsafe
功能。它允许你有一个指针并固定它,以便GC不会移动它:
fixed(byte* b = &bytes[20]) {
}
但是,如果没有性能问题,则不建议使用仅托管代码进行此操作。您可以传递Stream
类中的偏移量和长度。
答案 4 :(得分:2)
如果您可以更改parse()方法,请将其更改为接受处理开始的偏移量。 TCPHeader Parse(byte [] buffer,int offset);
答案 5 :(得分:1)
您可以使用LINQ执行以下操作:
tcpbuffer.Skip(20).Take(20);
但System.Buffer.BlockCopy / System.Array.Copy可能更有效。
答案 6 :(得分:1)
这就是我从c程序员到c#程序员的解决方法。我喜欢使用MemoryStream将其转换为流,然后使用BinaryReader来拆分二进制数据块。不得不添加两个辅助函数来从网络顺序转换为小端。也用于构建一个byte []发送看看 Is there a way cast an object back to it original type without specifing every case?,其函数允许从对象数组转换为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);
}
答案 7 :(得分:0)
我认为你不能在C#中做那样的事情。您可以使Parse()函数使用偏移量,或者创建3字节数组开始;一个用于IP标头,一个用于TCP标头,一个用于有效负载。
答案 8 :(得分:0)
使用可验证的代码无法执行此操作。如果你的Parse方法可以处理IEnumerable&lt; byte&gt;然后你可以使用LINQ表达式
TCPHeader tcp = Parse(packet.Skip(20));
答案 9 :(得分:0)
为什么不翻转问题并创建覆盖缓冲区的类来拉出位?
// 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 );
}
答案 10 :(得分:0)
有些人回答
tcpbuffer.Skip(20).Take(20);
做错了。这是优秀的解决方案,但代码应如下所示:
packet.Skip(20).Take(20);
您应在主数据包上使用Skip and Take方法,并且您发布的代码中不应存在 tcpbuffer 。此外,您不必使用System.Buffer.BlockCopy
。
JaredPar 几乎是正确的,但他忘记了Take方法
TCPHeader tcp = Parse(packet.Skip(20));
但他对 tcpbuffer 没有错。 您发布的代码的最后一行应如下所示:
TCPHeader tcp = Parse(packet.Skip(20).Take(20));
但是如果你想使用System.Buffer.BlockCopy而不是Skip and Take,因为可能性能更好,因为Steven Robbins回答:“但System.Buffer.BlockCopy / System.Array.Copy可能更有效” ,或者您的Parse函数不能处理IEnumerable<byte>
,或者您在发布的问题中更习惯使用System.Buffer.Block,那么我建议只是使tcpbuffer 非本地变量,但私有或受保护或公开或内部和静态或不字段(换句话说,应该定义并创建外部方法,执行您发布的代码)。因此tcpbuffer只会被创建一次,每次传递你在System.Buffer.BlockCopy行发布的代码时,都会设置他的值(字节)。
这样您的代码可能如下所示:
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.
}
或仅仅是必要的部分:
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 );
...
}
如果你这样做了:
private byte[] tcpbuffer;
相反,那么你必须在构造函数上添加以下行:
this.tcpbuffer = new byte[20];
或
tcpbuffer = new byte[20];
你知道你不必在tcpbuffer之前键入 this。,它是可选的,但是如果你将它定义为静态,那么不能那样做。相反,你必须输入类名,然后输入点'。',或者保留它(只需输入字段的名称即可)。