UDPで受信しながらデータをファイルへ保存するには?


丈太郎  2009-01-21 03:19:01  No: 141338

VB2005&Windows XP(SP2)でプログラムを作成しています。

UDPで受信しながら受信データをファイルへ保存(受信データが到着する毎という訳ではなく、保存するタイミングは遅れても良い)することを考えています。ログファイルのイメージです。
現在の受信の処理は
BackgroundWorkerでUdpClientクラスのReceiveメソッドでデータ受信して、受信データをADODB.StreamオブジェクトのWriteメソッドでStreamに保存するということをしています。
それで、このStreamに保存したデータを取り出してファイルへ保存する処理をもう1つBackgroundWorkerを作成して、そこで動作させようと考えているのですが、ADODB.StreamのRead, Write位置はPositionで共通なのでADODB.Streamで受信しながらファイル保存は難しそうだと感じています。
そこで、受信しながらファイル保存が実現できそうな方法があれば教えていただけませんでしょうか?

ちなみに、ADODB.Streamを使ったのは簡単にバイト配列をRead, Writeできるということだけの目的で使用しています。もし他に方法があれば教えていただけると幸いです。


我龍院  2009-01-21 17:18:32  No: 141339

ちょっと考えすぎの様な気がしますね。
普通に別スレッドで受信してそのままStreamクラスでバイナリファイルを
書き込めば十分間に合うと思いますが。


丈太郎  2009-01-21 20:59:13  No: 141340

我龍院様ありがとうございます。
ReceiveしてFileStreamにバイナリ書込みするように変更してみました。
コードは以下のようにしました。

...
Public Class Form1
    ...
    Const TEST_FILE = "RcvData.bin"         
    Dim udp As New UdpClient(LocalPort)
    Dim remoteEP As New IPEndPoint(System.Net.IPAddress.Any, 0)
    Private fs As New FileStream(TEST_FILE, FileMode.Create)
    Private w As New BinaryWriter(fs)
    ...
    '受信スレッド
    Private Sub bgWorker1_DoWork(...) Handles bgWorker1.DoWork
        Do
            ReceiveData()
        Loop While (1)
    End Sub
    ...
    Private Sub ReceiveData()
        Dim rcvBytes As Byte() = udp.Receive(remoteEP)
        w.Write(rcvBytes)
    End Sub
End Class

上のコードを実行中、32767パケット送ったのですが、32600程度しかファイルへは保存できていませんでした。これは、保存中にパケットが来て、受信ができずに次のパケットで上書きされてパケットが少なくなったのだと推測しています。
これを避けるには、ファイル保存中にも受信できるようにする必要があると考えています。
もしコードが我龍院様が考えていたのと違っていましたら申し訳ないです。


魔界の仮面弁士  2009-01-21 23:14:53  No: 141341

> 保存するタイミングは遅れても良い)することを考えています。
試していませんが、受信と保存を分けるなら、.ReportProgress(0, rcvBytes) などとして、
追記保存処理をメインスレッド側の ProgressChanged に追い出してみては如何でしょう。

> ADODB.Streamを使ったのは簡単にバイト配列をRead, Writeできる
COM のマーシャリングが発生するので避けてください。
Marshal.ReleaseComObect する手間もかけねばなりませんし。

読み書き可能なバイナリストリームが目的なら、MemoryStream が使えるかと。

> コードは以下のようにしました。
DoWork の中で、フィールド変数にアクセスするのは避けてください。
e.Argument / e.Result へのアクセスであれば構いませんけれども。

> Do
>   ReceiveData()
> Loop While (1)

それだと、Option Strict On の時にエラーになりますよ。

無限ループを行うつもりであるならば、脱出条件は不要なので
  Do
    ReceiveData()
  Loop
のように記述してやれば OK です。

とはいえ、これでは何時までたっても RunWorkerCompleted になりませんよね。
もし、外部から bgWorker1.CancelAsync() された時にのみ終了とするのであれば、
脱出条件を「Until .CancellationPending」にすることができます。

> 上書きされてパケットが少なくなったのだと推測しています。
> これを避けるには、ファイル保存中にも受信できるようにする必要があると考えています。
推測が正しいか間違っているかの論議はともかく、受信と保存とを分けたいのであれば、
「一時保存用バッファ」と「書き込み用ストリーム」は分けねばならないでしょう。
(保存作業中にデータが追加された場合は、次回の書き込みに回さねばならないので)

今は rcvBytes が一時バッファになってはいますが、受信スレッドで同期書き込みを
行っているため、あまり意味が無いような気がします。先に書いたような方法で、
保存部分だけをメインスレッドに委ねるか、または BeginWrite などで、
保存処理を別のワーカースレッドに回してみてはいかがでしょう。


我龍院  2009-01-21 23:53:37  No: 141342

>32767パケット送ったのですが、32600程度しかファイルへは保存できていませんでした。
パケットで言われてもね、UDP通信と言うのはそんなものでは。

Frameworkには非同期ファイル I/Oが有りますよ。
http://msdn.microsoft.com/ja-jp/library/kztecsys(VS.80).aspx


オショウ  2009-01-22 10:52:46  No: 141343

>32767パケット送ったのですが、32600程度しかファイルへは保存できていませんでした。

※  回答ではありません。あしからず。

    そもそもUDPは、パケットが確実に相手に届く保障の無い
    プロトコルです。

    仮にローカルでは無くインターネット越しの場合だと、受信
    確認のACKパケット(仮称)を返信するかして届いたこと
    を相互に確認する必要があります。

    あくまでローカルで100%届く状態ならば、受信側のルーチ
    ンが取りこぼしてしまうような構造になっているのでしょう

    届く届かないを切り分けるならば、パケットモニターを導入
    して、まず受信側PCが100%受けていることを確認されれ
    ば如何でしょう。

    届いているならば、後はプログラム次第。

※  参考までに・・・

    http://dobon.net/vb/dotnet/internet/udpclient.html

以上。


丈太郎  2009-01-23 00:42:30  No: 141344

我龍院様、魔界の仮面弁士様ありがとうございます。

追記保存処理をメインスレッド側に追い出す考え方でコードを組んでみました。
(ReceiveDataの中でグローバル変数のudp,remoteEPのe経由の扱い方がわからなかったのでそのままにしています。また、一時バッファもrcvBytesのままにしています。)

Public Class Form1

    Const TEST_FILE As String = "RcvData.bin"
    Private j As Integer = 0
    Dim blReceiving As Boolean = False
    Dim LocalPort As Integer = 20000
    Dim udp As New UdpClient(LocalPort)
    Dim remoteEP As New IPEndPoint(System.Net.IPAddress.Any, 0)
    Private fs As New FileStream(TEST_FILE, FileMode.Create)

    '受信開始(受信開始ボタンクリック)
    Private Sub Button1_Click(...) Handles Button1.Click
        blReceiving = Not blReceiving

        If blReceiving = True Then
            Button1.Text = "受信停止"

            bgWorker1.RunWorkerAsync()
        Else
            Button1.Text = "受信開始"

            bgWorker1.CancelAsync()
        End If
    End Sub

    'データ受信スレッド
    Private Sub bgWorker1_DoWork(...) Handles bgWorker1.DoWork
        Dim worker As BackgroundWorker = CType(sender, BackgroundWorker)

        Do
            ReceiveData(worker)

        Loop Until worker.CancellationPending

    End Sub

    'データを受信する
    Private Sub ReceiveData(ByVal worker As BackgroundWorker)
        Static i As Integer = 0
        Dim rcvBytes As Byte() = udp.Receive(remoteEP)
        worker.ReportProgress(0, rcvBytes)
        i = i + 1
    End Sub

    Private Sub bgWorker1_ProgressChanged(...) Handles bgWorker1.ProgressChanged
        Dim saveBytes As Byte() = CType(e.UserState, Byte())
        fs.Write(saveBytes, 0, saveBytes.Length)
        j = j + 1
    End Sub
End Class
結果的には、受信できたパケット数はあまり変わりませんでした。
(気持ち上がったかなという程度)
.ReportProgress(0, rcvBytes)によって保存処理がメインスレッドに移ったのですぐに受信状態になりデータを受け取れると考えていたのですが。。。
i,jでReceiveData()と.ProgressChanged()の実行回数を比較したのですが同じでしたので、前と同じで受信が間に合ってないのかなと考えています。

次はFileStreamを使ってBeginWriteでファイル保存をしてみるつもりです。


丈太郎  2009-01-23 00:47:15  No: 141345

オショウ様ありがとうございます。

パケットモニタ(WireShark)で全(32767)パケット届いていることは確認できています。
後は、私のプログラムの出来次第です。。。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加