構造体の送受信方法

[戻る]
クリスタル 2005/04/23(土) 12:55:59
sendでデータの送信をする時に
第2引数に構造体を入れて送信したいと思っています。

下記の様な指定の仕方ですと、エラーは起きないのですが・・・
char buf[256] = { 0 };
send(clsock,buf,256,0);

// 構造体を第2引数に指定してみると。。。
typedef struct{
int a;
int b;
}sample;

sample A;
send(clsock,&A,sizeof(sample),0);

構造体を入れてコンパイルをすると
'send':2番目の引数を'sample *'から'const char*'に変換できません。
とエラーが出てしまいます。

この場合どう対処したらよいのでしょうか?
ご教授お願い致します。

開発環境:
VC++.netを使用しています。

Ban 2005/04/23(土) 14:46:36
送るだけなら &A を reinterpret_cast<char*>(&A) にする。

但し、通信相手がまったく同じ環境でもない限り、
バイナリデータをそのまま send しても正しく recv できる保証はありません。

構造体のメモリ配置はコンパイラにより異なる可能性がありますし、
CPUのバイトオーダ等が異なることもありえます。

Ban 2005/04/23(土) 14:49:47
追記:
sample の例だと、そもそもint のサイズが同じとは限らないというのもあります。

reshia 2005/04/23(土) 20:52:33
バイトオーダーの件は、MPIを利用するといいかも

クリスタル 2005/04/23(土) 21:53:31
Banさん、reshiaさん
レスありがとうございます。

Banさんに教わりました方法で早速試してみました。
typedef struct{
  char buf[256];
}sample;

sample A;
send(clsock,reinterpret_cast<char*>(&A),sizeof(sample),0);        // 送信
recv((SOCKET)wParam,reinterpret_cast<char*>(&A),sizeof(sample),0);// 受信

上記の送受信方法で、受信後に構造体のメンバを表示すると
『フフフフフフフフフフフフフフフフフフフフ』と表示された後に、送信した文字が表示されます。

これは、バイトオーダというのが原因なのでしょうか?
また、MPIというのは使用した事がないのですが、難しいものなのでしょうか?

Ban 2005/04/23(土) 22:12:17
バイトオーダのせいではありません。
(char では通常、バイトオーダの問題"は"発生しません)

VC のデバッグ版モジュールをブレイクしながらし見ているのであれば、
「フフフ」というのは未初期化のデータを示します。
(まだ値が設定されていない状態の時、VCはデバッグしやすいようにフフフを入れます)

よって、受信用の領域を初期化していない状態で recv を行った場合、
受信前の時点では「フフフ」になっていて、受信がすんだらその文字列が入る、
という挙動であれば特におかしくないと思います。こういう挙動ですか?

# ちなみに、フフフが入るのは、VC のデバッグ版のときだけです。
# C/C++ としては、その値は「不定(何になるか分からない)」です。
# 毎回値が違うと確認しにくかったりするのでデバッグ用に同じ値にしてくれるのです。

クリスタル 2005/04/24(日) 02:50:54
まだ試していませんが受信前に表示しても、フフフと出ると思います(後ほど試してみま
す)。
受信した文字はダイアログボックスに貼り付けたエディット ボックスに表示していま
す。

受信前に構造体を初期化してみたのですが
メッセージ受信時にエラーが出てしまい、強制終了されてしまいます。
memcpy( &A , 0 , sizeof(sample)); // 初期化処理

構造体を初期化する場合、他の手段があるのでしょうか?

Ban 2005/04/24(日) 04:37:33
> まだ試していませんが受信前に表示しても、フフフと出ると思います
(後ほど試してみます)。

ん?
コードを見てないのでなんともいえませんが、想定通りなら私も多分出ると思いますよ。
> 受信前の時点では「フフフ」になっていて、受信がすんだらその文字列が入る
先に書いたとおり。
# で、もしもこのフフフが、例えばエディットコントロール等に出てたとしたら
# それは「未初期化変数を参照してる」バグを持ったコードです。
# 警告レベルによっては検出できるかも。


> 上記の送受信方法で、受信後に構造体のメンバを表示すると
> 『フフフフフフフフフフフフフフフフフフフフ』と表示された後に、
> 送信した文字が表示されます。

この観測方法を明確にしてもらわないと、あくまで推測の域を出ませんが。

フフフと表示されるのはどのタイミングなのか。
「後に」とは何をした後なのか。何がきっかけで文字列が表示されるのか。
# 非同期実行してて受信したタイミングで切り替わるとか、
# ステップ実行でF10おしたら切り替わるとか、........

そもそも、上記のsend と recv をどのタイミングで書いているのか。
どうやってその結果を確認しているのか。何かに出力したりとかしてます?
よく見ると recv の引数がウィンドウプロシージャの引数っぽいですが、
send と recv の呼び出し関係はどうなってます?

私には両者が同一プロセス内なのか、違うのかすら分からないわけですが、
こういう部分に関する情報をもっと示していただけると、
もう少し踏み込んだ回答や説明ができるかもしれません。


> 構造体を初期化する場合、他の手段があるのでしょうか?

例えばコンストラクタを書くとか、まぁいろいろあると思いますが、
とりあえずその memcpy を memset に直せば多分通ると思います。
そのままだと 0メモリ参照で落ちるはず。

Ban 2005/04/24(日) 04:47:51
> この観測方法を明確にしてもらわないと、あくまで推測の域を出ませんが。

おぉっと、失礼。

> 受信した文字はダイアログボックスに貼り付けたエディット ボックスに
> 表示しています。

見落としてました........orz


というわけで、フフフが表示されてしまうコードはバグ持ちです。

リリース版だとどんな値が入るか分からないので、最悪アプリが突然落ちたりする
危険があります。

対策は適切な初期化。memset に直せばとりあえず動く筈です。
ただ、その char の配列が文字列として使われるなら必ずしも全体を 0 初期化する
必要はないので、こんなのでも可。

typedef struct sample_struct
{
  char buf[256];

  sample_struct(){ buf[0] = '\0'; }
}sample;

受信文字列長が変わるときは末尾に文字列終端'\0'がつくように注意して下さい。

REE 2005/04/25(月) 10:18:49
今回のケースは単に受信できていないだけと思われます。

クリスタル 2005/04/25(月) 23:38:49
REEさんレスありがとうございます。
Banさんお返事が遅くなり申し訳ございません。

> まだ試していませんが受信前に表示しても、フフフと出ると思います

コードを書き換えるのに時間が掛かるため、先に初期化のほうを試して見ました。。


> よく見ると recv の引数がウィンドウプロシージャの引数っぽいですが、
> send と recv の呼び出し関係はどうなってます?

簡単な通信ゲームを作成しようと思っており、現在はチャットの部分を作成していま
す。
受信時は、プロシージャ内で『case FD_READ:』でキューに値が入っていれば、recvを呼
ぶようにしています。
sendは現在は、『Enter』を押されたときに呼ぶようにしています。
今後は毎回呼ばれるようにコードを書き換える予定です。
送受信は違うプロセスで稼働しています。

Banさんの仰った通り初期化を行いました所、何事も無く文字が表示されました。
ただ、構造体の中にint型のメンバを付け足すと文字の受信が行えなくなりました。

typedef struct sample_struct
{
    int  a;
  char buf[256];

  sample_struct(){ buf[0] = '\0'; }
}sample;

これがバイトオーダと言うものなのでしょうか?

Ban 2005/04/26(火) 01:50:20
> これがバイトオーダと言うものなのでしょうか?

いいえ、まったく別の問題と思われます。

「バイトオーダ」については調べてみましたか。参考までに綴りは「byte order」です。
例えば Intel の CPU ならリトルエンディアンと呼ばれるオーダ(順番)です。


> ただ、構造体の中にint型のメンバを付け足すと文字の受信が行えなくなりました。

バイトオーダの問題で普通こんなことは起きません。
前述のように、文字列を格納している char の配列には直接影響しないのです。

おそらくそれ以前の問題でどこか間違っているものと思いますが、
どういう挙動になったのか、どんなコードを書いたのかさえ分かりませんし、
提示の情報のみではそれ以上はなんともいえません。
C言語の型や構造体のメモリ配置について理解は十分ですか?


# デバッガを使いこなせばコードを修正しなくてもフフフは表示できたような。

クリスタル 2005/04/29(金) 02:28:06
> C言語の型や構造体のメモリ配置について理解は十分ですか?

例えばこの用な構造体があった場合

struct sample {
    int    a;
    char    b[10];
    int    c;
};

上からメモリ上で、
2バイト
10バイト
2バイト
と割り振られるのですよね。

> どういう挙動になったのか、どんなコードを書いたのかさえ分かりませんし、
> 帝Dの情報のみではそれ以上はなんともいえません。

申し訳ございません。
どの程度までコードをここに載せれば良いのか迷っていたので。

構造体にメンバを増やしても何とか送受信、文字表ヲが出来るようになりました。
表ヲされなかった原因は、stringクラスに構造体を丸ごと代入していたのが悪かったみ
たいです。

> サーバー、クライアント間で通信している構造体です。
typedef struct sample_struct{
    int    a;
         char    f[256];    
    sample_struct()
         {
       f[0]    = '\0';
       a    = 0;
    }
}sample;

   sample A;

> std::string Send(reinterpret_cast<char*>(&A), rBytes);  // ×
> Sentence_Dialog(Send.c_str());                          // 送信フェーズ

上記のですと表ヲuDッ敗しますが
下記の方法ですと無事に表ヲされます。

> std::string Send( A.f, sizeof(A.f) );                   // ○
> Sentence_Dialog(Send.c_str());                          // 送信フェーズ


受信したint型をchar型と同じように表ヲさせると
『』となってしまうのが難点ですが。

とおりすがり 2005/04/29(金) 07:36:33
> struct sample {
>     int    a;
>     char    b[10];
>     int    c;
> };
>
> 上からメモリ上で、
> 2バイト
> 10バイト
> 2バイト
> と割り振られるのですよね。

そうとは限りません。
コンパイラ等の環境にも依存しますし、フィールドとフィールドの間にパディングという
埋め草領域ができる場合もあります。(まあ、今回はなさそうですが)
intが2バイトという保証もありません。sizeof(int)で確認して下さい。

シャノン 2005/04/29(金) 14:08:25
> コンパイラ等の環境にも依存しますし、フィールドとフィールドの間にパディングと
いう
> 埋め草領域ができる場合もあります。(まあ、今回はなさそうですが)

ありませんか?
コンパイルオプションにもよりますが、sizeof( int ) = 4 でアラインメント 4 と仮定
した場合、
上から順に 4 バイト、10 バイト、パディング 2 バイト、4 バイト のような。
VC++ 7.1 のデバッグモード(コンパイルオプションはデフォルトのまま)でやったら、
そうなりました。

YuO [E-Mail] 2005/04/29(金) 15:44:39
> std::string Send(reinterpret_cast<char*>(&A), rBytes);  // ×

int *を強引にchar *に変換したところで,
それを出力してもintの値が出力されるわけではない事はわかっているのでしょうか。

クリスタル 2005/04/29(金) 18:33:31
レスありがとうございます。

> int *を強引にchar *に変換したところで,
> それを出力してもintの値が出力されるわけではない事はわかっているのでしょうか。

確かに・・・送信時に型キャストでcharに直して、受信時にそのままの型で受け取って
いて
int型に戻すという作業は行っていませんでした。
そこまで気が付きませんでした。

この場合、受信後にint型をatoi的な事をすれば良いのでしょうか?
int a = atoi( A.a );   // これだとエラーになってしまうので・・・

char型になってしまった構造体を元に戻す?
をしてくれるクラスみたいのはあるのでしょうか?

Ban 2005/04/29(金) 19:48:47
> この場合、受信後にint型をatoi的な事をすれば良いのでしょうか?

int を数値(バイナリ)ではなく、数字(テキスト)に変換して送ったのであれば
それでいいでしょう。
但し、同構造体内の文字列(?)と続けて送ると区切りが分からないとか、
そういう問題がおきないようには考慮してください。

また、バイナリのままキャストして送っていれば受信時にも再キャストすることに
なると思いますが、(ここで初めて)バイトオーダなどを意識する必要が出ます。


> char型になってしまった構造体を元に戻す?
> をしてくれるクラスみたいのはあるのでしょうか?

そんなクラスはありません。

単純なキャストで済めばそれまでですし、
それ以上のシリアライズ/デシリアライズを行うには(どこぞの規格にでも従わない限り)
構造体を定義した人(あなた)の判断が必要です。


ちなみに、テキストにシリアライズして送らないのであれば、ただのバイト列に
std::string を使うのは不適切です。(バイナリ内に '\0' が含まれるときなど…)
せめて std::vector<char> なりにした方がいいです。

シリアライズするなら std::stringstream なり使って << や >> など実装するのも
いいと思います。

Ban 2005/04/29(金) 20:06:47
補足

> せめて std::vector<char> なりにした方がいいです。

薦めているわけではありません。そもそも、キャストなんて必要悪なものですし、
まして reinterpret_cast は C++ のキャストの中でも特にお勧めできません。
# コンパイラに無理やり型を押しつけるのでプログラマの責任が増えます。
send / write のようなきわめて低水準の関数に直接渡すときくらいしか
使うべきではないと思ってます。

# std::string についても、最初の質問にはなかったと思いますが、なぜ突然.......?
# テキストに変換して送りたかったのでしょうか.......。

クリスタル 2005/04/30(土) 01:13:34
> テキストに変換して送りたかったのでしょうか.......。

すみません。こちらの記述ミスでした。
送信時は、stringの文字列を
構造体のメンバにコピーした後にsendで送っています。

typedef struct sample_struct{
         int     a;
         char    f[256];    
         sample_struct()
         {
            f[0]    = '\0';
            a       = 0;
         }
}sample;

void CLIENT_SEND(string Send){

   sample A;

   lstrcpy( A.f , Send.c_str() );
   send(Sock,reinterpret_cast<char*>(&A),sizeof(sample),0);

}

> そもそも、キャストなんて必要悪なものですし、
> まして reinterpret_cast は C++ のキャストの中でも特にお勧めできません。

ですが、ソケット通信のsendではreinterpret_cast以外には方法が無く
キャストしないと送れないのではないでしょうか?

int(バイナリ)もchar(テキスト)にキャストしないと
『2番目の引数を'int'から'char*'に変換できません。』
とエラーがでてしまいますし。

Ban 2005/04/30(土) 03:16:57
> ですが、ソケット通信のsendではreinterpret_cast以外には方法が無く
> キャストしないと送れないのではないでしょうか?

言葉が足りませんでした。send は read/write なみの低水準関数という認識です。
そして string は高水準という認識です。

>  send(Sock,reinterpret_cast<char*>(&A),sizeof(sample),0);

送受信が同じコンパイラの同じオプションで作られて、同じ環境で動かす前提であれば、
受信も同様にキャストするだけだと思います。
recv(Sock, reinterpret_cast<char*>(&B), ...

環境が違う場合、int のサイズが違ったり、バイトオーダが違ったり、
構造体のアライメントが違ったり、文字コードが違ったり、......
などを送受信時に全て考慮する必要があります。
サイズなどはあらかじめ一定のルールを決める、オーダは htonl などの関数を使う、等

> int(バイナリ)もchar(テキスト)にキャストしないと

もしかして誤解があるような。
例えば int の 2 を char に「キャスト」しても 1byte のバイナリ値 2 になるだけで
文字の '2' にはなりませんし、int を char に static_cast しても、
int* を char* に reinterpret_cast しても、基本的にはビットパターンベースの
変換がキャストですから、いずれもバイナリのままです。

型としては同じ char ですが、テキストの文字とバイトサイズのバイナリデータ、
文字列とバイト列は扱い上別物ですし、send/recv や read/write が扱う
char は単にバイト列であって文字列を意味しません。

> int a = atoi( A.a );   // これだとエラーになってしまうので・・・

例えば atoi が要求するのは(バイト列ではない)文字列ですので、
仮に int の A.a を char にキャストしてもうまく動きません。
キャストしたものはキャストで戻すのが基本です。


# Windows 以外のOSでソケットのAPIが若干変わってもいいなら、WSASend でバッファを
# 分けるとかすると書き方次第で送信時の string コピーは要らなくなりそうな....
# string では試してませんが。

クリスタル 2005/04/30(土) 08:20:25
レスありがとうございます。

> 送受信が同じコンパイラの同じオプションで作られて
> 同じ環境で動かす前提であれば、
> 受信も同様にキャストするだけだと思います。

送受信は同じコンパイラの同じオプションで作っていますが
ゲームに通信部分を組み込みした場合、同じ環境では動かす事は無いと思います。
同じ環境と言うのは、自分と相手のPC環境が一緒と言う事ですよね。

> 同じ環境で動かす前提であれば、受信も同様にキャストするだけだと思います。
> recv(Sock, reinterpret_cast<char*>(&B), ...

現在は私のPCで動かしているので、受信も同じ事をしています。
これが、違う環境の場合Banさんが挙げてくださった

> オーダは htonl などの関数を使う

などを使用すれば良いのですね。


> もしかして誤解があるような。

すみません。
勘違いしていたみたいです。


> # Windows 以外のOSでソケットのAPIが若干変わってもいいなら

実行環境は全てWindowsになる予定ですが・・・
(こういう意味ではない・・・?)

Ban 2005/04/30(土) 15:45:43
> 同じ環境と言うのは、自分と相手のPC環境が一緒と言う事ですよね。

そして、送信プログラムと受信プログラムの作成コンパイラとそのオプションも一緒。

バイトオーダは通常 CPU で決まるので、ぶっちゃけサーバも含めて
Wintel(Windows/Intel) 専用なら「ネットワークバイトオーダにする」という
慣習(?)を無視して放っておいてもとりあえず動くはずです。

C/C++ の保証する互換性は「コンパイル時(ソース)」レベルで、
出力される実行ファイルについての保証はあまりしないので、
int のサイズや、構造体のサイズなどがコンパイラのオプションや種類で
変わってしまう可能性があります。これは、両方を同じ設定のコンパイラで
作ってあげれば、とりあえず動くはずです。

文字コードも、Windows なら S-JIS/UNICODE位しか選択肢がありませんし、
意識するのは NT 系と 98 系の問題くらい同じコンパイラで作ればそう問題も
ないと思います。
# NT系と98系でAPIの挙動が違うという、今回とは無関係ない問題はあるでしょうが...。

> これが、違う環境の場合Banさんが挙げてくださった
>> オーダは htonl などの関数を使う
>などを使用すれば良いのですね。

そうですね。将来的にサーバ側は UNIX で、とかの予定があると考えておく
必要があります。(予定がなくても考慮はしておいた方がいいと個人的には思ってますが.
..)


実行環境は全てWindowsになる予定ですが・・・
(こういう意味ではない・・・?)

WSASend という関数は WinSock にしかありませんので、
開発(コンパイル)環境がWindows限定になってしまいます。
# UNIX 系でも同種の機能はありますが、ソース事態の書き方が違います。
# send / recv などの基本的なソケットはほぼそのままで動くのですが......。

WSASend の利点は、送信するバイト列を全て連続したメモリに格納する必要がないこと。
このアドレスから何バイト、次はこのアドレスから何バイト、.....まとめて送って。
というお願いができる。まぁ最適化云々は動いてから考えればいいことなので、欄外に。

クリスタル 2005/04/30(土) 19:08:21
レスありがとうございます。

実行環境が
intelとAMDの場合はバイトオーダを気にして作成しないといけないと言う事ですね。

reshiaさんが仰った
『MPI』を使うと良いかもと書かれていますが
『htonl』とどちらが良いのでしょうか?

シャノン 2005/04/30(土) 20:32:43
> intelとAMDの場合はバイトオーダを気にして作成しないといけないと言う事ですね。

Intel と AMD ではバイトオーダーが違うかということならば、そんなことはありませ
ん。
AMD の CPU は Intel 互換なので、同じバイトオーダーを使っています。

Intel とは違うバイトオーダーを使っている代表格は、モトローラの CPU です。主に 
Mac で使われています。
モトローラの CPU 上で動く Windows はありませんので、対象 OS を Windows に限定で
きるのなら、

> Wintel(Windows/Intel) 専用なら「ネットワークバイトオーダにする」という
> 慣習(?)を無視して放っておいてもとりあえず動くはずです。

ただし、C 言語は Windows 以外のさまざまな環境や、もちろん Mac でも使える言語で
すし、ソケット通信というのも同様に、他環境との通信が可能です。
特にネットワーク通信では、そういうことを踏まえて、ネットワーク上に送り出すデー
タはネットワークバイトオーダー(ビッグエンディアン。Intel とは逆で、モトローラ
派のエンディアン方式)に統一しましょう、というお約束があるわけです。

クリスタル 2005/05/04(水) 12:08:33
レス有難うございます。

> 対象 OS を Windows に限定できるのなら

限定の予定ですが、サーバー側はいずれはLinux上かもしれませんので
ネットワークバイトオーダーをを気にしながら作成しようと思います。

> というお約束があるわけです。

そうだったのですか!?
それでしたら、尚更バイトオーダを意識して作成しないといけないですね。


これから、通信部分を作り変えてみたいと思います。

古いスレッドには追加発言できません。