• 締切済み

ソケット通信時のエンディアン変換について

現在、WindowsとLinux(Unix)でソケット通信を行い、データをやり取りするプログラムを作成しています。 ソースコードやコンパイルの環境は、 Windows側(Windows7):C言語(Windowsプログラミング)、VisualStudio2013でビルド&実行 Linux側:C++、g++(Cygwinを使用) 送信したいデータは、 Windows→Linuxはfloat型の配列に保持しているデータ Linux→Windowsはconst string型のデータ です。 (1)例として、送りたいfloatのデータが float a[3]; a[0] = 1.1; a[1]=2.2; a[2]=3.3 であるとします。(実際には負の値も考えられます) floatは4バイトなので、各要素でエンディアンを変えてa[0]からa[3]のデータを一括してLinux側にsendしたいと考えているのですが、どのように実装すればよいかが分かりません。 for(int i=0; i<3; i++){ //htonl(*(long*)&a[i]);でエンディアンを変換 //変換したものを何かしらの変数に保存 } //保存しておいたものをsend という大まかな流れだけは考えているのですが、実際どう実装していけばいいのか分からず困っています。 (2)(1)のデータを受信した側で元のfloatのデータに直す方法 (3)Linux→Windowsではstring型のデータを送りたいのですが、c_strを用いてchar型に変換したものをそのままsendしてよいのでしょうか? (char型は1バイトなのでエンディアンを変換する操作は必要はないでしょうか?) もし分かることがありましたら、教えていただけると助かります。 よろしくお願いします。

みんなの回答

  • ques9900
  • ベストアンサー率34% (47/136)
回答No.6

CPUによってサイズが変わるのは困るので、 uint32_tなどのサイズが指定されている型を使いましょう。 それとやはりunionの方が筋がいい気がします。 キャスト使えないので別の変数を用意してコピーする事になると、 メモリも処理コストも無駄になりますし、コードも複雑になっていまいます。 後余談ですが http://linuxjm.osdn.jp/html/LDP_man-pages/man3/endian.3.html 64のバイトオーダの関数なんてあるんですね。びっくり。

  • ques9900
  • ベストアンサー率34% (47/136)
回答No.5

Linux側がlong 8byteということは、そちらが64bit機なんですね。 精度が下がるのはキャストした時だと思われます。 しかたない事ではない気がします。 構造体でlongとしていて送信側と受信側で4と8で変わるみたいだと思います。 バウンダリが発生しているかもしれません。 http://www.itmedia.co.jp/enterprise/articles/0506/14/news003_3.html 簡単にいうとアライメントというメモリを何byteで区切るかというのが CPUによって決まります。 区切られる値より小さい変数を構造体にすると、余った分はパディングとして 確保されます。 構造体に4byteのメンバ変数しかないのにアライメントが8byteなので、 構造体をsizeofすると8byteになるとかいう現象はバウンダリといいます。 値自体は変化しないので、きちんと合わせれば復元できると思います。

  • ques9900
  • ベストアンサー率34% (47/136)
回答No.4

http://ideone.com/rb6xUJ いろいろ試しましたが、 floatをunsigned longにキャストするだけで丸め誤差発生して 1.1から1.0にされてしまうので一旦ポインタにしてから*してるみたいです。 float嫌い(汗) 戻す時は関数の戻り値に&つける方法が思いつかなかったので、 変数を間に挟む事にしました。 変換用にunsigned longを同じ数用意するの一時的とはいえメモリ無駄ですね(涙) 送信バッファも含めるとコピー多すぎて悲しい。 ではでは

113sigma
質問者

お礼

回答ありがとうございます。 教えていただいた方法で実際に送受信テストを行ってみたところ、元のfloatのデータに復元することができました。 ただ、送信側(Windows)のプログラムではsizeof(long)が4byteであるのに対し、受け取り側(Linux)ではsizeof(long)が8byteであり、unsigned longで受け取ってしまうと正しく復元できなくなってしまうことが発覚しました。 そのため、送られてきたデータをunsigned intで受信することにしたのですが、表現できる範囲の関係のせいか、floatに復元しても元のfloatのデータより精度が下がってしまっています・・・。 多分仕方のないことなので、これでよしとしようと思います。 何度も回答いただき、本当にありがとうございました。

  • ques9900
  • ベストアンサー率34% (47/136)
回答No.3

お風呂あがりに取り急ぎ。 http://ideone.com/zeXkoO unionだとうまく行くの釈然としない。 キャストでも同じなのにと思いつつ、ちょっと疲れておりまして、 本日はこれで眠ります。 ご指摘の通り、最初のfloatにキャストしてhtonlしているのは完全に間違っています。 念の為の記述ですが、sendする時はhtonlで recvはntohlを使ってください。 一見swapしてるだけなので、どちらも一緒な感じですが 送信側のCPUのアーキテクチャと 受信側のCPUのアーキテクチャで エンディアンが違うと期待通りにならないはずです。 元々ビッグエンディアンの場合は無変換とかするはずなのでうろ覚え(汗)

113sigma
質問者

お礼

回答ありがとうございます。 union(共用体)を使うという方法もあるのですね。 載せていただいたプログラムを実行してみたところ、同じ結果になることが確認できました。 ただ、あまり共用体を使いたくないので、longでキャストする方法で上手くいかないかなと考えており、色々試行錯誤しています。 もしまた何か分かりましたら教えていただけると助かります。 >念の為の記述ですが、sendする時はhtonlで recvはntohlを使ってください。 ありがとうございます、こちらについては大丈夫です。

  • ques9900
  • ベストアンサー率34% (47/136)
回答No.2

ボケてました。 見事に丸め誤差っぽい何かになりました。 http://ideone.com/4eMiUN 何か解ったらまた書きますね。 今ちょっと時間ないです(汗)

113sigma
質問者

お礼

回答ありがとうございます。 いつも助けていただいて、申し訳ないです。 お暇な時で構いませんので、また教えていただけると助かります。

  • ques9900
  • ベストアンサー率34% (47/136)
回答No.1

(1) については大まかな流れで問題ないですよ。 htonlで変換したら、それをsendすればよいかと。 http://ideone.com/9hulsu (2) についてはntohlで戻すだけです。 (3)についても1バイトなので変換不要で認識あってますよ 理解していると思いますけど念の為、 http://linuxjm.osdn.jp/html/LDP_man-pages/man3/byteorder.3.html uint32_t htonl(uint32_t hostlong); uint16_t htons(uint16_t hostshort); uint32_t ntohl(uint32_t netlong); uint16_t ntohs(uint16_t netshort); hがホストバイトオーダー nがネットワークバイトオーダー lがlong sがshort リトルエンディアンとかビッグエンディアンとか考えずに、 送信時はhtonしましょうって覚えればいいと思います。

113sigma
質問者

お礼

回答ありがとうございます。 (1)の回答で載せていただいたプログラムを動かしたところ、floatデータのエンディアンを変換した結果が、 00 00 00 00 00 00 00 00 00 00 00 00 というように、全て0で表示されてしまいます。 (PCが64bitだからでしょうか?) また、載せていただいたプログラムについて少し質問があります。 htonl()の場合、引数と戻り値は共にlong型だと思うのですが(uint32_t=longという解釈が間違っているかもしれません) floatデータをhtonlしている部分(プログラムの53行目)では、引数をfloat*にキャストしており、また、戻り値もfloat型の配列に入れてしまっていると思うのですが、この処理で大丈夫なのでしょうか? (多分私のポインタやデータ型についての知識不足のためこのような疑問が生じているのだと思います) もしまた何かわかりましたら教えていただけると幸いです。 よろしくお願いします。