2010-04-16 61 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