• ベストアンサー

WinsockAPIのrecvfromの受信データがおかしい

環境は WinXPSP2、VB6SP6です WinsockAPIでUDPのマルチキャスト通信で 定期的にrecvfromでデータを受信しています サーバの配信が止まってる時に recvfromをすると、以前に受信したデータが残っており 同じデータを受信し続けます、返り値はゼロではないです Cならばrecvfromの返り値にゼロが入るのですが・・・ どうすれば受信が終わった(正しく受信できない)ことを判ることができるでしょうか?

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

  • ベストアンサー
  • saitoha
  • ベストアンサー率81% (9/11)
回答No.5

>サーバ側のバッファサイズを1470にすることでエラーは出なくなりました >が、lngRetの返り値は常に1470に・・・ C側のバッファで、固定長配列のsizeof()をとっているからだとおもう strlen()をつかうか、std::string等の可変長コンテナをつかう

ackapapa
質問者

補足

ご指摘どおりサーバ側の処理で sendto ( sock, buf, sizeof ( buf ), 0, ( struct sockaddr * ) & addr, sizeof ( addr ) ); としておりこれを sendto ( sock, buf, strlen ( buf ), 0, ( struct sockaddr * ) & addr, sizeof ( addr ) ); とすることで希望通りの動作をすることができました 問題部分のほとんど全てを解決していただき本当にありがとうございます

その他の回答 (4)

  • saitoha
  • ベストアンサー率81% (9/11)
回答No.4

参考URLでErr.LastDLLErrorをつかえみたいなことをBob Butlerさんが言っていますがどうなんでしょう。 WSAGetLastErrorとGetLastErrorはリンクしてるっぽい(いつも同じ値を返してくる)ので、そのへんがVBの例外処理機構に引っかかってこないように内部的にSetLastErrorしてるとか(推測) ところで受信するたびにAsyncSelectしなおす必要あるんだろうか

参考URL:
http://forums.devx.com/showthread.php?t=37954
ackapapa
質問者

お礼

WSAEMSGSIZEですがUDPソケットの送信サイズ1470バイトを無視していたため起きていました。 サーバ側のバッファサイズを1470にすることでエラーは出なくなりました が、lngRetの返り値は常に1470に・・・ エラーが出なければ大きな問題ではないためReplaceを使い対処します いろいろとありがとうございます

ackapapa
質問者

補足

情報ありがとうございます さっそく情報を元に確認してみました --------------------------------------- lngRet = recvfrom(udpsock, ByVal buf, ByVal LenB(buf), 0, fromaddr, LenB(fromaddr)) If (lngRet > 0) Then LbDiscv.Caption = "UPnP機器を検出しました" ElseIf lngRet = SOCKET_ERROR Then Label1.Caption = Err.LastDllError & " : " & WSAGetLastError() End If --------------------------------------- でクライアントのみ実行するとLabel1には "10035 : 0" の文字が・・・ サーバ、クライアント両方を起動させると "10040 : 0" となりました。 10035はWSAEWOULDBLOCK 10040はWSAEMSGSIZE です、これで原因を知ることができました よくこのような情報を見つけられますね、すごいです あとはWSAEMSGSIZEの対処をすることでなんとか対応できそうです ありがとうございます AsyncSelectですが、起動してそのまま何もしなければ 送信にあわせ受信し続けるんですが、ウィンドウを移動させる テキストボックス内でメニューを出す ブレークで一時止めるなど、何かしらの動作をさせると 受信しなくなってしまうため、AsyncSelectを入れてます・・・ コレでもたまに受信しなくなるためタイマ内に入れている状態です ※変な仕様ですね・・・VB(6)は・・・

  • saitoha
  • ベストアンサー率81% (9/11)
回答No.3

>その状態でわざとrecvfromをすると返り値は相変わらず-1ですが >bufの中身は 0x00*512 文字分で返ってきました GetLastErrorで WSAEWOULDBLOCK が返っていたら -1 でも正常だと思います。 なぜならWSAAsyncSelectを呼んでソケットが非ブロッキングモード(戻り値をすぐに返すモード)になっているから。 受信データがないので、結果を返したくても返せず、関数は失敗と見なされるんじゃないでしょうか。 受信するまで制御を返すなというのであれば、ブロッキングモードを使用すべきです。 >前回(06/02)の時点では受信していたのですが・・・ このときのGetLastErrorは、ひょっとして 10014のWSAEFAULT だったんじゃないでしょうか? recvfromの最後のパラメータがSOCKADDRのバイト長に満たないと、たぶんそうなるとおもいます。 SOFTBANKのWinsock2.0本(初版)にはfromlenを初期化しなくていいとか書いてあって、それでハマったことがあります。

ackapapa
質問者

補足

今までのやり取りに関することでは常に recvfromの返り値は-1で GetLastErrorではゼロが返っており 受信エラーが起き原因は不明という状態でコレの原因が不明でした 現在わかってる範囲で関係ありそうなのはバッファサイズです 現在はCサーバのバッファサイズは4096、VBクライアントは512としていますがこれを Cのサーバのバッファサイズを char buf [ 64 ]; VBのクライアントのバッファサイズを Dim buf As String * 128 とし、recvfromをすると返り値は 64、サーバ側のバッファサイズが返ってきます もちろんサーバのバッファサイズ内(64以下)に収めたデータを送信しています おそらくVB側の受信データ取得サイズがバッファのサイズを越えているためrecvfromでエラーを吐くと思われますが GetLastErrorではゼロになっております

  • saitoha
  • ベストアンサー率81% (9/11)
回答No.2

recvfrom直前でのバッファ初期化忘れ? もし、Winsock内部のバッファが消えてないのなら、受信データがおかしいとかの以前に、FD_READが送られ続けてアプリがフリーズするはず。 あと気になったのは(bufのタイプがわからないのでアレですが)、LenよりLenBを使うべきかと。 こちらで実験したところ、stringのときはマルチバイトを含む場合も文字数をかえしてしまうし、内部形式がバイト配列のVariantだと半分の長さしか返してきませんでした。

ackapapa
質問者

お礼

さきほどサーバ、クライアントを起動した状態で サーバを停止させるとクライアント側のFD_READも停止しました その状態でわざとrecvfromをすると返り値は相変わらず-1ですが bufの中身は 0x00*512 文字分で返ってきました 一応これを未受信状態と判断しておきます しばらくコレで様子を見ます 前回(06/02)の時点では受信していたのですが・・・ おそらくソケットを使用してのデバッグ作業なので 途中でソケット自体ががおかしくなった為なのかもしれません・・・ 聞く前に再起動をするべきでしたね・・・すみません あとは一定時間受信できていなければ未接続と判定することで一応解決できそうです アドバイスありがとうございます 2,3日締め切らずに置いておきます 何かご意見ご指摘があればよろしくお願いします

ackapapa
質問者

補足

返事が遅れてすみません recvfromの前に初期化はしています 仰るとおり常にFD_READが起きていますがフリーズはせずDoEventsをかけた様な状態です。 VBの受信部分の詳細ですが ------------------------------------------------ Private Sub Data_Read() Dim lngRet As Long Dim portstr As String Dim addrstr As String Dim strErrMsg As String Dim buf As String * 512 Dim buf2 As String buf = "" lngRet = recvfrom(udpsock, ByVal buf, ByVal LenB(buf), 0, fromaddr, LenB(fromaddr)) buf2 = Replace(buf, Chr(0), "") Label3.Caption = buf2 If (lngRet > 0) Then   Label1.Caption = "接続されています" ElseIf lngRet = SOCKET_ERROR Then   LbDiscv.Caption = ""   If WSAGetLastError() = WSAEWOULDBLOCK Then     Label1.Caption = "未接続"   ElseIf WSAGetLastError() > 0 Then     strErrMsg = "send:" & strWSAErrorGet(WSAGetLastError())     closesocket udpsock     lngRet = WSACleanup()     GoTo exitSend:   End If Else   Exit Sub End If addrstr = CStr(fromaddr.sin_addr / &H1 Mod &H100) & "." & _      CStr(fromaddr.sin_addr / &H100 Mod &H100) & "." & _      CStr(fromaddr.sin_addr / &H10000 Mod &H100) & "." & _      CStr(fromaddr.sin_addr / &H1000000 Mod &H100) portstr = CStr(ntohs(fromaddr.sin_port)) Label2.Caption = "address of " & addrstr & " : " & portstr Call WSAAsyncSelect(udpsock, Text1.hWnd, &H100, FD_READ) Exit Sub exitSend:   On Error Resume Next   If strErrMsg <> "" Then   MsgBox strErrMsg, vbOKOnly + vbExclamation, App.Title   End If End Sub ------------------------------------------------ このような感じです 上記でbufを初期化はしていますが buf = "" としても内部は 0x00*512 文字分で埋まって LenB の返り値は512で recvfrom の返り値は常に -1 です おそらく String * 512 で宣言しているせいかと思われますがそうしないと受信できません ただの String で初期化し、LenB() をするとゼロが返るためゼロ文字受信することになってしまいます これも改善方法がわからない状態です アドバイスどおりLenからLenBに変えてみました

  • saitoha
  • ベストアンサー率81% (9/11)
回答No.1

自信ないですが、フラグがMSG_PEEK(0x2)になっているとか?

ackapapa
質問者

補足

ご返事ありがとうございます サーバ、クライアント共にフラグの設定はしていません Cサーバ側 ------------------------------------------------ sendto ( sock, buf, sizeof ( buf ), 0, ( struct sockaddr * ) & addr, sizeof ( addr ) ); /* サーバへデータと自分のアドレス情報を送信 */ ------------------------------------------------ VBクライアント側 ------------------------------------------------ recvfrom(udpsock, ByVal buf, ByVal Len(buf), 0, addr, Len(addr)) ------------------------------------------------ といった感じです。 特におかしそうな部分は無いと思うのですが・・・ 一応VB側の部分の詳細も書いておきます ------------------------------------------------ Call WSAStartup(&H101, musrStartup) udpsock = socket(AF_INET, SOCK_DGRAM, 0) addr.sin_family = AF_INET addr.sin_port = htons(10000) addr.sin_addr = INADDR_ANY Call bind(udpsock, addr, Len(addr)) Call WSAAsyncSelect(udpsock, TextBox1.hWnd, &H100, FD_READ) Call MemCopy(mreq, 0, Len(mreq)) mreq.imr_interface = INADDR_ANY mreq.imr_multiaddr = inet_addr("239.192.1.2") Call setsockopt(udpsock, IPPROTO_IP, IP_ADD_MEMBERSHIP, mreq, Len(mreq)) ------------------------------------------------ としています(上記はエラー処理の部分などは端折って書いています) TextBoxのKey_Downにハンドルを当て、反応があればrecvfromをしています コレに関してはタイマーでも同様の動作をします

関連するQ&A