• 締切済み

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

現在、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型の配列に入れてしまっていると思うのですが、この処理で大丈夫なのでしょうか? (多分私のポインタやデータ型についての知識不足のためこのような疑問が生じているのだと思います) もしまた何かわかりましたら教えていただけると幸いです。 よろしくお願いします。

関連するQ&A

  • エンディアン変換を高速に行うには?

    こんばんわ, 現在Redhat Linux 9の環境でCの勉強をしています。 ファイルから読み取ったデータがBIGENDIANの形なので,それをLITTILE ENDIANになおして使用したいのですが,できるだけ高速に変換するアルゴリズムを考えています。 変換するデータは1byte(これは関係なし),2byte,4byte,でそれぞれunsignedのshortintとint型です。 とにかくべた書きでもいいのですが,どのようにすれば高速に変換できるのでしょうか。 glib.hやhtonlなどを使用する方法などよろしくお願いします。

  • 最近のCPUのほとんどはリトルエンディアンかビッグエンディアンでしょうか?

    2000年あたり以降に出た Windows, Mac, Linuxに使われているCPUのほとんどはリトルエンディアンかビッグエンディアンでしょうか? また、たとえば以下のような方法でエンディアンを調べられると考えていいのでしょうか?(VC++です) #include <windows.h> void GetEndian4(char* c){ unsigned __int32 a=0x03020100; BYTE *b = (BYTE*)&a, i=4; while (i--) c[i]=b[i]; } ////////// const char e[4]={}; GetEndian4( const_cast<char*>(e) ); //eが 0,1,2,3 になればリトルエンディアン //3,2,1,0 になればビッグエンディアン //PDP-エンディアンだと 2,3,0,1 …のはず (または2択ならこれだけでも判断可能…?) short s=1; printf( *(char*)&s ? "リトルエディアン\n" : "ビッグエディアン\n" ); あとここでもアラインメントの問題が絡みますが、このように アラインメントが(2のべき乗だとして)大きいであろう方から小さいであろう方にキャストする分には安全で、逆に sizeof(short) == sizeof(char)*2 として char c[2]={1,0}; short s=*(short*)&c; というのは危険な場合がある、ということでしょうか? また、その場合は たとえばビッグエンディアンなら short s=(c[0]<<8)|c[1]; とすればいいでしょうか?

  • VC String型のエンディアン変換

    お世話になります String型の文字列のそれぞれの文字(一文字ずつ)の エンディアンを変更したいのですが、簡単な方法はないでしょうか? 今考えている方法は Stringから一文字ずつcharに入れ込んで このcharをシフトとマスクで入れ替える方法なのですが ((val<<8) & 0xff00) | ((val>>8) & 0x00ff) エンディアン変換の前後でString⇒charとchar⇒Stringを行うので、 少し煩雑になるのが気になっております。 型変換無しでエンディアン変換できないでしょうか

  • リトルエンディアン、ビッグエンディアンについて

    リトルエンディアン、ビッグエンディアンについて 質問があります。 (1)簡単にそのPCがリトルエンディアンなのか、ビッグエンディアン  なのか、分かる方法はありますか?  簡単なCプログラムを書いてメモリ状態をダンプするのが、  一番早いのでしょうか?  それとも、Intel系?モントローラ系?CPUにはあまり詳しくないので、  分かりませんが、これらのどちらかに属していれば決められるので  しょうか?ほかの系とかあるのかな・・・ (2)ネットワークプログラミングをするときに、ビッグエンディアンの  マシンからデータを送出する場合には、htonlなどの関数を使用しなく  ても問題ありませんでしょうか?(ネットワークバイトオーダが  ビックエンディアンであるため)

  • リトルエンディアン→ビッグエンディアン

    (1)リトルエンディアン typedef struct recvData{  int a;  unsigned char b[16]; unsigned char c[8]; unsigned int d[4]; } recvData_t; recvData_t rData; (2)ビッグエンディアン typedef struct sendData{  int a;  unsigned int b[4]; unsigned int c[2]; unsigned int d[4]; } sendData_t; sendData_t sData; 上記のようなリトルエンディアンの構造体の各メンバのデータを、ビッグエンディアンの構造体の各メンバのデータにそれぞれ格納するには どうしたらよいでしょうか?

  • エンディアンについて

    すみません。 教えてください。 リトルエンディアンからビッグエンディアンに変換しないと いけません。 エンディアンについては勉強したつもりですが、 どうしてもわからないことがあります。 ご存知の方、教えていただせんか? CPUはリトルです。 まず、エンディアンの違いについては 以下のように認識しています。 x = 0xAABBCCDD メモリの配置方法が、 トリルだと DD CC BB AA ビックだと AA BB CC DD だと思っています。32ビットの場合です。 で、これを変換するには、htonlで変換可能だと思っっています。 (試したところ可能でした) で次に、32ビットを超えるデータ、たとえば100バイトとかを mallocにして変数に代入しました。 この時はエンディアン変換(ファイルに出力する際)は必要ないのでしょうか? 試しに出力すると、 x = 0x AA BB CC DD EE FF GG ・・・・・・ZZ (100バイトと仮定) バイナリでの出力結果は AA BB CC DD EE FF GG ・・・・・となっていました。 私の認識だと、本CPUはリトルエンディアンのため、 ZZ ・・・・・・・・ DD CC BB AA (四バイトずつ反転しているデータ) が出力されるものと思っていました。(反転してメモリに格納されるため) リトル/ビックを意識しないといけないのは、 2バイトや4バイトの時のみで、それを超える大きなデータ(100バイト)などは 意識せず、そのままバイナリ出力しても、ビックエンディアンで出力されると いうことでよろしいでしょうか? そうなると、エンディアンってなんだんだ???と混乱しています。 わかりにくい説明で大変申し訳ござませんが、 よろしくお願いいたします。

  • Endianについて

    Endianというキーワードでいろいろ探してみたのですが、どこをみても「2バイト以上のデータ量を持つ数値データを記録したり転送するときには1バイトごとに分割する」と書いてあります、2バイト以上というのは、たとえば、int型(int a=777)のような変数ですよね!? これを別のPCに転送する際に1バイトずつしかデータが転送されないというのが疑問でなりません。 ネットでは、パケットごとにデータを転送しているのではないでしょうか?

  • ソケット通信

    たびたびお世話になります。 CreateProcessで「cmd /C ***.bat」、 ***.batの中では vbsを動かして、telnet xxx.xxx.xxx.xxx を起動しています。 ウィンドウを表示させないように言われていますが、どうしてもtelnetが動くと、 ウィンドウが表示されてしまいます。 batのなかで、cscriptで*.vbsを読んでるからと思います。 (cscriptは画面を消せない???) なので、ソケット通信に変更してみました。 まったくウィンドウが出ないので「良し良し!」だったのですが 動かない箇所が出てきました。 例えば、ボタンが複数あって ボタンごとに「set 0」「set 1」...「set 77」 などとsend()するのですが、「set 10」以降、set の次の数字が2桁になると動かなくなってしまいました。 特にclient側ではエラーにはならなくて、サーバー側・受けとったほうで解釈できないものと思います。 サーバー側での解析はできません(ROMに焼き付けてある箱(!)なので) これって、そのままsend()してはダメでしょうか? バイトオーダーの変換???とか関係ありますか? 関係あったとしても、バイトオーダーの変換のやり方がわかりません。(;_;) (いろいろ試したけれどわかりませんでした) telnetで送るデータ、そのままなんですが、当然telnetでは ちゃんと動いています。 すみません、どんなことでも良いのでアドバイスお願いいたします。

  • vb6 バイトオーダエンディアン変換の高速化

    お世話になります。 VB6にて下記ソースの様にバイトオーダのエンディアン変換を行っているのですが、 一旦Stringに入れ込んでいるせいなのか、 非常に処理が遅く困っております。 2Btyteのデータの入れ替えだけなのですが、 高速化もしくは単純化する方法がございましたら教えていただけないでしょうか Public Function Swap(ByVal L As Long) As Integer Dim S As String S = Right(String(4, "0") & Hex(L), 4) Swap = CInt("&H" & Right(S, 2) & Left(S, 2)) End Function

  • ビッグエンディアンをPCで処理する問題

    ビッグエンディアンと指定されているバイナリファイルがあります。 これをPC(intel系,Linux or Windows)で処理する場合、エンディアンの変換が必要だろうと思います。具体的にはどのようにするのでしょうか。ネットでもダウンロードできそうですが。 変換コマンド -btol ファイル名(ビッグ)> ファイル名(リトル) という感じですかね。こんなものあるでしょうか。 購入したバイナリファイルなのですが、このご時勢たいていPCで処理するわけでしょうからリトルであってほしいものだと思いますが。 ところでMACはビッグとリトルのどっちなのでしょうか。