トップページ > 過去ログ > 記事閲覧
通信待ち
名前:ライブラリ使用者 日時: 2007/12/10 00:49

 しばらくぶりです。 ある程度のレベルでアプリは完成したのですが、通信接続エラー(待ち?)が発生する場合があります。 発生の条件としては、多人数接続時です。 あとはPC性能は悪い人ほどでしょうか? 原因特定したいのですが、どうにも解析しようがありません。(私自身の環境では再現しません 下記ソースの一部ですが、TIMEOUTを10000(10秒)にすると、接続人数が30人以上になるとこのループが終わりません。(ほぼ全員で、タイムアウトすらしない そこで、60000(60秒)とすると、この問題は解決しました。(最大60名弱まで同時接続チャットできました。 が、何名かはこのループが終わらない人が出てきてしまいます。 状況としてはこのループがまわっていない感じです。 GetNetWorkDataLength()内でも処理待ちなような事は発生しえるのでしょうか? ※エラーの発生ですが、ある時以降全員がなるのではなく、特定の人がなり続けます。 接続できない人がいても、その後に接続試行して繋がる人もおります。 アプリの概要として、スレッド処理にて通信処理してる部分もありますがCriticalSection()を用い、排他的に処理ができていると思います。 長くなりましたが、対応のご助言頂けないでしょうか //タイマー開始 timer = GetNowCount(); while((GetNowCount() - timer) < TIMEOUT) { if(ProcessMessage() == -1) { //エラー処理抜け return 0; } if(CheckHitKey(KEY_INPUT_ESCAPE) == 1) { break;//ESCキーが押されたらループから抜ける } DataLength = GetNetWorkDataLength(GetNetHandle()); if(DataLength > 0) { //データ取得用バッファ確保 char* buf = new char[DataLength]; //接続キャラデータの取得 NetWorkRecv(GetNetHandle(),buf,DataLength); //このタイミングで来た、キャラデータ以外は無視する int DataPos = 0; do { pmsg = (SendMessageType*)&buf[DataPos]; switch(pmsg->Type) { case SMT_ALLEND: //取得完了 Finish = TRUE; break; default: //チャットデータなどはすべて無視する break; } //次ぎのメッセージまで移動する DataPos += pmsg->PacketSize; } while(DataLength > DataPos); delete [] buf; if(Finish) break; }

Page: 1 | 2 | 3 |

Re: 通信待ち ( No.1 )
名前: 日時:2007/12/11 00:57

質問を見る限り、ある程度完成度が高い アプリケーションのようなのでこのレベルになると 現象だけではデバックするのはかなり厳しいかと。 通常はログなどを入れてファイルなどに書き出し どのようなことが起こっているのかを突き止める のが先決です。 なので、上記を踏まえた上で以下は参考程度に してください。 >CriticalSection()を用い 逆にデットロックになっていないか注意が必要です >ProcessMessage() 受信処理のなかで、一緒にプロセスメッセージを 処理しているようですが、この受信処理はメインに かかれているのでしょうか? システムのメッセージ処理はどれだけかかるか、 わからないので、タイムアウトを測っている プログラムの中で使うのはあまり良くないかと。 >DataPos += pmsg->PacketSize PacketSizeにはどんなサイズが入っているのでしょうか?NetWorkRecv関数が失敗した場合は条件が 満たされずに無限ループになる可能性がある気が しますが。。。
Re: 通信待ち ( No.2 )
名前:ライブラリ使用者 日時:2007/12/12 00:13

ご意見ありがとうございます。 1つ1つ応えていきます。 >通常はログなどを入れてファイルなどに書き出し どのようなことが起こっているのかを突き止める のが先決です。 while文の間にDataLengthの和を書き出す1文を追加して既に確認していたのですが、値は変わらずだったそうです。そのため、値がずっと0かループが途切れているかのどちらか。且つタイムアウトしていないことから、 >状況としてはこのループがまわっていない感じです。 と記載させて頂きました。私ではこれ以上は憶測でしか考えられなかったもので。。 >ProcessMessage() メインループに入る前になります。 リファレンスにて、とにかく定期的に呼び出せとあるので処理待ちの間も呼び出していました。 正常時であればこのループは1秒未満で終了しますので、特に入れる事に拘る必要はなかったですか。 >DataPos += pmsg->PacketSize う。確かに言われる通りですね。 GetNetWorkDataLength()後の受信を無条件に成功すると信じ込んでしまっていました。 pmsg->PacketSizeの値が0を示す場合など無限ループに陥りますね。ここは絶対の修正箇所です>< >CriticalSection()を用い 逆にデットロックになっていないか注意が必要です ごめんなさい、勉強不足なものでイマイチ理解できないです。 これはEnterCriticalSection、LeaveCriticalSectionの2つの間で排他制御を保障してくれるものではないのでしょうか? ・・・ 再度、デッドロック調べてみました。 LeaveCriticalSectionが行われていないパターンがないかという事でしょうか? 途中でループ抜けしてる箇所がないか今一度確認してみます。 とはいえ、CriticalSectionで管理しているのはサーバー側アプリだけですので、今回のクライアント側で固まる原因にはならないだろうと思っています。 もしデッドロックならサーバー側ですべてが固まると思います。 まずは上記で確認してみたいと思います。 無限ループにはまるパターンは気付きませんでしたので、大変参考になりました。
Re: 通信待ち ( No.3 )
名前: 日時:2007/12/12 00:58

>LeaveCriticalSectionが行われていない >パターンがないかという事でしょうか? 大体あっているのですが、 LeaveCriticalSectionを呼んでいてもタイミングに よってなる可能性があります。 スレッド1・2が生成されたとき。 スレッド1 EnterCriticalSection1 CriticalSection1を取得 EnterCriticalSection2 <<ここで待ち CriticalSection2取得待ち 処理 LeaveCriticalSection2 CriticalSection2終了 LeaveCriticalSection1 CriticalSection1終了 スレッド2 EnterCriticalSection2 CriticalSection2取得 EnterCriticalSection1 <<ここで待ち CriticalSection1取得待ち 処理 LeaveCriticalSection1 CriticalSection1終了 LeaveCriticalSection2 CriticalSection2終了 となったときに、1は2が終了するまで動かず、 2は1が終了するまで動かないというような場合を デッドロックといいます。 確かに今回の場合、 サーバー側ということで、確かにこちらが固まれば クライアント全てが何かしら異常になるはずなので、 この可能性は低そうですね。 >メインループに入る前になります。 入る前というのが気になりますが、 クライアント側は複数のスレッドを使っていない ということでしょうか? であれば、メインにはProcessMessageの処理は 必要かもしれません。
Re: 通信待ち ( No.4 )
名前:ライブラリ使用者 日時:2007/12/13 00:01

CriticalSectionですが1つしか使ってないので、スレッド立ててはいますが実質的には1本通行なので、示されたようなデッドロックは発生しないと思います。 クライアントはMain()のループのみです。 上のソースはInit()内の一部とお考えくださいませ。 Main() {  Init(); <-この内部処理でお題の問題発生してます  while() { ProcessMessage(); } }
Re: 通信待ち ( No.5 )
名前: 日時:2007/12/13 16:32

>この内部処理で サンプルを見ると、受信や切断待ちでも ProcessMessageは呼ばれているようですね。 この関数から抜けていないとなると、 やはりwhile(DataLength > DataPos); このループでの無限ループの可能性が濃厚ですが。。。 データの構造とかはちょっと変わると思いますが、 後でサンプルでも。。
Re: 通信待ち ( No.6 )
名前: 日時:2007/12/13 23:30

サーバ側まだ作ってないので動作未確認ですが。。。 #include "DxLib.h" #define FONTHIGNT 16 #define TIMEOUT (10*1000) //10秒 #define SMT_CHAR 0x00000001 typedef struct {  int Type;//パケットのタイプ  int Size;//パケットのサイズ(このヘッダを含む) }PK_HEADER; typedef struct {  PK_HEADER hdr;  char name[16];  int hp, mhp;  int mp, mmp; }CHAR_DATA; int GetPacketHeader( int NetHandle, PK_HEADER* pheader, int timeout ); int GetPacketData( int NetHandle, void* Data, int Size, int timeout ); int GetCharDataPacket( int NetHandle, CHAR_DATA* pData ); int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) {  int Ret;  char StrBuf[ 256 ] ;  IPDATA Ip ;  // 接続用IPアドレスデータ  int NetHandle ;  // ネットワークハンドル  CHAR_DATA MyChar;  memset( &MyChar, 0, sizeof(MyChar));  ChangeWindowMode( TRUE );  SetDoubleStartValidFlag( TRUE );  if( DxLib_Init() == -1 ){ return -1; }  Ip.d1 = 192, Ip.d2 = 168, Ip.d3 = 1, Ip.d4 = 2;  // 通信を確立  NetHandle = ConnectNetWork( Ip ) ;  if( NetHandle < 0 ){   MessageBox( NULL, "通信の確立に失敗しました", NULL, MB_OK );   goto Exit;  }  //キャラ情報取得  Ret = GetCharDataPacket( NetHandle, &MyChar );  if ( Ret < 0 ){   sprintf( StrBuf,"キャラクタ情報の取得に失敗しました:Ret(%d)", Ret );   MessageBox( NULL, StrBuf, NULL, MB_OK );   goto Release;  }  while( !ProcessMessage() && !CheckHitKey(KEY_INPUT_ESCAPE) ){   sprintf( StrBuf,"キャラクタデータ Name:%s HP:%d/%d MP:%d/%d", MyChar.name, MyChar.hp, MyChar.mhp, MyChar.mp, MyChar.mmp );   DrawString( 0 , FONTHIGNT*10 , StrBuf , GetColor( 255 , 255 , 255 ) );  } Release:  CloseNetWork( NetHandle ); Exit:  DxLib_End();  return 0 ; // ソフトの終了 } int GetCharDataPacket( int NetHandle, CHAR_DATA* pData ) {  int FuncRet;  CHAR_DATA chr;  FuncRet = GetPacketHeader( NetHandle, &chr.hdr, (TIMEOUT/2));//ヘッダ情報取得  if ( FuncRet < 0 ){ return -1;}  if ( chr.hdr.Type != SMT_CHAR ){ return -2; }//キャラデータ以外無視  FuncRet = GetPacketData( NetHandle,(void*)&chr, chr.hdr.Size,(TIMEOUT/2));//キャラデータ取得  if ( FuncRet < 0 ){ return -3; }  memcpy( pData, &chr, sizeof(CHAR_DATA));  return 0; } //データのタイプとサイズを先行して取得(通信バッファには残す) int GetPacketHeader( int NetHandle, PK_HEADER* pheader, int timeout ) {  int DataLength, RecvRet;  int Timer = GetNowCount();  if( pheader == NULL ){ return -1; }  while( !ProcessMessage() ){   if ((GetNowCount() - Timer) >= timeout ){ return -2; }   DataLength = GetNetWorkDataLength(NetHandle);   if ( DataLength >= sizeof(PK_HEADER) ){    RecvRet = NetWorkRecvToPeek( NetHandle, pheader, sizeof(PK_HEADER));    if ( RecvRet < 0 ){ return -3; }    break;   }  }  return 0; } //実データの取得 int GetPacketData( int NetHandle, void* Data, int Size, int timeout ) {  int DataLength, RecvRet;  int Timer = GetNowCount();  if ( Data == NULL ){ return -1; }  while( !ProcessMessage() ){   if ((GetNowCount() - Timer) >= timeout ){ return -2; }   DataLength = GetNetWorkDataLength(NetHandle);   if ( DataLength >= Size ){    RecvRet = NetWorkRecv( NetHandle, Data, Size );    if ( RecvRet < 0 ){ return -3; }    break;   }  }  return 0; } 行数稼ぐためにすこし見辛いですが タイムアウトまでは受信バッファが溜まるのを待ちます。 溜まれば取得って感じです。 タイプとサイズは調べるために先行で取得し、 欲しいタイプと違えばそのまま無視しても受信バッファに残ります。
Re: 通信待ち ( No.7 )
名前:ライブラリ使用者 日時:2007/12/15 02:20

ゆっくり見る時間がなかなかとれず、申し訳ありません。 この週末にでも知り合いに、通さん指摘を修正したEXEで確認してみようと思います。 サンプルですが(ここまでしてもらって有難うございます)、確認用にということであまり実用的な突っ込みは避けますがこの位のチェック体制でソースは作らないと確認はとれませんね。 (私のソースのなんと甘いことか・・ しいて上げるなら、GetPacketData()で取得したDataの中身を確認をしたほうが良いのではないかと思います。 実際にこのソースを流用させて頂く場合には、 ・受信データの最初のパケットがPK_HEADERとは限らない事 ・NetWorkRecvToPeek()失敗時に処理を抜けていますので、その後処理及び次ぎの受信データを受け取る処理 が必要ですね。 話が少し逸れますが、NetWorkRecvToPeek()で抜けていますが、このままCloseNetWork、DxLib_Endした場合というのはバッファ上のデータはどうなるのでしょう?
Re: 通信待ち ( No.8 )
名前: 日時:2007/12/15 06:08

>・受信データの最初のパケットがPK_HEADERとは >限らない事 わたしのコードの仕様では、キャラパケットの 先頭にHDRというヘッダ情報をもたせていますが、 これを後に実装する全てのパケットに対して実装 することで、受信された、ヘッダ情報分のみを 先行して調べることでそのあとに続くデータが どのようなパケットなのかをあらわすことが 出来ます。 つまり、送るデータ(パケット)の全てにこのヘッダ 情報を付加すれば、どんなデータ(パケット)を 送っても、必ずヘッダ情報が最初に付加されること になります。 >GetPacketData()で取得したDataの中身を確認を >したほうが良いのではないかと思います。 中身を確認というのがよくわかりませんが、 GetPacketDataはヘッダ情報に含まれるパケットの 全てのバッファを取得することを期待しています。 パケットのデータが何であるかはこの関数は意識 しません。 中身が何であるかを調べるのは、呼び出し側が、 ヘッダのTypeを持って判断する以外ないでしょう。 >バッファ上のデータはどうなるのでしょう 後、バッファ上のデータとはDxライブラリが 内部で持っている通信バッファのことをいっている のでしょうか? もしそうなら、ライブラリの実装に依存しますが、 そもそも、確保したことすら使用者にみせていないので、 CloseNetWork、DxLib_Endこれらのタイミングで 解放されていなければ、逆に仕様バグになるような。
Re: 通信待ち ( No.9 )
名前:ライブラリ使用者 日時:2007/12/15 14:12

>・受信データの最初のパケットがPK_HEADERとは >限らない事 すみません、ここは言葉足らずでした。 望んだPK_HEADERとは限らないと云う意味です。(このType値が異なる場合) 上記ソースでは、受信バッファはそのままですので先行して取得したデータはいつも同じになってしまいますよね? 通信バッファはまさにそこがフト疑問に思ってしまいました。 また夜でにも検証結果書き込みます。 知り合いがうまく捕まれば、ですが。
Re: 通信待ち ( No.10 )
名前: 日時:2007/12/17 03:27

>望んだPK_HEADERとは限らない >先行して取得したデータはいつも同じ Yesです。 GetPacketData関数とGetPacketHeader関数は 汎用的に使われることを意識していますので、 そのパケットが不要かどうかは、GetPacketHeaderを 呼び出した関数が、その後にそのヘッダのタイプの パケットを破棄するかどうかを決めて、破棄する 場合には、GetPacketDataでヘッダごと、 パケットを受信して捨てる必要があります。

Page: 1 | 2 | 3 |