CAsyncSocket::OnReceive()の使い方

[戻る]
NOR 2013/02/05(火) 17:05:27
http://msdn.microsoft.com/ja-jp/library/vstudio/wfttx1af.aspx
上記のMSDNに書かれているCAsyncSocket::OnReceive()のサンプルソースを見ると、
Receive()で受け取った文字列がきっちり"bye"という3文字ならそこで終了となっている
のですが、
この"bye"が、"by"と"e"というように、2回のOnReceive()に分かれたり、
逆に直前の送信内容と一緒に"hogehogebye"などと送られてきたりすることはないので
しょうか。

このへんの受信の方法はお決まりのやり方があるのではと思っているのですが、
なかなか疑問を感じないサンプルが見つけられずに困っています。

しま 2013/02/05(火) 17:28:52
TCP 上に流れるのは1バイトづつだらだと流れるのではなく、パケット単位なので、
若し、 hogehogebye と受信するとしたら同一パケット中に
hogehoge と bye とがあったということになる筈です。

受信側もパケット単位にまとめて受信します。送信側が故意に by と e とに分割して
送信しない限り、bye が by と e とに分離することは無い筈です。

パケット内に、送信したデーターが入っているので、送信データが受信側で
再生できる訳です。

tetrapod 2013/02/05(火) 18:58:44
> パケット単位なので、
物理層の話でなく、データグラムの話であれば、そんなことはない=
いつどんなふうに分割されるかは事前に予測することは不可能.
by と e に分割される可能性は常にあります。
hogehogebye になる可能性も常にあります。
なので提示サンプルはあまりよくないですな。

http://www.kt.rim.or.jp/~ksk/wskfaq-ja/articles/lame-list.html
の20番

NOR 2013/02/06(水) 09:36:35
> by と e に分割される可能性は常にあります。
> hogehogebye になる可能性も常にあります。
> なので提示サンプルはあまりよくないですな。

となると、このMSDNのサンプルに書かれているような、
「クライアントが"bye"とだけ送ってきたら」みたいな判定は
不可能ということになりますよね?

また、クライアントが一度に送ったデータに対して
OnReceive()が何度も呼ばれる可能性があり、
「クライアントが一度に送ったデータを受信し終えたか」を判断するためには、
長さや終端文字などの書式を決めないと実現不可能ということでしょうか?

tetrapod 2013/02/07(木) 18:04:09
そのとおり

TCP はデータの到達や順序を保証するけど
到達までの遅延時間や「パケット性」は保証しないので、
> 長さや終端文字などの書式を決めないと実現不可能
ということになる。

「パケット性」が重要なら UDP を使うほうが良いかもしれない。
その場合、到達保証や順序保証はないけどね

NOR 2013/02/12(火) 16:10:35
解説ありがとうございます。
自分なりに納得したいと思っているのですが、

http://support.microsoft.com/kb/185728/ja
上記のページに書かれているMfcsocs.exeがダウンロードできないのですが、
サンプルの実行手順を見ると、

> exe1 または exe2 で通信を終了するには、
> いずれかのインスタンスで「bye」と入力し、[Send] をクリックします

と書かれていて、前述のCAsyncSocket::OnReceive()のサンプルは
ここから持ってきたのだろうと思われます。

だとすると、「2つの誤りを回避するサンプル」と言っておいて、
なぜMFCの開発者自身がこのようなサンプルを書くのか、よく理解できません。

・3文字くらいなら、分かれたりくっついたりせずに一度のOnReceive()で来るだろう、と
いう手抜き?
・WSAEWOULDBLOCKの仕組みが、実はその部分を吸収できる?

もしよろしければ、詳しい方の見解を聞かせていただけませんでしょうか。

また、もっと正しいと思われるCAsyncSocketのサンプルが書かれている
サイトや本などをご存じではありませんでしょうか。

紅' 2013/02/13(水) 00:06:04
別に詳しくはないけど、興味があるので。
間違ってたら指摘をお願いします。

このスレの質問の趣旨って以下の通り?
・ソケットメッセージが分割されて受信することはあるか
 → 答え:ある

で、Microsoft の 185728 の「誤りの回避」ってのは以下の通りかな。
・OnReceive の中で何度も Receive を使うな
・OnSend で送信するときには送信しきるに必要な分だけちゃんと Send を呼べ

だとすると、185728 はなにも問題ないと思う。
なぜなら、メッセージが分割されて受信するとすると
OnReceive がその回数だけ呼ばれることになるけれど、
それは OnReceive の中で Receive を何度も実行することとは無関係だから。

で、サンプルがああな理由は、
・あれはあくまでも CSocket::OnReceive の使い方の説明であって
・ソケットの受信処理とメッセージの分割受信への対策は別物であり、
・サンプルとして実行する環境では分割受信はあまり発生しない物と思われ
 かつ、分割受信があったとしても大した害はないから
じゃないかと。

ちなみに自分が VC でソケットプログラムを書いてたころって、
MFC のクラスは色々アレだから使わない方がいいってふいんき(ryが主流だった
記憶があるのだけど、最近は違うのかな。
実際、send、recv、select で自作した方が小回りが利いて楽だった物だけれど。

もともとの仕様から考えて、たぶんお望みの「サンプル」なんてないよ。
プログラムの仕様面を含めて自力で頑張った方がきっと早い。

tetrapod 2013/02/13(水) 09:31:49
御意。
およそ「サンプル」ってのは、説明したいことに重点を置いて書くものである。
厳密なコードは説明すべき焦点をぼやけさせるのでむしろ有害。
(その結果、嘘や不具合を含んでいてもそれは「方便」である、と。)

俺も Microsoft のあのサンプルは「説明したい内容」に特化しているだけだと思う。
説明したいこと= OnReceive 中では1回だけ Receive しろ。

実用に供するコードとサンプルは違う、ってこと。

あと CSocket は使うな、も御意。俺は絶対使わない。

仲澤@失業者 2013/02/13(水) 10:00:40
まず、

 1.バークレーソケット(=WinSock2)のrecv()関数
 2.CAsyncSocket::Receive()関数

は、極端に言えば無関係な2つの関数であると判定します。

TCPの受信と言うのは、一般に1.のことを言い、この場合
送信側が一度に送信したデータが2回以上のrecv()で受信されることは
ありえます。

次に、2.はMFCのクラスのメンバであり、名称上は非同期となっていますが、
どのように実装されているかは、実は「不明」です。

マニュアルには、内部的にrecv()が使われているのかWSARecv()関数が
使われているのかも明示されていません。また、複数回の受信結果を
結合していない。とも書かれていません。

まぁ、内容の類似性から察するにWSARecv()関数が使われているだろう
との予測はできます。また、WSARecv()関数の説明には、recv()が使われている
ことが明記されています。

以上のことから、CAsyncSocket::OnReceive()の実装例については、

 3.一概に誤りだとは判定できない(判断材料が足りない)。

と言えるのではないでしょうか。

個人的には、CAsyncSocket及びその派生クラスであるCSocketは
この様に仕様があやふやなので使うことはありません。

ITO 2013/02/13(水) 14:49:03
うーーん、
僕は、印刷機能のときにMFCを使ってうまくいかなかった時にWIN32SDKを使いました。
今回のスレもうまくいかないのなら、WIN32SDKを使えばいいのでは?
いろいろ手間がかかる面もありますが、手順どおり作ればうまくいくと思います。

NOR 2013/02/18(月) 16:53:04
[[解決]]
みなさんご意見ありがとうございます。
お礼が遅れてしまって申し訳ありません。

> http://www.kt.rim.or.jp/~ksk/wskfaq-ja/articles/lame-list.html
> 理由: ストリームソケット(TCP)は、どうしてストリームソケットと呼 ばれるかというと、
> えーと、それはデータストリームを提供するからで す(ああ、もう!)。

の部分は、なんとなく理解できる説明でした。
要は、電話やラジオのように連続データが流れてくるだけで、
終わりのタイミングを決めるのはその媒体(TCP)を使う側ということですよね。
たしかに、TCPのヘッダには全体の長さの情報はありませんでした。

そのためには、送るデータのヘッダなどに「長さ」を仕込んでおき、
最低限そのヘッダの固定サイズ分を読み終えた上で初めて、
クライアントが一度に送ったデータの長さを知ることができ、
当初の「OnReceive()が何度も呼ばれる可能性」に対しては、
今回のMSDNのCAsyncSocket::OnReceive()に書かれているように、
受け取ったデータをメンバ(m_strRecv)に結合していけばよいと解釈しました。

ちなみに、このMSDNのサンプルも、"byeが送られてきたら"ではなく、
改行文字が出てくるごとにm_strRecvから取り出して、
「最新の1行が"bye"だったら」などのようにすれば、
よりよい終わり判定だったということですよね。

> ちなみに自分が VC でソケットプログラムを書いてたころって、
> MFC のクラスは色々アレだから使わない方がいいってふいんき(ryが主流だった
> 記憶があるのだけど、最近は違うのかな。

自分も今になって初めて、既存のMFCプログラムにTCP/IPの通信が必要になり、
WinSockの知識ゼロの状態で必死に調べているところなのですが、
MFC全盛期の頃ならともかく、今となってはろくに資料が無くて困っています。

同期式のCSocketが好ましくないのは、昔から聞いたことがあります。

とはいっても、CAsyncSocketに関しては、
このOnReceive()を正しく実装できれば最低限使えそうなので、
バークレーソケットやWinSockを生で使う方法に関しては、
一からTCP/IP通信アプリを作ることがあるときにじっくり勉強し直したいと思います。

古いスレッドには追加発言できません。