我有一个表示完整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)传递给<=>函数,而无需先将其提取到新的字节数组?

在C ++中,我可以执行以下操作:

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

C#中有类似的东西吗?我希望尽可能避免临时字节数组的创建和后续垃圾收集。

有帮助吗?

解决方案

您可以在.NET框架中看到并且我建议在此处使用的常见做法是指定偏移量和长度。因此,使您的Parse函数也接受传递数组中的偏移量以及要使用的元素数。

当然,同样的规则也适用于传递类似C ++的指针 - 不应修改数组,否则如果您不确定何时使用数据,则可能导致未定义的行为。但是,如果你不再修改数组,那就没问题了。

其他提示

在这种情况下,我会通过 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>不会复制数组,它将在构造函数中为您进行边界检查(这样就不会指定不正确的边界)。然后你改变你的<=>方法来处理段中指定的边界,你应该没问题。

您甚至可以创建一个方便的重载来接受完整的字节数组:

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

如果IEnumerable<byte>可以作为输入而不是byte[],并且您正在使用C#3.0,那么您可以写:

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

请注意,这仍然会在封面下分配枚举器实例,因此您不会完全转义分配,因此对于少量字节,它实际上可能比分配新数组并将字节复制到其中要慢。

但是,我不会过分担心小型临时阵列的分配和GC。 .NET垃圾收集环境在这种类型的分配模式下非常有效,特别是如果数组是短暂的,所以除非你已经对它进行了分析并发现GC是个问题,否则我会以最直观的方式编写它并且当你知道有这些问题时,请解决性能问题。

如果你真的需要这种控制,你必须看看C#的unsafe功能。它允许你有一个指针并固定它,以便GC不会移动它:

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

但是,如果没有性能问题,则不建议使用仅托管代码进行此操作。您可以像Stream类一样传递偏移量和长度。

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

您可以使用LINQ执行以下操作:

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

但System.Buffer.BlockCopy / System.Array.Copy可能更有效。

这就是我从c程序员到c#程序员的解决方法。我喜欢使用MemoryStream将其转换为流,然后使用BinaryReader来拆分二进制数据块。不得不添加两个辅助函数来从网络顺序转换为小端。也用于构建一个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);
  }

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

使用可验证的代码无法执行此操作。如果你的Parse方法可以处理IEnumerable <!> lt; byte <!> gt;然后你可以使用LINQ表达式

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

有些人回答

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。,它是可选的,但是如果你将它定义为静态,那么不能那样做。相反,你必须输入类名,然后输入点'。',或者保留它(只需输入字段的名称即可)。

为什么不翻转问题并创建覆盖缓冲区的类来拉出位?

// 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归因
不隶属于 StackOverflow
scroll top