■動作環境 :WindowsXP
■開発環境 :Visual C++6.0
■インクルード:#include <winsock2.h>
#include <ws2tcpip.h>
■リンク :WSock32.lib
お世話になります。
MFCをつかわないソケット通信について質問があります。
スタンドアローンの環境でテストをしています。
クライアントからサーバーへのデータ送信は出来たのですが、
サーバーからの送信データをクライアント側で受信が上手く出来ません。
クライアント側の rcv_bytes = recv(dstSocket, buf, BUFF , 0);で
処理が止まってしまいます。
クライアント用のプログラムとサーバー用のプログラムを同時に
デバッグして、サーバーからデータを送信してから、クライアント側
でステップ実行してデータを受けると上手くできます。
きっとタイミングの問題だと思うのですが、その方法がわかりません
分かる方がいましたらよろしくお願い致します。
プログラムにアドバイスがありましたらよろしくお願い致します。
<<サーバー側>>
// 受信
for(int count =0; count< 3; count++){
rcv_bytes = recv(dstSocket, buf, BUFF , 0);
if(rcv_bytes == 0 || rcv_bytes == -1) {
closesocket(dstSocket);
return false;
}
switch(count){
case 0:
memcpy(m_chCompany, buf,sizeof(buf));
break;
case 1:
memcpy(m_chSName, buf, sizeof(buf));
break;
case 2:
memcpy(m_chFName, buf, sizeof(buf));
break;
}
}
SendClient();
// 終了処理
WSACleanup();
return true;
}
bool CServerReception::SendClient()
{
strcpy(buf, 受信完了);
send(dstSocket, buf, strlen(buf) + 1, 0);
return true;
}
<<クライアント側>>
for(int count =0; count< 3; count++){
switch(count){
case 0:
send_buffer = Company;
break;
case 1:
send_buffer = SName;
break;
case 2:
send_buffer = FName;
break;
}
// 送信
SendServer(dstSocket, send_buffer);
Sleep(nSendTimer);
}
// 受信
ReceivServer();
// 終了処理
closesocket(dstSocket);
WSACleanup();
return true;
}
bool CClientSend::ReceivServer()
{
int rcv_bytes;
char buf[51];
// ここで止まっている
rcv_bytes = recv(dstSocket, buf, BUFF , 0);
if(rcv_bytes == 0 || rcv_bytes == -1) {
closesocket(dstSocket);
return false;
}
// Failopen
std::fstream cFStrm
( Data.txt , std::ios::out );
if( cFStrm.fail() ){
return false; // ファイルを開けませんでした。
}
cFStrm<< received: << << buf << '\n';
return true;
}
典型的間違いだね。
http://www.kt.rim.or.jp/~ksk/wskfaq-ja/articles/lame-list.html
20番を参照。
Winsock FAQ すべてに目を通してみよう
tetrapodさんありがとうございます。
がしかし、何を間違えているのか全然わかりません・・・
せっかくのアドバイスを生かせずに申し訳ありません。
>指定されたバッファ長よりも少ないバイト数しか転送しないと
>いうことは、いつでもありえるのです。
ここの文が怪しい気がするのですが・・・
よく分かっていません・・・
間違いは send/receive の返却値を見てないこと
1. send で 10byte 送ろうとしても 1byte しか送られないことがある
この場合は残り 9byte を再度送らなきゃならない
2. send と receive が1対1に対応することは無い
send で 10byte を2回送ったら→
receive が 1byte を20回受け取るかもしれない
receive が 20byte を1回で受け取るかもしれない
receive が 1+2+3+4+10 と5回になるかもしれない
提示のプログラムは3回 send して3回 receive してる。
それぞれが1対1対応してる前提になっているけど、実際の動きはそうとは限らない。
1回の receive が3回分の send の結果をいっぺんで受け取ったなら
次にデータが来るまで2回目の receive はブロックしてる
けど送られないから結局無限にブロックされてしまう、ということだね
ステップ実行すると send/receive が1対1対応するのでうまく動く
連続実行すると、3回の send が1回の receive で受け取られてしまうのだろうな
ただし network busy なときは send 1回が receive 数回になることもある
tetrapodさんも書かれていますけれど、
sendはともかく、
receiveに関しては必要なバイト数分読み込めるまで何回でも受信処理を
行わないといけないし、一回で複数分受け取ってしまう可能性もちゃんと
考慮しないとだめって事ですね。
受け取り側で複数分をいっぺんに受け取ってしまうケースに対応するには
送信するフォーマットをきちんと決めて送信側がどれだけ送ったのかが
ちゃんとわかるようにすることでしょう。
電文フォーマットを決めると言う事です。
そうしないと受け側は何処から何処までが何のデータだかさっぱり分からないと
言う事になります。
少なくともデータのデリミタ位は決めないと駄目です。
ここまで読んでいてちょっと気になったことが。
確かに典型的なTCP仕様の勘違いが原因の問題に見えますが、そもそも、プロトコルがTCPかUDPか
一言も触れられていないような。
とりあえず、質問者はこの点をはっきりさせておいた方がよいと思います。
tetrapodさん, PATIOさん, yoh2さん
ありがとうございます。
プロトコルはTCPです。
サーバー側のプログラムの SendClient(); の前に Sleep(5000);
を入れる事でタイミングが取れてクライアント側で受信ができました。
(クライアントのフォルダーにData.txtができ受信完了と書かれていた)
送信フォーマットを作成するなど初めてしりました、きっと変数に入れて送るのとは
違うんですよね?(済みません初めて挑戦しているので調査不足です。)
質問の問題は解決したのですが、この方法がいいのか不明ですので、改善点などの
ご指摘がありましたら、ご教授お願い致します。
実験ならそれでいいと思うが、実用に供するつもりならだめだろ
1件の転送にどんなに短くても5*2で10秒かかるんだぜ、話にならん。
んで、解決策などは既に出てるし解説するまでも無い話なんで
あとは自分で調べておくんなまし
Winsock FAQ ページを全部読むのが先だよ。
>質問の問題は解決したのですが、この方法がいいのか不明ですので、改善点などの
>ご指摘がありましたら、ご教授お願い致します。
大いに問題有り。
>サーバー側のプログラムの SendClient(); の前に Sleep(5000);
>を入れる事でタイミングが取れてクライアント側で受信ができました。
それはたまたまでしょ。毎回送受信する毎に5秒も待たされたら
使う側はたまりませんし、もし5秒以上データの送受信に掛かったら
どうするのですか?
解決策はすでに前の回答者の方から出ています。
上記の改修で解決などとは絶対に×です。
だーかーらー、ちゃんと電文フォーマットを決めなさいって。
というかTCPでやるにしても他の手段をとるにしてもですけれど、
こういう通信をする場合には電文フォーマットを決めるのが
まず一番最初にしなくてはいけない事ですよ。
ただ、何でも良いから通信がとおる事だけを確認したいなら
今のでも良いでしょうけれど、それは単なるTCPの実験であって
意味がある通信とは言えません。
せめてデリミタだけでも決めないと話は全く進みませんよ。
フォーマットを調べているのですが
電文フォーマットを作るとゆうのは
http://www.ne.jp/asahi/yamashita/programming/tips/mail_sample.html
class Mailのような事なのでしょうか?
コードを見ないでカキコですが・・・
フォーマットは流れてくるデータを分割(いかようにも操作)
できるように規格を決めること
自分で決めることです
送信2回
データ 12345
データ 1234567890
を送ってもパケットの最適化で1回の受信で取れる場合もあります。
受信データ
データ 123451234567890
で、2つに分けたいと思っても分けれない。
そこで、各データの先頭に1つ分のデータの長さを設定(もしくは決められた記号)
して送る
というように約束事を決めるのです
データ @12345
データ *1234567890
@にはデータ長をあらわす5
*にはデータ長をあらわす10が入っているとします。
受信データ @12345*1234567890
こうすると長さを見て分割できます
とはいうものの
データ @12345 @は5
データ *1234567890 *は10
データ +ABCDEFGHIJKLMN +は13
を送ったけど、
受信側で
受信データ @12345*1234567
受信データ 890+ABCDEFGHIJKLMN
なんてとり方もありえるわけで
1つ目を分割したのはいいけど後続のデータ足りない場合は
2つ目を追加してから分割とかやらないとだめなはず
なんか難しく考えすぎだよ。ふつーにファイル入出力するのと同じだよ。
テキストファイル:区切りが CR や CR+LF になってる (区切り子をデリミタという)
バイナリファイル:自分で決めた規則にしたがって適切に区切る
だろ?フォーマットを決めるってのはただそれだけの話
テキスト方式の転送ならたとえば、送受信形式を
1.CompanyName+CR+SName+CR+FName+CR と決める
2.送信側はこの形になるよう送る
3.受信側はこの形がくるはずとみなして受信
**Name に CR が入っている可能性があるなら上の手続きではまずいので
バイナリ方式の転送としてたとえば
1BYTE : CompanyName のバイト数
nBYTE : CompanyName (必ず先立つバイト数だけ送る)
のようにする。
TCP なら重複・欠落は無いし順序も保証されてる。ただし既に何度も書いたように
receive は全電文を一気に受信するかもしれないし
送ったときと違うバイト数に区切って受信するかもしれない
ので、そこだけ注意。
ソケットだからって特別なことは何も無い。
フツーにファイルを読み書きするのと同じにすればいい。
#う--ん、通信のソフトを作ったことないのかな?
一番簡単なフォ-マットは、RS-232Cのフォーマットですね。
STX 02H、ETX 03H でデータを挟み、データはすべてアスキ-形式にする。
データ以外のヘッダは以下の通り(代表的なもの)
1. ACK 06H
2. NAK 15H
3. ENC 05H
多分、TCP/IPの通信にもあると思います。(調べてません)
まず、本を購入することを薦めます。
> 一番簡単なフォ-マットは、RS-232Cのフォーマットですね。
これとはまた問題のレイヤが違うと思います。
IP層にはそのIPデータグラムの長さを示すフィールドがありますが、それはそれとして、
TCP層に載る通信データは、IP層での分割がどうなっているか気にせず(気にしてはならず)、
継ぎ目のないデータがだだ流れになっているという仕様ですから。
だから、TCPを使うプログラムが自前でデータの区切り方を定義する必要があるわけで。
区切り方については、すでにいくつか案が出ていますが、それでもどう設計したらいいか
分からないといった場合、既存のプロトコルを参考にするとよいです。
オススメはSMTP (RFC 2821) やPOP3 (RFC 1939) あたりかな。
TELNET、FTPはTCP通信の中でもだいぶ変態的な方法を使っているのでちと参考にし難いです。
HTTPは、KeepAliveという拡張機能を使う場合はそれなりに参考にできますが、そうでない場合、
HTMLとか画像とかのデータ送信がひとつ完了する毎に切断するといった乱暴仕様だからなぁ……
SIPはその点結構綺麗な仕様になっているのですが、全体の仕様が巨大で、必要な分を抜き出すだけでもひ
と苦労。
まあ、結局のところ
> なんか難しく考えすぎだよ。ふつーにファイル入出力するのと同じだよ。
なわけですが。違いはシークができない、ということくらい。