【问题标题】:Object to Network serialization - with an existing protocol对象到网络序列化 - 使用现有协议
【发布时间】:2010-04-16 18:03:04
【问题描述】:

我正在为用 C++ 编写的服务器程序编写客户端。不寻常的是,所有的网络协议都采用一种格式,数据包可以很容易地被内存复制进/出 C++ 结构(1 字节数据包代码,然后每种数据包类型有不同的排列)。

我可以在 C# 中做同样的事情,但有没有更简单的方法,特别是考虑到很多数据是我想作为字符串使用的固定长度的 char 数组?还是我应该把它吸起来并根据需要转换类型?我已经研究过使用 ISerializable 接口,但它看起来不像所需的那样低级。

【问题讨论】:

    标签: c# networking serialization binary-data


    【解决方案1】:

    我在 2004 年写了一篇关于此的文章,其中涵盖了一些可用于将二进制流转换为 .NET 内存结构的选项。由于旧博客站点已不存在,因此我将其重新发布在我的新博客上。

    http://taylorza.blogspot.com/2010/04/archive-structure-from-binary-data.html

    基本上你有三个选择

    1. 在需要 /unsafe 开关的 C# 中使用 C++ 样式的内存指针
    2. 使用 .NET 封送分配非托管内存块,将字节复制到非托管内存,然后使用 Marshal.PtrToStructure 将数据封送回托管堆,将其映射到您的结构中。
    3. 使用 BinaryReader 手动读取字节流并将数据打包到结构中。就个人而言,这是我的首选。

    在考虑选项时,您还应该考虑字节顺序可能对您产生的影响。

    作为示例,我将使用 IP 标头作为示例,因为在发帖时我正在处理原始 TCP 数据包。

    您需要定义二进制数据将映射到的 .NET 结构。例如,IP Header 如下所示。

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    struct IpHeader
    {  
      public byte VerLen;  
      public byte TOS;  
      public short TotalLength;   
      public short ID;  
      public short Offset;  
      public byte TTL;  
      public byte Protocol;  
      public short Checksum;  
      public int SrcAddr;  
      public int DestAddr;
    }
    

    请注意,仅前两个选项需要 StructLayout 属性,当然您需要根据从服务器序列化的结构设置包装。

    所以在 C/C++ 中,给定一个指向内存块的指针,该内存块包含映射到 C/C++ 结构的数据字节,您可以使用以下代码将数据块视为内存结构块,其中数据包是内存中的一个字节*。

    IpHeader *pHeader = (IpHeader*)packet;
    

    使用 /unsafe 选项的 C# 也是如此,上面定义的结构使用以下代码。

    IpHeader iphdr;
    unsafe
    {  
      fixed ( byte *pData = packet)  
      {    
        iphdr = *(IpHeader*)pData;  
      }
    }
    //Use iphdr...
    

    编组选项如下所示

    IntPtr pIP = Marshal.AllocHGlobal( len );
    Marshal.Copy( packet, 0, pIP, len );
    iphdr = (IpHeader)Marshal.PtrToStructure( pIP, typeof(IpHeader) );
    Marshal.FreeHGlobal( pIP );
    

    最后,您可以使用 BinaryReader 完全在托管代码中执行此操作。

    MemoryStream stm = new MemoryStream( packet, 0, len );
    BinaryReader rdr = new BinaryReader( stm );
    
    iphdr.VerLen = rdr.ReadByte();
    iphdr.TOS = rdr.ReadByte();
    iphdr.TotalLength = rdr.ReadInt16();
    iphdr.ID = rdr.ReadInt16();
    iphdr.Offset = rdr.ReadInt16();
    iphdr.TTL = rdr.ReadByte();
    iphdr.Protocol = rdr.ReadByte();
    iphdr.Checksum = rdr.ReadInt16();
    iphdr.SrcAddr = rdr.ReadInt32();
    iphdr.DestAddr = rdr.ReadInt32();
    

    正如我之前提到的,您可能需要考虑字节顺序。例如,上面的代码并不完全正确,因为 IpHeader 没有使用与 ReadInt16 假定的相同的字节顺序。 ReadInt32 等。使用上述解决方案解决问题就像使用 IPAddress.NetworkToHostOrder 一样简单。

    iphdr.VerLen = rdr.ReadByte();
    iphdr.TOS = rdr.ReadByte();
    iphdr.TotalLength = IPAddress.NetworkToHostOrder(rdr.ReadInt16());
    iphdr.ID = IPAddress.NetworkToHostOrder(rdr.ReadInt16());
    iphdr.Offset = IPAddress.NetworkToHostOrder(rdr.ReadInt16());
    iphdr.TTL = rdr.ReadByte();
    iphdr.Protocol = rdr.ReadByte();
    iphdr.Checksum = IPAddress.NetworkToHostOrder(rdr.ReadInt16());
    iphdr.SrcAddr = IPAddress.NetworkToHostOrder(rdr.ReadInt32());
    iphdr.DestAddr = IPAddress.NetworkToHostOrder(rdr.ReadInt32());
    

    【讨论】:

    • 啊,谢谢!我去尝试了 BinaryReader 的方式,对为什么数字出现错误感到完全困惑。鉴于似乎没有任何其他托管替代方案,我想我将直接从流中读取并填充类。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-09-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-24
    相关资源
    最近更新 更多