我正在为用 C++ 编写的服务器程序编写客户端。不寻常的是,所有的网络协议都采用一种格式,数据包可以很容易地被内存复制进/出 C++ 结构(1 字节数据包代码,然后每种数据包类型有不同的排列)。
我可以在 C# 中做同样的事情,但有没有更简单的方法,特别是考虑到很多数据是我想作为字符串使用的固定长度的 char 数组?还是我应该把它吸起来并根据需要转换类型?我已经研究过使用 ISerializable 接口,但它看起来不像所需的那样低级。
我正在为用 C++ 编写的服务器程序编写客户端。不寻常的是,所有的网络协议都采用一种格式,数据包可以很容易地被内存复制进/出 C++ 结构(1 字节数据包代码,然后每种数据包类型有不同的排列)。
我可以在 C# 中做同样的事情,但有没有更简单的方法,特别是考虑到很多数据是我想作为字符串使用的固定长度的 char 数组?还是我应该把它吸起来并根据需要转换类型?我已经研究过使用 ISerializable 接口,但它看起来不像所需的那样低级。
我在 2004 年写了一篇关于此的文章,其中涵盖了一些可用于将二进制流转换为 .NET 内存结构的选项。由于旧的博客网站不再存在,我将其重新发布在我的新博客上。
http://taylorza.blogspot.com/2010/04/archive-structure-from-binary-data.html
基本上你有三个选择
当您考虑这些选项时,您还应该考虑字节顺序可能会如何影响您。
作为示例,我将使用 IP 标头作为示例,因为在发帖时我正在处理原始 TCP 数据包。
您需要定义二进制数据将映射到的 .NET 结构。例如,IP 标头如下所示。
[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());