こんにちはゲストさん。会員登録(無料)して質問・回答してみよう!

解決済みの質問

バイナリデータ受信時のデータ順

Winsockを用いてバイナリデータの送受信を行うプログラムを作成しました。

サイズは約4MBです。

データはすべて送受信できたようなのですが、バイナリエディタで確認したところ受信データがばらばらに入っているようなのです。完全にばらばらなのか、それとも塊ごとに後ろから順に入ってるのかまでは確認してません
受信方法としては

BYTE buff;
size_t fsize;
recv( Recv, (char *)buff, sizeof(size_t), 0 );
memmove( &fsize, buff, sizeof(size_t) );

size_t Total = 0;
BYTE *SubBuff = new BYTE[fsize*2];
BYTE *Buff = new BYTE[fsize];
while( Total < fsize )
{
int n = recv();
memmove( &Buff[Total], SubBuff, n );
Total += n;
}

としています。
どのようにすれば、正しい受信ができるのかわかりません。教えていただけないでしょうか?

投稿日時 - 2012-03-14 15:44:16

QNo.7361759

すぐに回答ほしいです

質問者が選んだベストアンサー

そ~ゆ~こってす>#9. で「ついでに」のバッファオーバーランが #5 の補足にある「配列が足りないからエラー」じゃないかとにらんでみたりする.

投稿日時 - 2012-03-15 14:49:05

補足

No.8~No.10まで纏めてここに書かせていただきます。
ということは、
while( Total < fsize )
{
 int n = recv( Recv, (char *)SubBuff, fsize, 0 );
 memmove( &Buff[Total], SubBuff, n );
 Total += n;
}
ではなく(recv関数の第3引数を変更)
 int n = recv( Recv, (char *)SubBuff, fsize-Total, 0 );
とすればいいのでしょうか。
ちょっとこれでやってきます。

結果がわかる前に回答していただいてあれば結果はそちらに、まだなければこちらのお礼部に書かせていただきます

投稿日時 - 2012-03-15 14:59:17

お礼

recv部を変えたところ、バイナリエディタで不一致点はありませんでした。
ただ、ファイル名がおかしなことになっているのと、
fopen→fwrite→fcloseで書き込み処理を行うとなぜかfcloseでエラーが出るようになったというのはありますが・・・・・・

そこらへんはもう一度自分で見直してみようと思います。


今後これを見る人のために最終的なプログラムをまとめてここに書いておきます。(fsize部などもBYTE使わなくてもできました。(なぜ不具合が起きていたんでしょう…・))


size_t fsize;
recv( Recv, (char *)&fsize, sizeof(size_t), 0 );

size_t Total = 0;
BYTE *Buff = new BYTE[fsize];

while( Total < fsize )
{
int n = recv( Recv, (char *)&Buff[Total], fsize-Total, 0 );
Total += n;
}

みなさんありがとうございます。
ベストアンサーをWr5さんかTacosanさんのどちらにするか迷ったのですが、最終的な結論をTacosanさんのところに書いたということでTacosanさんにさせていただきます。
またよろしくお願いします。

投稿日時 - 2012-03-15 16:09:55

ANo.10

このQ&Aは役に立ちましたか?

0人が「このQ&Aが役に立った」と投票しています

回答(10)

ANo.9

Wr5

>今, 1000バイト受け取る予定で recv を呼び出したら 50バイトもらえたとします. そのあとでまた「1000バイトくれ」ってのはおかしいと思いませんか?

おお……確かに。
同じソケットで他のデータもやりとりしていると…次のデータも一緒に取り込んでしまいますね。
# ついでにコピー先のバッファも飛び出すことになりますか…。コピー元は無事ですけど…。

1ファイル分の転送が済んだら切断。
とかなら……一応セーフでしょうか。
サイズ受け取っている意味が半減しますが。

投稿日時 - 2012-03-15 14:32:10

ANo.8

strcpy じゃないんだから, 「'\0' で終了」なんてことはあり得ないって.... memcpy と memmove の違いは「コピー元とコピー先が重複していてもいいかどうか」の 1点だけ.

あと, 「受け取る予定以上のサイズを受け取る」のところについてですが, あなたのプログラムで本当に
while( Total < fsize )
{
 int n = recv( Recv, (char *)SubBuff, fsize, 0 );
 memmove( &Buff[Total], SubBuff, n );
 Total += n;
}
としているのであれば, それは recv の不具合でもなんでもなく単に「あなたのプログラムがボケているだけ」です.

今, 1000バイト受け取る予定で recv を呼び出したら 50バイトもらえたとします. そのあとでまた「1000バイトくれ」ってのはおかしいと思いませんか?

投稿日時 - 2012-03-15 14:15:11

ANo.7

Wr5

>いえ。ファイル名の文字数は受信データ内に入っています。(No3の捕捉でいう12文字)
>最初に受診したのはバイト配列のサイズです。

了解です。
それなら問題はなさそうですね。

>しかし、下のリンク先を見ると私が使用してるのは2010EEなので使えないようですね

2008EEでは使えましたけどね。
今現在、2010EEが手元にないので確認出来ませんが。
# 自宅に帰らないと…。

投稿日時 - 2012-03-15 13:53:23

ANo.6

Wr5

>対象PCはWindowsのみで、XP以降対象(送信元はLinux)なのでエンディアンは大丈夫だと思いました。

まぁ、やってみて…ということになるでしょうね。
x86系同士なら問題ないとは思いますけど。

>>recv( Recv, (char *)&fsize, sizeof(size_t), 0 );
>>で直接受け取っても問題ないと思いますが。
>これでやってみたところ、値があり得ない数値になったのでソースの通りに変更しました。

size_t分受信出来なかった…とかそんなところでしょうかねぇ……。
ちょっと不明ですが。

>データ部の最初から、
>(BYTE配列のサイズ) - ( sizeof(size_t) ) - (ファイル名の文字数)
>分だけコピーすればいいかなと思ったのです。

その「BYTE配列のサイズ」はどこから?
掲示されたコードだとファイル名取得した時点でループ抜けていますが…。
最初に受信したサイズが「(ファイル名の文字数)」ですよね?
# まぁ、プロトコルのハナシになるのであまり踏み込むつもりはないですけど。
# そういう意味では…既存プロトコルを使うのが楽なんですけどねぇ。HTTPとか。

>受け取る予定のサイズと同じだと、どうも不具合(受け取る予定以上のサイズを受け取ること)があるみたいなのですが

recv()で受信するサイズ指定していますし、正しく処理すれば越えることはないのではないかと。
# メモリ確保出来ないなんてパターンもありますのでご注意を。
# 私が触ったPOP3受信のヤツでは、起動時にrecv()で一次受信する領域確保して以降は使い回しです。

>memcpyのほうが処理が速いのでしょうか?

気にする程の速度差は無い…と思われます。
ライブラリ作成者(Microsoft?)の方で最適化も掛かっているでしょうし。
# 人間からみたらどっちにしろ一瞬です。

ただ……memmove()でないと問題のある使い方をしているのか?? と、コードを読む人に要らぬ推測をさせる可能性がある。というところはあります。
# コードを読む人には、数ヶ月後の自分も含まれる場合があります。
## 「memmove()ってコトは…コピー元とコピー先の領域が重なる可能性があるってことだよね?でもココではそれぞれ動的確保しているから領域がかぶるコトはないハズ……それでもmemmove()を使う理由ってなんだろう??」
## みたいな思考を辿ることも。(というか、私がそうだったんですが…)

>やり方がわからないので調べてきます。
http://msdn.microsoft.com/ja-jp/library/5d2www5s%28v=vs.80%29.aspx
http://msdn.microsoft.com/ja-jp/library/s3aw423e.aspx
など。(VisualStudioの場合ですが。 バージョン違いは読み替えて下さいな)
ブレークポイントで止めてから覗くことになります。

投稿日時 - 2012-03-15 13:24:43

補足

>最初に受信したサイズが「(ファイル名の文字数)」ですよね?
いえ。ファイル名の文字数は受信データ内に入っています。(No3の捕捉でいう12文字)
最初に受診したのはバイト配列のサイズです。言葉がおかしいかな?
最大添え字数+1の値??

>コードを読む人に要らぬ推測をさせる可能性がある
なるほど。以降は必要ないときはmemcpyにするようにします。

>バージョン違いは読み替えて下さいな
わざわざすみません。ありがとうございます。しかし、下のリンク先を見ると私が使用してるのは2010EEなので使えないようですね

投稿日時 - 2012-03-15 13:38:14

ANo.5

それはそれでいくつか突っ込みどころが....

まず「値があり得ない数値になった」なんだけど,
recv( Recv, (char *)&fsize, sizeof(size_t), 0 );

recv( Recv, (char *)buff, sizeof(size_t), 0 );
memmove( &fsize, buff, sizeof(size_t) );
は本質的に同じなんだから, 上でダメなら下もダメなはず. そもそも「どのような数値を送ったらどのような数値になったのか」をきっちり把握してから「どう変更するか」を考えないと.

あと, このプログラムでは memmove も memcpy も同じです. ちょっとだけ memcpy の方が早いはず (2つの関数の違い, わかりますか?).

しかし, recv で「受け取る予定以上のサイズを受け取る」ってどんな状況だろう. 詳細がないからどういうことを言っているのかわからんけど, 明らかに盛大なバグだから当然最優先で直しているはず. つまり「プログラムでボケをかましていない限り問題は発生しえない」と考えられる. ま, それをおいたとしても, 「だったら SubBuff に追加すればいいだけ」 (つまり Buff の存在意義がない) と思えてしまう.

投稿日時 - 2012-03-15 12:56:22

補足

>まず「値があり得ない数値になった」なんだけど
そうなんですよね。一緒のはずなんですが、その時によって約4MB(細かい数値は忘れた)とすると0になったり、6MBぐらいの値が受信されたりでした。
分からないから取りあえず成功しているBYTE型で受け取って変更しとこうという考えでした。

>2つの関数の違い, わかりますか
確か、ナル文字があったらそこでcpyは終了。コピー先に被る部分があったときは未定義。moveはそこら辺の心配はないだったような・・・・・

>recv で「受け取る予定以上のサイズを受け取る」ってどんな状況だろう
なぜかはわからないのですが、
BYTE *SubBuff = new BYTE[fsize];
BYTE *Buff = new BYTE[fsize];
while( Total < fsize )
{
int n = recv(略);
memmove( &Buff[Total], SubBuff, n );
Total += n;
}
としたところ、配列が足りないからエラーと出まして・・・・・・・。
ならと、SubBuff[fsize+10]にしてみると大丈夫になったので
もう一度テストすると今度も足りない・・・・・なら+1024に
→大丈夫→再テスト→ダメ→*2→以降大丈夫
という経緯がありました。

投稿日時 - 2012-03-15 13:21:35

ANo.4

Wr5

う~ん…とりあえず、受信処理自体は大丈夫でしょうかねぇ。

とりあえず気になった点を。

BYTE buff[sizeof(size_t)];
size_t fsize;
recv( Recv, (char *)buff, sizeof(size_t), 0 );
memmove( &fsize, buff, sizeof(size_t) );

で、サイズを受信しているのですよね?
# エンディアンが気になるところですが…。

size_t fsize;
recv( Recv, (char *)&fsize, sizeof(size_t), 0 );
で直接受け取っても問題ないと思いますが。
# 送信側と受信側でエンディアンが揃っていることが前提…ですけど。
# htonl()/ntohl()などで明示的に処理する方がよいかと。
念のため、recv()の戻り値は確認しておいた方がいいでしょうけどね。

>送信するデータ形式は
>「12文字 "data\01.test" データの中身」
>としています。

「12文字」というのは、"data\01.test"のサイズのコトですよね?
# '\0'ナシみたいなので、文字列として扱う際には注意が必要ですが。
で…「データの中身」自体のサイズはどこに???
# ストリームなのでどこかでサイズの受け渡しとかしていないと……。

受信ループは…
while( Total < fsize )
{
 int n = recv( Recv, (char *)SubBuff, fsize, 0 );
 memmove( &Buff[Total], SubBuff, n );
 Total += n;
}
ですよね?
recv()の戻り値でのエラー処理がされているのであれば、問題はなさそうに見えますが…
# Totalとfsizeが正しいコト…という前提はありますけど。
SubBuffが事前に受け取っているサイズの倍も確保してあるのはなぜ?とか、
memmove()ではなくmemcpy()でも十分では?
とかはありますが…。

あとは…実際にファイルに書き出す処理に問題があった…という可能性も否定はできないところですね。
デバッグで受信したメモリの内容を確認はしてみましたか?
# 私なら、デバッグで覗く時の為に特定の値で埋めておく…なんてこともやることがありますね。
# 0x00や0xffだと判りにくいので他の値使いますが。

>recvは文字列を送信するためのものなので
文字列だけ…というワケではありません。
プロトタイプがchar *になっているからそう思ったのかも知れませんが。


あとは……WireSharkなどで実際に送受信しているデータが正しいか…も確認した方がいいかも知れません。
# デバッグでメモリ内容覗いて確認済みであれば、ソコまでは不要でしょうけど。

投稿日時 - 2012-03-15 09:56:18

補足

そういえば・・・・・とりあえずは同一PC内で(127.0.0.1を用いて)通信しています。
対象PCはWindowsのみで、XP以降対象(送信元はLinux)なのでエンディアンは大丈夫だと思いました。

>size_t fsize;
>recv( Recv, (char *)&fsize, sizeof(size_t), 0 );
>で直接受け取っても問題ないと思いますが。
これでやってみたところ、値があり得ない数値になったのでソースの通りに変更しました。

>…「データの中身」自体のサイズはどこに???
># ストリームなのでどこかでサイズの受け渡しとかしていないと……。
必要でしたか。
データ部の最初から、
(BYTE配列のサイズ) - ( sizeof(size_t) ) - (ファイル名の文字数)
分だけコピーすればいいかなと思ったのです。

>SubBuffが事前に受け取っているサイズの倍も確保してあるのはなぜ?とか、
>memmove()ではなくmemcpy()でも十分では?
受け取る予定のサイズと同じだと、どうも不具合(受け取る予定以上のサイズを受け取ること)があるみたいなのですが、どれくらいとっておけばいいかわからなかったので、2倍にしとけば大丈夫だろうと。
memmoveを使っておけば問題ないという認識でした^^;
memcpyのほうが処理が速いのでしょうか?

>デバッグで受信したメモリの内容を確認はしてみましたか?
やり方がわからないので調べてきます。

>プロトタイプがchar *になっているからそう思ったのかも知れませんが。
その通りです。

投稿日時 - 2012-03-15 11:41:56

ANo.3

もっと転送量が小さい, 例えば 16バイトとかでは試してみましたか?

あと, おそらく本題とは関係ないでしょうが非常に気になったので:
recv( Recv, (char *)buff, sizeof(size_t), 0 );
って, 何をしたいんですか? 「buff がさすメモリ領域に読み込む」というのは, かなり危険な感じがするのですが.

投稿日時 - 2012-03-15 02:06:25

補足

普通の文字列は送信できました。
文字列で送信したことあるのは
全て半角ですが、5、7、11、17文字です。

えっと、そもそもアップデートプログラムを作りたいです
更新するデータ部は毎回違うので、例えば"data\01.test"を更新したいなら
送信するデータ形式は
「12文字 "data\01.test" データの中身」
としています。
データはバイナリ形式で読み取っており、それらをBYTE配列に格納して送信しています。

recvは文字列を送信するためのものなのでキャストして、BYTE型で受け取り、受け取ったものを
ファイル名の文字数を確認
→&Buff[sizeof(size_t)]以降から文字数分char配列に格納。
→&Buff[sizeof(size_t)+文字数]以降から全てを他のBYTE配列に読み込んでファイルに書き込みとしています。

投稿日時 - 2012-03-15 07:42:18

ANo.2

Wr5

>BYTE buff;
>size_t fsize;
>recv( Recv, (char *)buff, sizeof(size_t), 0 );

sizeof(BYTE) >= sizeof(size_t)
の環境ですよね?
sizeof(BYTE) < sizeof(size_t)
な環境だとバッファオーバーフローしますが。
# 32BitなWindowsだと…sizeof(BYTE)は1、sizeof(size_t)は4になるかと思われますが…。
# アライメントの関係で、BYTE変数が一つだった場合は、後ろに3バイトの未使用領域がある…かも知れませんけどね。

あと、sizeof(size_t)分の受信が出来ていることは確認しているのですよね?
# よほどのコトがないかぎり、その程度のデータは1回で届くでしょうけど。(前回の受信データの後ろにくっついている場合も…)

>nt n = recv();
で、どのように受信しているのかが不明です。

socketのrecv()の戻り値を適切に使用している…のであれば、問題はないかと思われますが……
# 戻り値が0になる。とかいうパターンも考慮されてます?
# SOCKET_ERRORとかも。

投稿日時 - 2012-03-14 16:42:02

補足

あ、ごめんなさい。手元にプログラムが無い状態で手打ちしたので
抜けてしまいました。
sizeof(BYTE) >= sizeof(size_t)
です

受信バイト数は送信プログラムと同じ、かつ送信したいバイト数であることは確認できています。

ああ、recvも抜けてましたね。
recv( Recv, (char *)SubBuff, sizeof(size_t), 0 );

ここには書いていませんが(本題とは関係ないと思ったので)
エラー処理もしてあります

投稿日時 - 2012-03-15 07:12:49

お礼

書き間違いです。何度もすみません
BYTE buff[sizeof(size_t)]
recv( Recv, (char *)SubBuff, fsize, 0 );
です。補足で書き直した意味がなかったorz

投稿日時 - 2012-03-15 07:47:06

ANo.1

zwi

UDPだと順番通り届きませんので自分で番号を付与して後でソートするしかありません。ただ、UDP欠落する恐れもあるのでエラー再送処理も必要となります。
TCPなら順番通り欠落もなく届くはずです。

投稿日時 - 2012-03-14 16:07:24

補足

TCPです。
ごめんなさい。必要でしたね。

投稿日時 - 2012-03-15 07:47:50

あなたにオススメの質問