2

私はC++で書かれたサーバープログラムのクライアントを書いています。珍しいことではありませんが、すべてのネットワークプロトコルは、パケットをC ++構造に簡単にメモリイン/コピーアウトできる形式になっています(1バイトのパケットコード、パケットタイプごとに異なる配置)。

C#でも同じことができますが、特に多くのデータが文字列として使用したい固定長のchar配列であることを考えると、もっと簡単な方法はありますか?それとも、それを吸い上げて、必要に応じてタイプを変換する必要がありますか?ISerializableインターフェイスの使用を検討しましたが、必要なレベルではありません。

4

1 に答える 1

2

私は2004年にこれについて投稿し、バイナリストリームを.NETメモリ構造に変換するために利用できるオプションのいくつかを取り上げました。古いブログサイトがなくなったので、新しいブログに再投稿しました。

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

基本的に3つのオプションがあります

  1. / unsafeスイッチを必要とするC#でC++スタイルのメモリポインタを使用する
  2. .NETマーシャリングを使用してアンマネージメモリブロックを割り当て、バイトをアンマネージメモリにコピーしてから、Marshal.PtrToStructureを使用してデータをマーシャリングしてマネージヒープに戻し、構造にマッピングします。
  3. BinaryReaderを使用して、バイトストリームを手動で読み取り、データを構造体にパックします。個人的には、これが私の好みのオプションでした。

オプションを検討するときは、バイト順序がどのように影響するかについても考慮する必要があります。

例として、IPヘッダーを例として使用します。これは、投稿の時点でRawTCPパケットを使用していたためです。

バイナリデータがマップされる.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属性は、最初の2つのオプションにのみ必要であり、もちろん、サーバーからシリアル化される構造に適切なパッキングを設定する必要があることに注意してください。

したがって、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());
于 2010-04-17T11:33:35.100 に答える