ページ 1 / 1
Winsockで構造体をsendする方法につきましてご質問があります
Posted: 2011年2月21日(月) 22:57
by taketoshi
こんばんわ。お世話になります。
現在下記の構造体をwinsockで送信しようとしています。
コード:
typedef struct{
short nCol;
char szHeader[1024];
char szResult[1024*5];
}SQLRESULT;
サーバーとクライアントを同一PCで立ち上げて、送受信した際は問題なくクライアントで受け取れたのですが
LANで繋がれた別PCで実験してみたところ、データが受け取れませんでした。
良くわからなかったので、char szResult[1024*5];を→char szResult[1024];とバイト数を減らしたところ無事に受信できました
同一PCだと受け取れて、LANで繋がれたPCだと受け取れないのはなぜでしょうか??
その時に使用していたコードを記します。単純にsendとrecvを行っていただけです。
コード:
//送信データの入った構造体
SQLRESULT Result;
//省略
//送信側
nRet = send(s,(char *)&Result,sizeof(SQLRESULT),0);
//受信するための構造体
SQLRESULT RecvResult;
//受信側
nRet = recv(s,(char *)&RecvResult,sizeof(SQLRESULT),0)
そこで、自分で調べてみて、以下の様にコードを書き直して見ました
コード:
//送信側
int server::senddata(WPARAM wParam){
int i,nRet,nSendSize = 0;
int dwSize; //送信するバイト数
char *lptr; //構造体の先頭アドレスを格納するポインタ
dwSize = sizeof(SQLRESULT);//送信バイト数の計算
lptr = (char *)&Result; //構造体の先頭アドレスを代入
for(i = 0;i < nConnectCount;++i){
if((SOCKET)wParam == MemData[i].s){
while(nSendSize <= sizeof(SQLRESULT)){
nRet = send(MemData[i].s,lptr,dwSize,0);
dwSize -= nRet;//送信したバイト数を減算
lptr += nRet; //送信した分だけポインタを進める
nSendSize += nRet;
if(nRet == SOCKET_ERROR)
return -1;
}
}
}
return 0;
};
コード:
//受信側
int client::rcvData(){
int nRcv = 0;
char *recvptr = (char *)&Result;//ポインタの代入
int nRcvSize = 0;
int dwSize;
while(nRcvSize <= sizeof(SQLRESULT)){
nRcv = recv(s,(char *)recvptr,sizeof(SQLRESULT),0);
nRcvSize += nRcv; //受信バイト数を貯蓄
recvptr += nRcv; //受信した分ポインタを進める
if(nRcv == SOCKET_ERROR)
return -1;
}
return 0;
}
今現在このコードで同一PC内では通信できているのですが、LANでテストできる環境から離れてしまったのでうまくいくか結果は不明です。
かなり不安が残るので、添削できる方が居たら、是非お願いいたします。
また、同一PCだと問題なく通信できて、LAN環境に出ると受信できなくなる理由を教えてください。お願いいたします。
Re: Winsockで構造体をsendする方法につきましてご質問があります
Posted: 2011年2月21日(月) 23:51
by Ciel
ちなみにLAN環境では、クライアント側では「何も」受け取れてないんですか?
それとも何バイトかは受け取れてるんですか?
Re: Winsockで構造体をsendする方法につきましてご質問があります
Posted: 2011年2月22日(火) 00:04
by taketoshi
すいません、今直ぐにはLAN環境が無いので調べることが出来ないので、今夜はコードの書き直しを行い、
明日戻り値をデバッグしてみます。Cielさんヒントをありがとうございます。
##追記です
デバックコードを追加してテストしたとkろお
非LAN環境ではクライアント、サーバー側とも同じ数値をrecv,sendしておりました。
これをそのままLAN環境で実行してみます。
Re: Winsockで構造体をsendする方法につきましてご質問があります
Posted: 2011年2月22日(火) 00:13
by softya(ソフト屋)
パケットは経路上で分割されるのが普通です。
受信は決め打ちではなく分割されてくる前提で組む必要があります。
Re: Winsockで構造体をsendする方法につきましてご質問があります
Posted: 2011年2月22日(火) 00:25
by taketoshi
非同期化モードのFD_READで受信通知を受けているのですが
ここにメッセージボックスAPIを噛ませたところ、非LAN環境は一回だけメッセージボックスが出ました。
全く同じコードでLAN環境下ですとメッセージボックスが2回出たのですが。
つまり、これは二回に分割されてデータが来ているという事でしょうか・・・?
Re: Winsockで構造体をsendする方法につきましてご質問があります
Posted: 2011年2月22日(火) 00:43
by Poco
> つまり、これは二回に分割されてデータが来ているという事でしょうか・・・?
そういうことでしょうね。
分割数はEthernetフレームのサイズによります。
Re: Winsockで構造体をsendする方法につきましてご質問があります
Posted: 2011年2月22日(火) 22:00
by taketoshi
デバックを実行してみました。
結果から言うと、前回のままではクライアント側でデータを読み込めませんでした。
社内LANが遅延してるのかと思い。以下の様にSleep関数を入れてみた所、
クライアント側でうまく受信することが出来ました。
コード:
int client::rcvData(){
int nRcv = 0;
char *recvptr = (char *)&Result;//ポインタの代入
int nRcvSize = 0;
Sleep(1000);
while(nRcvSize < sizeof(SQLRESULT)){
nRcv = recv(s,recvptr,sizeof(SQLRESULT)-nRcvSize,0);
if(nRcv == SOCKET_ERROR)
return -1;
nRcvSize += nRcv; //受信バイト数を貯蓄
recvptr += nRcv; //受信した分ポインタを進める
}
//debug
char szDebug[256];
wsprintf(szDebug,"dwSize = %d, sizeof(SQLRESULT) = %d",nRcvSize,sizeof(SQLRESULT));
MessageBox(NULL,szDebug,"クライアント側",MB_OK);
return 0;
}
Sleepを入れたことで受信できるってことは遅延が考えられるのですが
社内LANによるデータ送受信の遅延って起こりうるのでしょうか。
環境はプロクシサーバーを通して、外部と接続しているネットワークになります。
Sleepを入れることで一応は解決したように見えるのですが、これが万全の解決方法でしょうか?
Re: Winsockで構造体をsendする方法につきましてご質問があります
Posted: 2011年2月22日(火) 22:23
by Ciel
読み込めなかったというのは具体的に言うとどういうことですか?
一部しか受け取れなかったのか、まったく受け取れなかったのか、
何かエラーメッセージはなかったのかとか、FD_READは何回飛んできたのかとか、詳しく教えて下さい。
Re: Winsockで構造体をsendする方法につきましてご質問があります
Posted: 2011年2月22日(火) 22:45
by taketoshi
全く読み込めませんでした。
以下の様にFD_READ部分のコードを書いているのですが、
Sleepを入れないとFD_READメッセージすら呼ばれていない様子でした。
cl.rcvData();内のデバックコードが呼ばれなかったです。
コード:
case FD_READ://データ受信通知
{
cl.rcvData();//データ受信関数を呼び出し。これが前述したコードになります
}
しかし、cl.rcvDataを呼び出す前に以下のようなコードにすると何故か受信が可能でした
どうやら処理を一時的に止める関数を呼び出すと、データが読み込めるようです。
コード:
case FD_READ://データ受信通知
{
MessageBox(NULL,"debug","",MB_OK); //MsgBox関数をrcvDataの前で呼び出す
Sleep(50) //もしくはスリープ関数を呼び出す
cl.rcvData();//データ受信関数を呼び出し。これが前述したコードになります
}
また、拠点A内の社内LANでテストしたところSleep(50)で全てのデータが読み込み可能だったのですが
拠点Aにサーバーを立てて、拠点Bのクライアントで読み込んだところ、Sleep(1000)ほどにしないと読み込めませんでした。
また、両方ともSleep関数やMsgboxなど、処理を一時的に止める関数をかませなければ受信が不可能でした。
拠点Aは東京で、拠点Bは大阪になります。
ご指導お願いいたします。
Re: Winsockで構造体をsendする方法につきましてご質問があります
Posted: 2011年2月22日(火) 22:57
by Ciel
FD_READすら呼ばれてないのに、FD_READ内にMessageBoxやSleepの処理を書くだけでうまくいくことがあるんですか。
じゃあやっぱり、FD_READのメッセージは飛んできているのでは?
サーバ側のsendではエラーは出てないんですよね?
Re: Winsockで構造体をsendする方法につきましてご質問があります
Posted: 2011年2月22日(火) 23:01
by Poco
非同期通信なんですよね?
recv()が0Byteリードしたときに無限ループになりませんか?
recv()呼び出しの後、0Byte読み込んだ場合に、whileループを抜けて次のメッセージ到着を待つ処理に変更したらどうなりますか?
Re: Winsockで構造体をsendする方法につきましてご質問があります
Posted: 2011年2月22日(火) 23:16
by taketoshi
cielさん
>じゃあやっぱり、FD_READのメッセージは飛んできているのでは?
そうですよね。。。おっしゃる通りなので飛んで来ているのだと思います。
>サーバ側のsendではエラーは出てないんですよね?
サーバー側のsendサイズも監視していますが、構造体のサイズに対して全て送信出来ていました。
ぽこさん
非同期通信で行っております。
while部分におっしゃるコードを書き足すとこのような感じでしょうか
コード:
while(nRcvSize < sizeof(SQLRESULT)){
nRcv = recv(s,recvptr,sizeof(SQLRESULT)-nRcvSize,0);
if(nRcv == 0) //此処を追加
return 0;
if(nRcv == SOCKET_ERROR)
return -1;
nRcvSize += nRcv; //受信バイト数を貯蓄
recvptr += nRcv; //受信した分ポインタを進める
}
アドバイスを頂いたコードで、明日再度テストを実施してみます。
皆様ありがとうございます。
Re: Winsockで構造体をsendする方法につきましてご質問があります
Posted: 2011年2月22日(火) 23:39
by Ciel
どちらにしろ、今の設計では、SOCKET_ERRORを返してきた時点で、
そのまま処理を終了してしまっていますので、色々と後で問題が出てくるかもしれません。
WSAEWOULDBLOCKっていうエラーは大きなデータなどを受信していると、結構頻繁に出るエラーなので、
なかなか受信できない状況にならなければいいですけどね。
私なら、WSAEWOULDBLOCKエラーが出たらどこまで受信したかを記録しといて、再度FD_READのメッセージが来た際に、
続きから受信するという方法をとりますね。
まあ、詳しいことは↓のサイトでも見てみてください。
http://kbmplaybbs.dip.jp/wiki/index.php ... k#i62a3c79
Re: Winsockで構造体をsendする方法につきましてご質問があります
Posted: 2011年2月22日(火) 23:44
by Poco
最初のステップとして、そんな感じでいいと思います。
WinSockの非同期通信やったことがないので確信はありませんが、これでもまだ上手くいかないと思います。
これで上手くいかない場合、whileループをなくす、すなわち、1回のFD_READのメッセージで1回のrecv()だけ呼ぶように修正してみてください。
Re: Winsockで構造体をsendする方法につきましてご質問があります
Posted: 2011年2月22日(火) 23:53
by taketoshi
>SOCKET_ERRORを返してきた時点で
なるほど、、、二回目のFD_READでソケットエラー起こすとデータ切れますね。。。
これは不味い。
お二方アドバイスありがとうございます。
ご紹介頂いたサイトをみて、今の知識で時間はかかりそうですが、なんとか理解できそうなので
少し自分で紐解いて、エラーに対する処理を実装してみたいと思います。頂いたアドバイスを基に、努力してみます。
当初の疑念は徐々に解決しましたが、
解決マークは付けずにおきます、壁に当たったら再度質問させて下さい。
感謝です。
Re: Winsockで構造体をsendする方法につきましてご質問があります
Posted: 2011年2月23日(水) 21:39
by taketoshi
引き続き実験してみました。
以下のコードでSleep関数をかまさずに、拠点A内のほかのPCと拠点Bまではデータの送受信が可能でした。
しかし、拠点Aから600kmほど離れた拠点cではデータの受信が不可能でした。
(デバック情報では受信できているが、何故か肝心のデータが表示されませんでした)
Winsockプログラミングで距離に寄って変わる要素って何でしょうか・・・・FD_READの通知回数かな?
コード:
//受信側
int client::rcvData(){
int nRcv = 0;
char *recvptr = (char *)&Result;//ポインタの代入
int nRcvSize = 0,;
while(nRcvSize < sizeof(SQLRESULT)){
nRcv = recv(s,recvptr,sizeof(SQLRESULT)-nRcvSize,0);
if(nRcv == 0){ //nRcvが0ならばループを抜ける
break;
}
if(nRcv == SOCKET_ERROR){ //WSAEWOULDBLOCKでなければエラーを表示する
if(WSAGetLastError() != WSAEWOULDBLOCK){
MessageBox(NULL,"データの受信でエラーが発生しました","確認",MB_OK);
return -1;
}
}else{//エラー以外ならば処理を施す
nRcvSize += nRcv; //受信バイト数を貯蓄
recvptr += nRcv; //受信した分ポインタを進める
}
}
DlgInfomation::nRcvSize = nRcvSize; //読み込んだ値をデバックウインドウに渡す
return 0;
}
なんだか、WSAEWOULDBLOCKの処理の仕方が怪しいのかなぁ。と踏んでいますが
お気づきの点はありますでしょうか・・。
whileを使わないコードにも挑戦してみようかと思っております。
Re: Winsockで構造体をsendする方法につきましてご質問があります
Posted: 2011年2月23日(水) 23:28
by Poco
Cielさんがご指摘しているとおり、WSAEWOULDBLOCKが返って来たら、再びFD_READの到着を待ってください。
つまり、client::rcvData()を抜けてください。
1回のrecv()で全てのデータを受信できるとは限らない、ということは認識されているようですが
1回のFD_READで全てのデータが届く、と思っていませんか?
ソースコード上でwhileループ内で送信したデータ全てを受けようとしていますが、これは間違いです(と、Cielさんも暗に指摘します)。
念の為に言っておきますが、ソースコードレベルで、TCP/IP通信の方法に2者の距離は「全く関係ありません」。
データ受信が出来ないのは、別に理由が必ずあります。
#昨日0Byteリードについて言及しましたが、これは受信バッファが空の場合を考慮に入れるという意味です。
#受信バッファが空の時は、WSAEWOULDBLOCKが返って来るみたいなので、昨日の発言は忘れてください。
Re: Winsockで構造体をsendする方法につきましてご質問があります
Posted: 2011年2月24日(木) 00:15
by taketoshi
ぽこさんアドバイスありがとう御座います。
一旦頭を整理してフローチャートを描いて、プログラムを書き起こしてみました
コード:
///////////////////////////////////////////////////////////////////
//FD_READ
//
///////////////////////////////////////////////////////////////////
int client::rcvData(){
char *rcvptr;
int nRcv;
int nError;
int dwSize;
static int nRecved; //どこまで受信したかを記憶する変数
//既にバッファを全て読み込んでいたら変数を初期化
if(nRecved == sizeof(SQLRESULT)){
nRecved = 0;
}
//受信バッファのポインタの代入
rcvptr = (char *)&Result;
//受信
nRcv = recv(s,rcvptr + nRecved,sizeof(SQLRESULT) - nRecved,0);
//ソケットエラー処理
if(nRcv == SOCKET_ERROR){
//エラーの取得
nError = WSAGetLastError();
//WSAEWOULDBLOCK以外なら-1を返して関数を抜ける
if(nError != WSAEWOULDBLOCK)
return -1;
//WSAEWOULDBLOCKなら-2を返して関数を抜ける
if(nError == WSAEWOULDBLOCK)
return -2;
//無事に受信できたらスタティック変数に読み込み量を追加
}else{
nRecved += nRcv;
}
//デバック情報を渡す
DlgInfomation::nRcvSize = nRecved;
return 0;
}
>念の為に言っておきますが、ソースコードレベルで、TCP/IP通信の方法に2者の距離は「全く関係ありません」。
>データ受信が出来ないのは、別に理由が必ずあります。
これすら疑念でした。ありがとうございます。
returnで処理を抜けたら、次にFD_READが呼ばれるのは何時なのか
疑問だったのですが続けざまに通知されるようですね。
同一PC間ではうまく動いていますが、明日再度テストを実施してみます。ありがとうございます。
Re: Winsockで構造体をsendする方法につきましてご質問があります
Posted: 2011年2月24日(木) 00:27
by たまねこ
ど素人で変なことを言っているかも知れませんが
WSAEWOULDBLOCK以外なら-1を返して関数を抜ける時、
static int nRecvedはそのままで良いんですか?(0にしなくて良いんですか)
Re: Winsockで構造体をsendする方法につきましてご質問があります
Posted: 2011年2月24日(木) 00:38
by taketoshi
ご指摘ありがとうございます。
普通のエラーで抜ける場合は初期化しておかないとだめでした。
Re: Winsockで構造体をsendする方法につきましてご質問があります
Posted: 2011年2月25日(金) 07:16
by taketoshi
上記のコードで無事に各拠点とも通信することが可能でした。
とても勉強になりました、アドバイス頂いた皆様、ありがとうございました。