2010-04-16 19 views
2

我正在写一个用C++编写的服务器程序的客户端。不常见的是,所有的网络协议都是这样的一种格式,即数据包可以很容易地写入/退出C++结构(1字节数据包代码,然后每个数据包类型有不同的安排)。对象到网络序列化 - 与现有的协议

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

回答

2

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

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

基本上有三种选择

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

当您考虑选项时,您还应该考虑字节顺序如何影响您。

作为一个例子,我将使用IP头作为一个例子,因为在这篇文章的时候我正在使用Raw 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; 

这样做是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()); 
+0

啊谢谢!我去尝试BinaryReader的方式,并且完全不知道为什么这些数字出错了。看起来似乎没有任何其他管理替代品,我想我会直接从流中读取并填充类。 – cpf 2010-04-17 16:46:41