• 締切
  • すぐに回答を!

TCPでパケットロス?

  • 質問No.4665145
  • 閲覧数6752
  • ありがとう数5
  • 回答数6

お礼率 33% (2/6)

初めまして。投稿させて頂きます。

現在C/C++でwinsockを使ったプログラミングを行っています。
その中で2台の端末間で画像データのやり取りを行っています。
【開発環境】
OS:Windows Xp sp2,sp3
IDE:VisualStudio2005(2台)

TCPを使い、ファイルのバイナリデータを送信して受信し、保存するという単純なプログラムなのですが、受信側で不定期にパケットロス(?)が発生します。以下に簡単なプログラムを示します。

【送信側】
byte szBuf[1025];
int read_fd;
_sopen_s(&read_fd,"test.bmp",_O_RDONLY|_O_BINARY,_SH_DENYNO,0);
while(true) {
rtn=_read(read_fd,szBuf,1024);
if(rtn<=0)break;
else {
snd=send(s,(char*)&szBuf,rtn,0);
}
}
【受信側】
byte buf[1025];
ofstream os;
os.open("test.bmp",ios::binary|ios::out);
byte buf[1025];
while(true) {
int nRcv = recv(s, (char*)&buf, 1024, 0);
if(nRcv<=0) {
break;
}
os.write((char*)buf,nRcv);
}
os.close();

何度か実行してみると、受信サイズが送信サイズが少ないことがあり、一回ごとの受信パケットを見てみるとパケットをロスしているような場所(受信サイズが1バイト)があります。
ちなみにデバッグモードの時はあまり起こりませんが、通常実行時は頻繁に発生します。
試しに送信側の
snd=send(s,(char*)&szBuf,rtn,0);
の後に
Sleep(1);
と入れてみると、デバッグモードでも通常実行時でも(今のところは)受信の不足がなくなりました。

フィルタリングソフトで細かいことは見ていないのですが、プログラムに問題、あるいは心当たりがある方はご教示願います。

回答 (全6件)

  • 回答No.6

ベストアンサー率 43% (7609/17468)

>現在は受信側での受信が終わっても受信の総サイズと送信の総サイズが異なっていることがあり(受信側が少ない)その場合は受け取った画像を見ることができません。

「自作プログラムで受信した、サイズが小さくなったファイル」と「他の方法でコピーして持って来た、送信したのと同じファイル」を「バイナリ比較」してみましたか?

MSDOSプロンプトを開いて
FC /B 短いファイル 長いファイル
と打ち込めば、2つのファイルがバイナリ比較されます。

バイナリ比較すれば「どの辺りが欠落したか?」が判ります。

もし「末尾が欠落している」のであれば「最後のsendから帰って来て、相手が最後のデータを受信し終る前に、送信側が終ったと思ってセッションを切ってしまったので、完全に受信が終る前に受信が中断された」と言う可能性があります。

もし「真ん中が欠落して、途中からズレている」のであれば「受信ルーチンが不完全」なのでしょう。

>送信側でも送信すると同時にその内容を新しいファイルに書き込んでみましたが、その画像はきちんと見れていた
という事なら、送信ルーチンは問題がなく、疑う余地が無い訳ですから、受信ルーチンを疑うしかありません。

とにかく「画像ファイルのどこが欠落してるのか?」が判らないと、どうにもならないでしょう。
  • 回答No.5

ベストアンサー率 38% (96/252)

> 一回ごとの受信パケットを見てみるとパケットを
> ロスしているような場所(受信サイズが1バイト)があります。
とありますが、1バイトだからロスしているということは
無いと思います。大げさな話、1024バイト送信ても、
1バイトが1024回という可能性だってあるわけですから。

問題とされている部分がよく理解できないので、
本当にエラーが起こっているのかも怪しいです。

結局のところどのような結果を期待していて、
現在はどのように動作しているのでしょうか?

実は送信側でファイル読み込み時に終端を読み取って、
0が帰ってきているということはありませんか?
>rtn=_read(read_fd,szBuf,1024);
>if(rtn<=0)break;
補足コメント
bony_cry

お礼率 33% (2/6)

ご回答ありがとうございます。
本来自分が実行したいプログラムは、
【送信側】
1.ファイル(画像などのバイナリファイル)のサイズを通知
2.ファイルを読み取りデータを逐次送信
【受信側】
1.ファイルのサイズを受け取る
2.ファイルサイズに達するまでデータを受け取る
3.ファイルを作成して受け取ったデータを書き込む
4.ファイルを保存する

という流れで作成したいと考えていました。
バイナリファイルでこの場合、どのようなプログラムを組むべきなのでしょうか。テキストファイルの時は難なくできたのですが。。。
sendのデータサイズをどのように指定すればよいのか分からず
(配列データに対してsizeof()演算子を使用すると一定のサイズで送られるため、受信側の総サイズがファイルサイズと合致しなくなると考え)、
投稿させて頂いたようなプログラムを書きました。
現在は受信側での受信が終わっても受信の総サイズと送信の総サイズが異なっていることがあり(受信側が少ない)その場合は受け取った画像を見ることができません。

また、
>実は送信側でファイル読み込み時に終端を読み取って、
>0が帰ってきているということはありませんか?
という点に関しましては、送信側でも送信すると同時にその内容を新しいファイルに書き込んでみましたが、その画像はきちんと見れていたため、途中でループを抜けているということはないように思います。

アドバイス等ありましたらお願いしますm(__)m
投稿日時:2009/01/28 12:05
  • 回答No.4

ベストアンサー率 38% (96/252)

> TCP/IPの送信バッファが詰まってて、
> バッファの空きが無ければ何も送らずに「0」が返される
ちなみにこれが起こるのはTCP/IPの非同期モードです。
同期モードはそもそもsendが0を返す事がありません。

なので、同期モードか非同期モードかを聞いています。
補足コメント
bony_cry

お礼率 33% (2/6)

返答が遅くなってしまい申し訳ありません。
同期モードで行っています。

なのでaris_wizさんのおっしゃる通りで、試しにじっこうしてみても結果は変わりませんでした。
投稿日時:2009/01/27 18:10
  • 回答No.3

ベストアンサー率 43% (7609/17468)

snd=send(s,(char*)&szBuf,rtn,0);

この呼び出しで、rtnバイトを確実に送信するとは限らない。

TCP/IPの送信バッファが詰まってて、バッファの空きが無ければ何も送らずに「0」が返される(クローズされて無くても0は返る)し、バッファに10バイトの空きしか無ければ「10バイトだけ送った」と「10」が返って来る。

なのでsnd == rtnなら良いが、snd < rtnになったら、rtn - sndバイトのデータが送られずに消え去る事になる。

>snd=send(s,(char*)&szBuf,rtn,0);
>の後に
>Sleep(1);
そりゃそうだろう。sleepすれば、その間にバッファに溜まったデータが出て行ってバッファが空になるから「バッファが詰まって一部分しか送れない、または、全部が送れない」って事が起きなくなる。

とは言え「Sleep(1);で待ち時間が充分だという保証はどこにもない」ので、sleepで誤魔化してはいけない。

以下のプログラムはタイムアウトの処理が入って無いので、下手すると無限ループになる可能性があるが、このように「こまぎれでsendを呼ぶ」ように書かないといけない。
byte szBuf[1025];
char *p;
int read_fd;
_sopen_s(&read_fd,"test.bmp",_O_RDONLY|_O_BINARY,_SH_DENYNO,0);
while(true) {
rtn=_read(read_fd,szBuf,1024);
if(rtn<=0)break;
else {
p=szBuf;
while(rtn) {
snd=send(s,p,rtn,0);
//send関数から永久に0が返され続ける場合もあるので対処する事。
//このままでは永久ループするかも知れない。
//また、1回0が返されたからと言って、そこでやめてはいけない。
//0が返された次のsendで何バイトか送れる可能性もある。
p+=snd;
rtn-=snd;
}
}
}

それと
snd=send(s,(char*)&szBuf,rtn,0);

int nRcv = recv(s, (char*)&buf, 1024, 0);
の「&szBuf」とか「&buf」は何をしたいのだろう?

snd=send(s,(char*)&szBuf[0],rtn,0);

int nRcv = recv(s, (char*)&buf[0], 1024, 0);
か、または
snd=send(s,(char*)szBuf,rtn,0);

int nRcv = recv(s, (char*)buf, 1024, 0);
なら何がしたいか判るけど。
お礼コメント
bony_cry

お礼率 33% (2/6)

ご返答頂きありがとうございます。

説明が遅れてしまいましたが、現在は同期モードで送信を行っています。
示して頂いたようにプログラムを実行してみましたが、やはり結果は変わりませんでした。
投稿日時:2009/01/27 18:16
  • 回答No.2

ベストアンサー率 73% (867/1179)

TCPでは、「データがバイト列として間違いなく届く」ことは保証していますが、転送はバイト列として処理しており「パケット」という概念がありません。
TCPレベルでは送信データはバイト列として処理し、それをIPレベルでパケットに分割しますが、受信したところでもTCPレベルではパケットの概念のないバイト列に戻ります。

そのため、送信側で1024バイトずつsendしたからといって、1024バイトずつ送信する保証はありません。
recvは、その時OSレベルで受信していたデータを返しますから、受け取っているデータが1024バイトに満たなければ、引数バッファサイズに1024を指定していても、それより小さい量のデータを受け取ることになります。
受信側で1024バイトずつrecvできるという保証も無くなるのです。

以下、ちょっと推定が入ってますが、Sleepを入れると動作が変わる理由について。
イーサネットのパケットは1500バイトで、TCPレベルだと1460バイト単位でデータの伝送ができます。
Sleepのないプログラムだと、送信データが続々と発生しますから、
1024バイトづつsendしたデータも、TCP/IPレベルではデータをため込んで1460バイトずつ送受信することになると思います。
で、受信側は、1460ずつ受信したデータを、
「最大1024バイトのバッファでrecv」するわけですが、その時受信するタイミングによって、1024バイト分受信できていれば、フルに受信できますが、受信済のデータ量が少ない場合は、1024より少ない結果となるのです。

で、Sleep を入れると、処理が間欠的になりますから、
送信側では、「最大1460バイト通信」できるものの、データが無いので仕方なく「1024バイトだけ送信」することになります。
受信側に到着してため込むデータも「1024バイト単位でデータがたまっていく」ので、
1024バイト単位のrecvで毎回ちゃんと受信できるのでしょう。

TCP/IPでプログラムを組む場合「送信側で1024バイトずつ送っているから、受信側で1024バイトずつrecvすれば、ちょうど送ったのと同じものが受信できる」といった仮定をしてはいけません。
「送信したデータを1024バイト単位で受信する」必要があるのでしたら、受信側でもrecvしたデータを1024バイトになるまでバッファに蓄えてから処理するようにしてください。
お礼コメント
bony_cry

お礼率 33% (2/6)

細かく回答いただきありがとうございます。

パケットは誤った表現でした。申し訳ありません。

>TCP/IPでプログラムを組む場合「送信側で1024バイトずつ送っているから、受信側で1024バイトずつrecvすれば、ちょうど送ったのと同じものが受信できる」といった仮定をしてはいけません。

受信サイズは送信サイズと必ずしも一致しないということは把握していました。ですので、

例えばdata[1024]を送信して、
512バイトしか受信しなかった場合には、
次の受信時に(最高で)512バイト分を受け取る

という見解でした。
そのためwhileで受信サイズが0になるまでrecvすることで、
送信回数と受診回数はもちろん一致しませんが、
送られた分のデータを全て受信できると考えていたのですがこれは誤った考え方なのでしょうか。
よろしければお教えください。
投稿日時:2009/01/27 16:02
  • 回答No.1

ベストアンサー率 38% (96/252)

行っている通信は
同期モードですか?
非同期モードですか?
関連するQ&A

その他の関連するQ&Aをキーワードで探す

ページ先頭へ