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できるということだけの目的で使用しています。もし他に方法があれば教えていただけると幸いです。
ちょっと考えすぎの様な気がしますね。
普通に別スレッドで受信してそのままStreamクラスでバイナリファイルを
書き込めば十分間に合うと思いますが。
我龍院様ありがとうございます。
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程度しかファイルへは保存できていませんでした。これは、保存中にパケットが来て、受信ができずに次のパケットで上書きされてパケットが少なくなったのだと推測しています。
これを避けるには、ファイル保存中にも受信できるようにする必要があると考えています。
もしコードが我龍院様が考えていたのと違っていましたら申し訳ないです。
> 保存するタイミングは遅れても良い)することを考えています。
試していませんが、受信と保存を分けるなら、.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 などで、
保存処理を別のワーカースレッドに回してみてはいかがでしょう。
>32767パケット送ったのですが、32600程度しかファイルへは保存できていませんでした。
パケットで言われてもね、UDP通信と言うのはそんなものでは。
Frameworkには非同期ファイル I/Oが有りますよ。
http://msdn.microsoft.com/ja-jp/library/kztecsys(VS.80).aspx
>32767パケット送ったのですが、32600程度しかファイルへは保存できていませんでした。
※ 回答ではありません。あしからず。
そもそもUDPは、パケットが確実に相手に届く保障の無い
プロトコルです。
仮にローカルでは無くインターネット越しの場合だと、受信
確認のACKパケット(仮称)を返信するかして届いたこと
を相互に確認する必要があります。
あくまでローカルで100%届く状態ならば、受信側のルーチ
ンが取りこぼしてしまうような構造になっているのでしょう
届く届かないを切り分けるならば、パケットモニターを導入
して、まず受信側PCが100%受けていることを確認されれ
ば如何でしょう。
届いているならば、後はプログラム次第。
※ 参考までに・・・
http://dobon.net/vb/dotnet/internet/udpclient.html
以上。
我龍院様、魔界の仮面弁士様ありがとうございます。
追記保存処理をメインスレッド側に追い出す考え方でコードを組んでみました。
(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でファイル保存をしてみるつもりです。
オショウ様ありがとうございます。
パケットモニタ(WireShark)で全(32767)パケット届いていることは確認できています。
後は、私のプログラムの出来次第です。。。
ツイート | ![]() |