トップページ > 過去ログ > 記事閲覧
通信データ型
名前:ライブラリ使用者 日時: 2007/10/29 01:35

重ね重ね質問してすいません。 NetWorkSend()/NetWorkRecv()で送受信するデータ型についてご助言願いたい点があります。 複数のデータ型を扱いたいと思い、以下の様な構造体を作りました。 //送受信データ型 struct SendMessageType { int Type; //送受信データタイプ void* data; //データ詳細 }; Type:通信する型の識別 data:Typeで決められた変数or構造体 data詳細は以下のような物を扱います。 //チャット構造体 struct ChatData { int color; //文字色 char* data; //文字列データ }; これを受信した際には、 NetWorkRecv(handle,buff,DataLength); SendMessageType* msg = (SendMessageType*)buff; if(msg->Type == #define型)で型識別  もしくはswitchでケース分けしたい ChatData* pdata = (ChatData*)msg->data; という様な変換で取得したいと思っているのですが、pdataには送信側で設定した際のポインタアドレスが代入されてしまいます。 参照ポインタを持つ構造体を使うと起きるみたいなのですが、うまくやる方法はないものでしょうか? 思い付くのは、 案1: それぞれ別個の構造体を送りそのデータサイズで判別する。 この場合ですと、文字列を扱うためサイズが被る時が問題になりますし、使い勝手も悪いと感じています。 案2: すべてひっくるめて、1つの構造体にする。 データサイズによりますが、あまりに無駄が多くなります。 どのように解決すれば良いか、知恵を貸して下さい。

Page: 1 |

Re: 通信データ型 ( No.1 )
名前:かたぱると 日時:2007/10/29 13:46

こんなのはどうでしょうか。 enum SEND_DATA_TYPE//タイプ { TYPE_A, TYPE_B }; struct Network_Data // 実際に送信するデータ { SEND_DATA_TYPE type; //データタイプ char work[256]; // ※@ }; struct Type_A_Data // データパターンA { int data_1; int data_2; int data_3; }; struct Type_B_Data // データパターンB { double data_4; char data_5[80]; int data_6; }; <送信側プログラム> SEND_DATA_TYPE mode; Network_Data base; switch( mode ) // ←モードで詰め込むデータ型を分ける { case TYPE_A: // Aデータ型として詰め込む場合 Type_A_Data *a_data; base.type = TYPE_A; a_data = (Type_A_Data *)&base.work; a_data->data_1 = 10; a_data->data_2 = 20; a_data->data_3 = GetColor( 255 , 255 , 255 ); break; case TYPE_B: // Bデータ型として詰め込む場合 Type_B_Data *b_data; base.type = TYPE_B; b_data = (Type_B_Data *)&base.work; b_data->data_4 = 100; strcpy( b_data->data_5 , "文字列ッス" ); b_data->data_6 = GetColor( 100 , 100 , 255 ); break; } // baseを送信する <受信側プログラム> Network_Data base; // ←受信したデータ switch( base.type ) // ←送られてきたタイプで分ける { case TYPE_A: Type_A_Data *a_data; a_data = (Type_A_Data *)&base.work; // 値を使う DrawPixel( a_data->data_1 , a_data->data_2 , a_data->data_3 ); break; case TYPE_B: Type_B_Data *b_data; b_data = (Type_B_Data *)&base.work; // 値を使う if( b_data->data_4 >=30 ) { DrawString( 10 , 10 , b_data->data_5 , b_data->data_6 ); } break; } 詰め込むデータサイズは ※@より小さくなくてはいけないのでそこは管理&調整
Re: 通信データ型 ( No.2 )
名前:ライブラリ使用者 日時:2007/10/29 23:47

ありがとうございます! うまく受信できました。 が、自分がいまだ理屈をできていない(−−; struct Type_C_Data // データパターンC { int Color; char* pchar; }; これを試すと、始めに私が嵌った罠に陥るみたいですね。。 char* pchar というchar型のポインタを使用する場合と、かたぱるとさんの提起されているchar work[256]でどう違ってくるのですか? b_data = (Type_B_Data *)&base.work; &base.workここのキャストで結局は同じcharのポインタを指しますよね。 また、可変のデータは扱えないということなのでしょうか? ※@の最大サイズに注意とともに、データ量に関わらず256+4バイトのデータを送り続けなかればならない?
Re: 通信データ型 ( No.3 )
名前:ライブラリ使用者 日時:2007/10/29 23:49

参考までにサンプルソース記載します。 --送信側----------------------------------------- #include <windows.h> #include <process.h> #include "DxLib.h" enum SEND_DATA_TYPE//タイプ { TYPE_A, TYPE_B, TYPE_C }; struct Network_Data // 実際に送信するデータ { SEND_DATA_TYPE type; //データタイプ char work[256]; // ※@ }; struct Type_A_Data // データパターンA { int data_1; int data_2; int data_3; }; struct Type_B_Data // データパターンB { double data_4; char data_5[80]; int data_6; }; struct Type_C_Data // データパターンC { int Color; char* pchar; }; int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) { int Handle; int DataLength; IPDATA ip; ip.d1 = 192; ip.d2 = 168; ip.d3 = 1; ip.d4 = 2; //タイトルの決定 SetMainWindowText("SendSample"); //画像モードの決定 //SetGraphMode(XPIXEL,YPIXEL,GAMEBIT); //背景色を設定 SetBackgroundColor(255,255,255); //常に動作させる SetAlwaysRunFlag(TRUE); //ウインドウモードでのプログラム起動 //デフォルトではフルスクリーンモード起動となる ChangeWindowMode(TRUE); //DxLib初期化 if(DxLib_Init() == -1) return FALSE; //描画先を裏画面にする //ScreenFlip()を使い描画する(描画後は裏画面クリアClearDrawScreen()する) SetDrawScreen(DX_SCREEN_BACK); //////////////////////////////////////////////////////////////////////////// //ここまで初期化 //通信接続する if((Handle = ConnectNetWork(ip)) == -1) { //DxLib終了 DxLib_End(); return FALSE; } /////////////////////////////////////////////////////////////////////////// //データ作成する SEND_DATA_TYPE mode; Network_Data base; mode = TYPE_C; switch( mode ) // ←モードで詰め込むデータ型を分ける { case TYPE_A: // Aデータ型として詰め込む場合 Type_A_Data *a_data; base.type = TYPE_A; a_data = (Type_A_Data *)&base.work; a_data->data_1 = 10; a_data->data_2 = 20; a_data->data_3 = GetColor( 255 , 255 , 255 ); DataLength = sizeof(Type_A_Data) + 256;//256 = work[256] break; case TYPE_B: // Bデータ型として詰め込む場合 Type_B_Data *b_data; base.type = TYPE_B; b_data = (Type_B_Data *)&base.work; b_data->data_4 = 100; strcpy( b_data->data_5 , "文字列ッス" ); b_data->data_6 = GetColor( 100 , 100 , 255 ); DataLength = sizeof(Type_B_Data) + 256;//256 = work[256] break; case TYPE_C: // Cデータ型として詰め込む場合 Type_C_Data *c_data; base.type = TYPE_C; c_data = (Type_C_Data *)&base.work; c_data->Color = 7777; c_data->pchar = "abcde"; DataLength = strlen(c_data->pchar)+1 + 256;//256 = work[256] break; } //////////////////////////////////////////////////////////////////////////// //データ送信する NetWorkSend(Handle,(void*)&base,DataLength); //裏画面クリア ClearDrawScreen(); //裏画面を表示させる ScreenFlip(); WaitKey(); //通信接続を切断する CloseNetWork(Handle); //DxLib終了 DxLib_End(); return 0; } ---受信--------------------------------------- #include <windows.h> #include <process.h> #include "DxLib.h" enum SEND_DATA_TYPE//タイプ { TYPE_A, TYPE_B, TYPE_C }; struct Network_Data // 実際に送信するデータ { SEND_DATA_TYPE type; //データタイプ char work[256]; // ※@ }; struct Type_A_Data // データパターンA { int data_1; int data_2; int data_3; }; struct Type_B_Data // データパターンB { double data_4; char data_5[80]; int data_6; }; struct Type_C_Data // データパターンC { int Color; char* pchar; }; int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) { int Handle; int DataLength; char buff[1024]; int color; char hh[256]; //タイトルの決定 SetMainWindowText("RecvSample"); //画像モードの決定 //SetGraphMode(XPIXEL,YPIXEL,GAMEBIT); //背景色を設定 SetBackgroundColor(255,255,255); //常に動作させる SetAlwaysRunFlag(TRUE); //ウインドウモードでのプログラム起動 //デフォルトではフルスクリーンモード起動となる ChangeWindowMode(TRUE); //DxLib初期化 if(DxLib_Init() == -1) return FALSE; //描画先を裏画面にする //ScreenFlip()を使い描画する(描画後は裏画面クリアClearDrawScreen()する) SetDrawScreen(DX_SCREEN_BACK); //////////////////////////////////////////////////////////////////////////// //ここまで初期化 //通信接続待ちにする if(PreparationListenNetWork() == -1) { //DxLib終了 DxLib_End(); return FALSE; } Handle = -1; while(Handle == -1) { if(ProcessMessage() == -1) { break; } //新しい接続先を取得する Handle = GetNewAcceptNetWork(); } while(1) { if(ProcessMessage() == -1) { //×でのみ終了 break; } DataLength = GetNetWorkDataLength(Handle); if(DataLength > 0) { NetWorkRecv(Handle,buff,DataLength); //ここでbuffの中身を確認する! Network_Data* base; base = (Network_Data*)&buff; switch( base->type ) // ←送られてきたタイプで分ける { case TYPE_A: Type_A_Data *a_data; a_data = (Type_A_Data *)&base->work; // 値を使う DrawPixel( a_data->data_1 , a_data->data_2 , a_data->data_3 ); break; case TYPE_B: Type_B_Data *b_data; b_data = (Type_B_Data *)&base->work; // 値を使う if( b_data->data_4 >=30 ) { DrawString( 10 , 10 , b_data->data_5 , b_data->data_6 ); } break; case TYPE_C: Type_C_Data *c_data; c_data = (Type_C_Data *)&base->work; color = c_data->Color; strcpy(hh,c_data->pchar); break; } break; } //裏画面クリア ClearDrawScreen(); //裏画面を表示させる ScreenFlip(); WaitTimer(100); } //通信接続を切断する CloseNetWork(Handle); //通信接続待ちを解除する StopListenNetWork(); //DxLib終了 DxLib_End(); return 0; }
Re: 通信データ型 ( No.4 )
名前:かたぱると 日時:2007/10/30 01:53

【その壱:ポインタについて】 えー、まずポインタの説明ですが ttp://homepage2.nifty.com/natupaji/DxLib/lecture/lectureOthers1.html に管理者様のポインタ説明がございます。 かいつまんで説明しますと、 ポインタとは「実機のメモリ内アドレス」 なので、この情報を送信しても 受信側マシンの同じアドレスに何が入っているかは解らない訳です。 なので、何があるか解らないアドレスを送るのではなく、 データの実態を送ってあげる必要があります。 つまり、 char* pchar というchar型のポインタ ←アドレス(受信側は何が入っているか解らない) char work[256] ← 実態のデータそのもの というわけです。 実態があるものに対してそこのメモリアドレスを参照してデータを詰め込む、 といったポインタの使い方が b_data = (Type_B_Data *)&base.work; になります。 実際に送信しているデータは Network_Data構造体のchar work[256] の為、 送信データは実態になります。 ※POINT:他人にアドレスだけ教えてもなんのこっちゃ? 実態を渡そう。ポインタは自分のもの! 【その弐:送受信用データサイズについて】 送信に使っている DataLengthですが、実際に送受信する構造体はNetwork_Dataなので、 DataLength = sizeof(Network_Data); でOKのはずです。 【その参:workのサイズについて】 char work[256]; の256という数字は私が勝手に今回書きました。 実際に運用する場合のworkのサイズは 使用予定のデータパターン(Type_A_Data等) の『最大サイズ』です。 <例> Type_A_Data :サイズ100 Type_B_Data :サイズ40 Type_C_Data :サイズ120 だった場合、workのサイズが80とかですと Type_AやType_Cのデータはあふれてしまいメモリ破壊が起きます。 この場合最低でもType_Cのデータが格納できる120のサイズは確保しておかないといけません。 workを可変で持たせる手段もない事は無いですが、 決めうちでサイズを決めておくと 送受信のサイズが一定になるので管理が楽です。 と、駆け足で説明しましたがご理解頂けましたでしょうか。 解りづらい説明でホンマスイマセン。
Re: 通信データ型 ( No.5 )
名前:ライブラリ使用者 日時:2007/10/30 02:03

もう1つ方法思い浮かびました。(ちと強引ですが) (送信) char* pdata = new char[可変サイズ] 可変サイズはsizeof(int)+α(データ詳細) int Type = TypeA; memcpy(pdata,&Type,sizeof(int); memcpy(&pdata[sizeof(int)],データ中身,α); これでpdataには最初の4バイトにタイプを示すint値が、残りのバイトがデータ中身を示す連続メモリになると思います。 (受信) switch(*(int*)pdata) { case TypeA: (データ)&pdata[sizeoof(int)]; } これならば可変データも扱えると思うのですが、問題あるでしょうか? (データサイズなどの管理は重要でしょうが)
Re: 通信データ型 ( No.6 )
名前: 日時:2007/10/30 06:38

通信を行う場合の構造体では、 通常No5で紹介されているような構造をとります。 構造体の開始位置からのオフセットでデータを 詰めていくことでパケットを作成します。 送信する場合にポインタで持っているデータの サイズ長を指定する必要があるため、通常は 構造体にデータのサイズも含めます。
Re: 通信データ型 ( No.7 )
名前:かたぱると 日時:2007/10/30 09:52

成程納得。 通信をマトモにやっていなかった為 まだまだ頭が固かったようです。 DiretShow等のストリームデータに似てますね。
Re: 通信データ型 ( No.8 )
名前:Will 日時:2007/10/30 10:10

私が可変サイズの通信データの構造体を使う時は以下のようにしています。 typedef struct SendMessageType { int Type; //送受信データタイプ unsigned long lenght; //データレングス(私はこれがあるほうが好き) unsigned char data[1]; //データ詳細(コンパイラによってはサイズ0で定義できるものもある) } ST_SENDMESSAGETYPE; // 送信側 ST_SENDMESSAGETYPE *psend; データサイズ = (sizeof(ST_SENDMESSAGETYPE) - 1) + 可変データサイズ; // -1はdata[1]のサイズ分 psend = new char[サイズ]; // データのセット psend->Type = データタイプ; // data部をそれぞれのデータ型に合った構造体に変換してデータをセットする ST_TYPE_A_DATA *pdata; pdata = (ST_TYPE_A_DATA*)(psend->data); pdata->data_1 = 0; ・ ・ ・ // 受信側 ST_SENDMESSAGETYPE *pdata; pdata = (ST_SENDMESSAGETYPE*)受信データの先頭アドレス; switch (pdata->Type) { case タイプA: // dataをそれぞれのタイプにあった構造体に変換しデータを参照する ・ ・ ってな感じでしょうか。 これのほうが、構造体を使用できるのでソースの見易さ、データの管理がしやすいと思います。
Re: 通信データ型 ( No.9 )
名前:ライブラリ使用者 日時:2007/10/30 21:56

>かたぱるとさん レス遅れましたが、説明有難う御座います。 そうじゃないかと思っていた通りでした。 夜は、ずっとこのHPを開いたままソースとにらめっこ状態であったためレスに気付ずでした。 >通さん コメントありがとう御座います。 >Willさん 参考になります。盲点的な使い方でした(やや反則気味な気もしますがw 構造体として使えるのは、やり易そうだと思います。 ポインタは使い方間違えると痛い反面、色々手段がありますね。勉強になります。 これで今度こそ(?)、仕上がりそうです。 ご助言本当に有難う御座います。
Re: 通信データ型 ( No.10 )
名前:ライブラリ使用者 日時:2007/11/03 01:25

くじけそうです。。 また質問です。 送信データは蓄積され、一気に取得してしまうのでしょうか? NetWorkSend() NetWorkSend() GetNetWorkDataLength() NetWorkRecv() このような順番で通信相手との通信が成り立ったとします。この時、未受信のデータ量は単順に×2倍に膨れていました。  これは仕様なのでしょうか? 受信したことを確認した後、改めて次ぎの送信をするようプログラムするしかないのでしょうか?
Re: 通信データ型 ( No.11 )
名前:管理人 日時:2007/11/03 21:37

仕様となります。 ただ、NetWorkRecv では、必ずしも GetNetWorkDataLength で取得できるデータサイズ分 一度に取得する必要はありませんので、次のような書き方をすることが出来ます。 送信側 int type_length[2]; // 0:type 1:length SENDTYPE0DATA data0; // ←これが送りたいデータだとします 〜〜〜〜 data0 に情報をセット 〜〜〜〜 type_length[0] = 0; //タイプをセット type_length[1] = sizeof( SENDTYPE0DATA ); // データ長をセット // タイプとデータ長を送信 NetWorkSend( nethand, type_length, sizeof( int ) * 2 ); // データを送信 NetWorkSend( nethand, &data0, sizeof( SENDTYPE0DATA ) ); 受信側 int type_length[2]; // 0:type 1:length void *buffer; SENDTYPE0DATA *data0; // タイプとデータ長分の情報を受信しているか調べる if( GetNetWorkSendDataLength( nethandle ) > sizeof( int ) * 2 ) { // タイプとデータ長を読み込む(データは破棄しない) NetWorkRecvToPeek( nethandle, &type_length, sizeof( int ) * 2 ); // 残りの受信データがデータ長と同じかそれ以上あるか調べる if( type_length[1] + sizeof( int ) * 2 <= GetNetWorkSendDataLength( nethandle ) ) { // 改めてタイプとデータ長の情報を通信ハンドル上のバッファから削除する NetWorkRecv( nethandle, type_length, sizeof( int ) * 2 ); // データを格納するバッファを確保 buffer = new char[type_length[1]]; // 読み込み NewWorkRecv( nethandle, buffer, type_length[1] ); switch( type ) { case 0: data0 = (SENDTYPE0DATA *)buffer; 〜〜〜〜〜〜 受信した情報を処理 〜〜〜〜〜〜 break; } delete []buffer; } }
Re: 通信データ型 ( No.12 )
名前:ライブラリ使用者 日時:2007/11/04 00:13

回答有難う御座います。 そうですか、仕様ですか。。 元もとの質問で書いた、 NetWorkSend() NetWorkSend() GetNetWorkDataLength() NetWorkRecv() となるタイミングがあるのが問題でして。。 仕様であれば、ソース改善しないといけない訳ですね。。 ループで必要なバイト数分づつ処理させていくことにします。 一応確認の意味を込めて。 NetWorkSend()1回分毎に読み分けられないでしょうか? 過去ログの2006/12/30 19:18 「通信関係の使用ポートについて」にて、 DXライブラリの通信機能は NetWorkSend 関数で渡されたデータに ちょっとした追加情報を加えて飛ばしていますので(NetWorkSend に渡された データの長さを先頭4バイトに加えて相手にパケットを飛ばしている) とありますので、そのパケットを利用してそこまでをNetWorkRecv()で受信するということはムリでしょうか?
Re: 通信データ型 ( No.13 )
名前:管理人 日時:2007/11/08 13:40

> そのパケットを利用してそこまでをNetWorkRecv()で受信するということはムリでしょうか?  現状のソースでは受信が完了した時点でサイズ情報の4バイトは破棄してしまって いるので現時点では不可能ですが、通信部分のプログラムを書き換えれば実現できます。  実装する場合は受信データがどれだけ溜まっても GetNetWorkDataLength で 取得出来るデータ量は時間的に一番古い受信データのサイズのみとなると思いますが、 それで宜しいでしょうか?(因みに今までの機能との互換性を保つために「新仕様の 通信処理を行うかどうか」を設定する関数も追加することになると思います)
Re: 通信データ型 ( No.14 )
名前:ライブラリ使用者 日時:2007/11/09 00:27

要望として上げておきながらではありますが、機能追加は無しでいいです。 理由:互換性のため、設定追加が必要とのことであり汎用性が薄れるということ。  なにより、今回の件は私のソースの記述力不足によるものと判断できる点が多い為です。 ご検討頂き有難う御座いました。

Page: 1 |