Сериализация объекта в сеть - с существующим протоколом - PullRequest
2 голосов
/ 16 апреля 2010

Я пишу клиент для серверной программы, написанной на C ++. Как обычно, все сетевые протоколы представлены в формате, в котором пакеты могут быть легко скопированы в / из структуры C ++ (1-байтовый код пакета, затем различные схемы для каждого типа пакета).

Я мог бы сделать то же самое в C #, но есть ли более простой способ, особенно учитывая, что большая часть данных - это массивы символов фиксированной длины, с которыми я хочу играть как строки? Или я должен просто смириться и конвертировать типы по мере необходимости? Я рассмотрел использование интерфейса ISerializable, но он не выглядит так низко, как требуется.

1 Ответ

2 голосов
/ 17 апреля 2010

Я написал сообщение об этом в 2004 году, в котором рассматриваются некоторые варианты, доступные для преобразования двоичного потока в структуру памяти .NET. Я разместил его в своем новом блоге, так как старый сайт блога больше не существует.

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

В основном у вас есть три варианта

  1. Использовать указатели памяти в стиле C ++ в C #, для которых требуется ключ / unsafe
  2. Используйте маршалинг .NET для выделения неуправляемого блока памяти, скопируйте байты в неуправляемую память и затем используйте Marshal.PtrToStructure, чтобы упорядочить данные обратно в управляемую кучу, отображая их в вашей структуре.
  3. Используйте BinaryReader, чтобы вручную прочитать поток байтов и упаковать данные в структуру. Лично это был мой предпочтительный вариант.

При рассмотрении параметров также следует учитывать, как порядок байтов может повлиять на вас.

В качестве примера я буду использовать заголовок IP в качестве примера, поскольку на момент публикации я работал с пакетами Raw 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;

То же самое можно сделать в C # с помощью параметра / unsafe, а указанная выше структура, которую вы считаете, использует следующий код.

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());
...