C# ソケット通信でデータ受信時の欠損について

このQ&Aのポイント
  • C#のSocket Classを使用してデータの送受信をするプログラムを作成していますが、非同期でデータを受信する際にデータの欠損が起きてしまう問題に悩んでいます。
  • 現在の方法では、socket.BeginReceiveで受信開始し、受信時のコールバック関数の中でデータとサイズを取得し、再度socket.BeginReceiveで受信を続けていますが、この方法だとデータの一部が取りこぼされる可能性があります。
  • 非同期でデータを受信する際に欠損なく受信する方法を知りたいです。別の方法などがあれば教えていただけると助かります。
回答を見る
  • ベストアンサー

C# ソケット通信でデータ受信時の欠損について

Visualstudio 2013 を使用して C# で開発を行っています。 Socket Classを使用してデータの送受信をするプログラムを作成しているのですが、 非同期でデータを受信する際に取りこぼしなくデータを受信させる方法で悩んでいます。 現在行っているのは、  socket.BeginReceiveでデータ受信時のコールバック関数を登録し受信開始。  データ受信時のコールバック関数の中で  len = socket.EndReceive(ar);  byte[] rcvBuff = (byte[])ar.AsyncState;  でデータとサイズを取得  再度socket.BeginReceiveで受信開始。 としています。 悩んでいるのは EndReceive から、二回目のBeginReceiveまでの間に送られたデータを取りこぼしてしまうのではないかということです。 認識として間違っていたら指摘頂きたいです。 また、別の方法で非同期で取りこぼしなく受信できるやり方が有りましたらヒントでもよいので教えていただけたらと思います。 よろしくお願いします。

  • fenri
  • お礼率50% (11/22)

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

  • ベストアンサー
  • anmochi
  • ベストアンサー率65% (1332/2045)
回答No.1

> 悩んでいるのは EndReceive から、 > 二回目のBeginReceiveまでの間に送られたデータを取りこぼしてしまうのではないかということです。 大丈夫よん。 プログラムがBeginReceive~EndReceiveでひとかたまりの受信データブロックを取得するのはあくまで「同じコンピューターのOSから」なので、その時「同じコンピューターのOS」は相手からのデータ送信をどんどこどんどこ受け付けている。 そもそも最初のBeginReceiveをしなくてもコネクションが確立された瞬間からOSは相手からデータを受け取っているのだ。 これは、次のようなイメージだ。 (1)あなたは一階に総合受付がある雑居ビルで仕事をしてる。(あなた=プログラム、総合受付=NIC) (2)一階の総合受付の中にあなた宛の郵便受けを置いたとする。(Socket#Listen、Socket#Accept) (3)郵便屋さんはそこにあなた宛の郵便をどんどこどんどこ持ってくる。 (4)あなたは部下に「郵便受けに何かないか見てこい。何もなかったら何か来るまでそこで見張ってろ。郵便が有ったら報告せよ。」という。(BeginReceive) (5)部下は降りていった時に郵便があればそのまま持って上がる。無ければ来るまで待つ。(コールバック) (6)あなたは部下から郵便を受け取って再び(4)を命じる。(EndReceive、AsyncState、BeginReceive) ここで、(3)と(4)~(6)は非同期にかつ独立して行われる。あなたや部下が郵便を取りに行こうが取りにいくまいが郵便屋さんは郵便を持ってきて便受けに入れるだけだ。具体的には(6)でEndReceiveとBegenReceiveの間に郵便屋さんが来たとしてもそれは便受けに郵便を入れるだけだ。 もちろん、あなたのプログラムが終了しようとしている時に郵便受けに溜まってるのにあなたが読まなかった郵便はプログラム終了(というかSocket#Close)とともにロストする。 TCPはこの郵便受けがぱんぱんになってしまったら郵便屋さん(というか郵便を送りつけてくる相手)にちょっと待って貰うとかそういう制御もしている。 イメージ的にはこんなイメージであなたはBeginReceiveとEndReceiveを実行するタイミングによる伝送データロストを気にする必要はない。もし本当にそんなちょっとしたタイミングの差でそんな事が起こるんならMicrosoftさんだってそんなコンポーネント作らんじゃろ。

fenri
質問者

お礼

ありがとうございます。 そのままでよさそうですね。 わかりやすいたとえで理解出来ました。

関連するQ&A

  • C# シリアル通信でデータ受信時の欠損について

    Visualstudio 2013 を使用して C# で開発を行っています。 SerialPort Classを使用してデータの送受信をするプログラムを作成しているのですが、 非同期でデータを受信する際にどうしてもうまくデータを取得出来ません。 5Byteのデータは正常に取得できるのですが、 その直後にくる40Byteのデータは、真ん中あたりの10数Byteや最後の10数Byteしか取れません。 serialPort.DataReceived に登録したイベント関数の中身です。 -------------------------------------------------------------------------------------- private void _serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e) { bytesRead = 0; // Initialize a buffer to hold the received data byte[] buffer = new byte[this.serialPort.ReadBufferSize]; try { bytesRead = this.serialPort.Read(buffer, 0, buffer.Length); if (true == serialPort.IsOpen) { serialPort.DiscardInBuffer();//受信バッファをクリアする } } catch (Exception ex) { DataLog.Exception(ex); } //派生クラス用の処理 DeviceClassEventArgs _DeviceClassEventArgs = new DeviceClassEventArgs(buffer, bytesRead); DeviceClassEvent(this, _DeviceClassEventArgs); } -------------------------------------------------------------------------------------- ネットの情報を参考に、 ReceivedBytesThreshold の値を期待するデータ量に逐一変えることで とりあえず正常に取ることが出来たのですが、これでいいのでしょうか? 期待するデータ量がわからなかった場合は使えないのかなとも思います。 データが欠損してしまう理由、 上記の対処法以外の一般的な対処法など有りましたら教えて下さい。 その他参考になるページ等ありましたら教えていただけると大変助かります。

  • Linux C言語 ソケット通信について

    C言語でソケット通信のプログラムを始めて作成しました。 受信時のデータ量が少ない場合は問題ないのですが、データ量が多くなると、 サーバーの受信が、出来たり、出来なかったりと不安定になります。 ログを確認しますと、パケットが勝手に分割されサーバーで受信した場合に、 おかしな動きになることがわかりました。 なぜそのようになるのか、また何がいけないのかわかりません。 どうぞご教授をお願いします。 FD_ZERO(&r_socket); FD_SET(sock, &r_socket); width = sock+1; *leng = 0; ef = TRUE; memset(&s,0x00,sizeof(s)); /* タイムアウト設定 */ time.tv_sec = 3; time.tv_usec = 0; while(1) { /* 受信待機 */ ret = select(width, &r_socket, NULL, NULL, &time) if ( ret < 0 ) { "受信待機エラー"表示 break; } if ( ret = 0 ) { "タイムアウトエラー"表示 break; } /* パケットを受信 */ if ( FD_ISSET(sock, &r_socket)) { memset(&rb,0x00,sizeof(rb)); read_len = read(sock, rb, sizeof(rb)); "受信した内容をログに出力" /* 割り込み中断エラーは継続し、その他エラーは終了する */ if ( read_len < 0 ) { if ( errno == EINTR ) { continue; } else { ef = FALSE; "受信エラー"表示 break; } } /* クライアントのソケットが閉じた場合は終了する */ if ( read_len == 0 ) { break; } /* データ送信要求を受信した場合は終了する */ if ( rb[read_len-1] == ACK ) { break; } /* 受信バッファーへ取り込み */ if ( rb[read_len-1] == ETX || rb[read_len-1] == ETB ) { len = read_len - 1; } else { len = read_len; } for (i = 0;i < len;i++) { if ( rb[i] != ETB ) { rsbuf[(*leng)++] = rb[i]; } } /* 最終データ時は終了する */ if ( rb[read_len-1] == ETX ) { break; } /* 最終データでない場合はデータ送信要求を送信する */ if ( rb[read_len-1] == ETB ) { sprintf(trxmsg,"%1c",ACK); ef = send_socket(sock, strlen(trxmsg), trxmsg); if ( ef != TRUE ) { break; } } } }

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

    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; } としています。 どのようにすれば、正しい受信ができるのかわかりません。教えていただけないでしょうか?

  • コールバック関数

    お世話になります。VB.NETにてソフト制作をしています。 今回、DLLの関数を利用する事になりましたが、うまく宣言できず困っています。御教授お願いします。 DLLはC++で作られており変更できません。 DLLは通信を補助するための物で、初期設定の関数と通信開始の物があります。 初期設定用を呼んだ後、通信開始を行なうようです。 説明には下記のような内容がありました。 【初期設定用】  initial(DWORD ip, LPNOTIFICATIONFUNC notificationFunc); ip=相手先のipアドレス  notificationFunc=コールバック関数へのポインタ コールバック関数    WINAPI *PNOTIFICATIONFUNC( DWORD id, BYTE bySet1, BYTE bySet2, BYTE bySet3, BYTE *data, DWORD datasize ); 【通信開始用】  start(DWORD ip BYTE *code ); ip=通信元のipアドレス code=相手のコードの格納アドレス 以上 全体的にどう宣言したら良いのか解りません。 (特にコールバック関数の宣言と、ポインタへの引数の渡し方) すみませんが、御教授お願いします。

  • ソケット通信

    http://www.microsoft.com/japan/msdn/vbasic/migration/tips/Socket/ 上記サイトのVB.NETのサンプルを動作させてみたところ 2バイト文字を送信すると 閉じ括弧”」”以降の文字が表示されません。 半角文字だとちゃんと表示されます。 プログラムの動作を追ったところ strReceivedData に入っているデータが 2バイト文字を使用すると 例・・・ああああ を送信したとき "ああああ というデータになっているようでその関係で表示が崩れるみたいです。 ためしに表示の直前で strReceivedData のデータを編集し "ああああ" とするとちゃんと閉じ括弧以降も表示されました。 どうして2バイト文字が入ると受信データの閉じの " が消えるのでしょうか?

  • コールバック関数 再び

    またまた、お世話になります。前回、御回答頂き動作するようにはなったのですが、コールバックに入力があった時に、アプリケーションが終了してしまう現象がおきています。御教授お願いします。 C++で作られたDLLを利用。 装置との通信を行なう物で初期設定用と通信開始用の 関数があり、コールバック関数で、装置からの送信もある。このDLLを使った、他のアプリで動作確認は取れているのでDLL自体には問題ないと思われる。 関数説明 【初期設定用】  initial(DWORD ip,LPNOTIFICATIONFUNC notificationFunc); ip=相手先のipアドレス notificationFunc=コールバック関数へのポインタ コールバック関数 WINAPI *PNOTIFICATIONFUNC(  DWORD id,  BYTE bySet1,  BYTE bySet2,  BYTE *data,  DWORD datasize); 以上の説明があり、現在下記のように宣言して使っています。 【初期設定用】 Declare Function MT_INITIALIZE Lib "TEST.dll" Alias "Initial" _  (ByVal ip As Integer, ByVal notificationFunc As CallBack) As Boolean Public Delegate Sub CallBack( _  ByVal id As Integer, _ ByVal byset1 As Byte, _ ByVal byset2 As Byte, _ ByVal data As IntPtr, _ ByVal dataSize As Integer) 【コールバック関数】 Public Shared Sub notificationFunc( _  ByVal id As Integer, _ ByVal byset1 As Byte, _ ByVal byset2 As Byte, _ ByVal data As IntPtr, _ ByVal dataSize As Integer )   //データのバッファ処理 End Sub

  • ソケット通信内 read関数について

    現在C言語にソケット通信を作成しているのですが、read関数内の作成でつまずいています。 ご存知でしたら教えてください。 自分なりに調べた結果read関数の戻り値は受信した バイト数、毎回指定サイズ受け取るとは限らないと いうことでした。 そこでread関数を無限ループで回し 指定サイズ受信したら、ループを抜ける使用にしたのですが、受信バイトが0だった場合はどうすればよいのでしょか? よくサンプルなどでは、0だったらエラーと判断し、すかさずbreakしているのですが、一度でも0バイトを受信すると、それ以降は1バイト以上受信出来ないものなのでしょうか? もし出来ないのなら一度でも0を受信したら、breakしようと思っています。 出来るのでしたら、タイマー設定をし、指定受信バイトに満たなくても一定時間が過ぎたらループから抜ける使用にしたいと考えています。 そのほか良い方法があれば教えてください。 分かりずらい説明になってしまいましたが、宜しくお願い致します。

  • シリアル通信プログラミングでのバイナリデータ送信

    UNIX系環境(IRIX)でのシリアル通信プログラムを作成していて、 バイナリデータの送信方法がわからず困っています。 write関数を使い、テキストデータの送信は出来ます。 write(fd,"テキスト",byte)のように。 ただ、今回はバイナリデータ送信を考えており、 例えば1byteのデータ00000001(01H)を送りたいと思っています。 このデータを送る場合、write関数で実現出来るのでしょうか? write(fd,0x01,1)←イメージです。 色々ネットで調べても出てきません。 開発がWindows環境ではないので、API関数が使えない状況で困っています(MsComm等が使えない)。また、fwrite関数は使用してはいけないみたいです。教えてください。宜しくお願いします。

  • ソケット通信時のエンディアン変換について

    現在、WindowsとLinux(Unix)でソケット通信を行い、データをやり取りするプログラムを作成しています。 ソースコードやコンパイルの環境は、 Windows側(Windows7):C言語(Windowsプログラミング)、VisualStudio2013でビルド&実行 Linux側:C++、g++(Cygwinを使用) 送信したいデータは、 Windows→Linuxはfloat型の配列に保持しているデータ Linux→Windowsはconst string型のデータ です。 (1)例として、送りたいfloatのデータが float a[3]; a[0] = 1.1; a[1]=2.2; a[2]=3.3 であるとします。(実際には負の値も考えられます) floatは4バイトなので、各要素でエンディアンを変えてa[0]からa[3]のデータを一括してLinux側にsendしたいと考えているのですが、どのように実装すればよいかが分かりません。 for(int i=0; i<3; i++){ //htonl(*(long*)&a[i]);でエンディアンを変換 //変換したものを何かしらの変数に保存 } //保存しておいたものをsend という大まかな流れだけは考えているのですが、実際どう実装していけばいいのか分からず困っています。 (2)(1)のデータを受信した側で元のfloatのデータに直す方法 (3)Linux→Windowsではstring型のデータを送りたいのですが、c_strを用いてchar型に変換したものをそのままsendしてよいのでしょうか? (char型は1バイトなのでエンディアンを変換する操作は必要はないでしょうか?) もし分かることがありましたら、教えていただけると助かります。 よろしくお願いします。

  • AccessでRS232Cからデータを受信したい

    Access2019を使用しています。 RS232C経由で、外部機器データを取り込みたいと思います。 スィッチがオンになったときとオフになったときのデータをその都度取り込みたいです。 通信方法も設定、受信方法などもわかりません。 サンプルプログラムなどありましたら教えてください。 よろしくお願いいたします。