2012年12月14日金曜日

C# : ネットワークバイトオーダのバイト列からushort型の値を取り出す

udpのパケットなど、ネットに流れているデータはネットワークバイトオーダ(ビックエンディアン)になっています。 それに対してx86系のシステムはリトルエンディアンです。 相互にやり取りするにはビックエンディアン⇔リトルエンディアンの変換が必要になります。

ネットワークバイトオーダ → クライアントマシンのバイトオーダの変換にはBitConverter.ToInt16などとIPAddress.NetworkToHostOrderを組み合わせるようです。 (BitConverterの代わりにMemoryStream+BinaryReaderを使うやり方もあり。) まず、エンディアンがあっていようがいまいがBitConverterで得たい型の変数に読み込み、その後NetworkToHostOrderで変換という流れです。

この変換をシフト演算を使った自前のコードで書いているのをたまに見ますが、それは環境依存になります。 NetworkToHostOrderを使ったら環境依存になりません。 (データとシステムのエンディアンが合っていたらNetworkToHostOrderは何もしない。 違っていたらNetworkToHostOrderは変換してくれる。) 性能的な問題が無いのならNetworkToHostOrderに任せた方が良いでしょう。

ちなみに、逆の変換は元の型の変数でIPAddress.HostToNetworkOrderしてからBitConverter.GetBytesをします。

ネットワークバイトオーダ → クライアントマシンのバイトオーダの変換コードだけ書いておきましょう。 byte配列からshortを読み込むには。

byte[] testBytes = new byte[] { (byte)0x80, (byte)0 };
short value1 = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(testBytes, 0));

ushortなど、符号なしの場合はキャストが必要になります。

ushort value2 = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(testBytes, 0));

ushort型のデータを読み込んでint型の変数に格納する場合など、変数のビット数が増えるときは注意が必要です。 このコードを実行すると。

int value3 = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(testBytes, 0));
int value4 = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(testBytes, 0));

値は次のようになります。

value3 : -32768 ... 11111111 11111111 10000000 00000000b
value4 : 32768  ... 00000000 00000000 10000000 00000000b

NetworkToHostOrder(short)の戻り値はshort型なので、明示的にushortにキャストしないと数字が変わってしまいます。 .netが動くシステムは負の数の表現に2の補数を使っているので、

  • short型で10000000 00000000b → -32768
  • ushort型で10000000 00000000b → 32768

となります。 value3のキャストの様子はそのまま、

  • [short型で-32768] >>int型に暗黙キャスト>> [int型で-32768]

value4の場合は明示的なキャストと暗黙キャストが組み合わさって、

  • [short型で-32768] >>ushort型に明示キャスト>> [ushort型で32768] >>int型に暗黙キャスト>> [int型で32768]

となります。