如何使用.net中的RX扩展来使用UDP字节流

时间:2012-09-27 15:42:31

标签: .net vb.net udp system.reactive dispose

我想出了这个解决方案。 (尚未测试)通过网络上的大量弹跳。

Private Function ObserveUDP() As IObservable(Of bytes())


    Dim f = Function(observer)
                Dim endpoint = New IPEndPoint(IPAddress.Parse(Me.IpAdress), Me.IPPort)
                Dim client = New UdpClient(endpoint)

                Dim obs = observable.*emphasized text*Generate(Of Task(Of UdpReceiveResult), UdpReceiveResult) _
                      ( Nothing _
                      , Function(task As Task(Of UdpReceiveResult)) task Is Nothing Or Not task.IsCompleted() _
                      , Function(task As Task(Of UdpReceiveResult)) client.ReceiveAsync() _
                      , Function(task As Task(Of UdpReceiveResult)) task.Result)

                Dim observable = obs.Select(Function(r) r.Buffer)

                dim handle = observable.Subscribe(observer)

                Dim df = Sub() 
                    client.Close()
                    handle.Dispose()
                End Sub

                Return Disposable.Create(df)

    End Function

    Return observable.Create(f)

End Function

我的要求是确保在订阅被删除时关闭UDP客户端。我很确定上面的代码很接近,但我认为这不太对。任何意见都将不胜感激。

*编辑*

实际上上面的例子是完全错误的,只会同步创建大量的任务对象 但不等待他们。经过一些试验和错误后,我想出了以下通用函数 展开一个一次又一次地叫做的等待。有什么意见吗?

''' initializer - a function that initializes and returns the state object
''' generator   - a function that asynchronously using await generates each value
''' finalizer   - a function for cleaning up the state object when the sequence is unsubscribed

Private Function ObservableAsyncSeq(Of T, I)( _
    initializer As Func(Of I), _
    generator As Func(Of I, Task(Of T)), _
    finalizer As Action(Of I))  As IObservable(Of T)

    Dim q = Function(observer As IObserver(Of T))
                Dim go = True
                Try
                    Dim r = Async Sub()
                                Dim ii As I = initializer()
                                While go
                                    Dim result = Await generator(ii)
                                    observer.OnNext(result)
                                End While
                                finalizer(ii)
                                observer.OnCompleted()
                            End Sub
                    Task.Run(r)
                Catch ex As Exception
                    observer.OnError(ex)
                End Try

                ' Disposable for stopping the sequence as per
                ' the observable contract
                Return Sub() go = False

            End Function

    Return Observable.Create(q)
End Function

与UDP一起使用的例子

Private Function ObserveMeasurementPoints2() As IObservable(Of ProcessedDate)
    Dim initializer = Function()
                          Dim endpoint = New IPEndPoint(IPAddress.Parse(Me.IpAdress), Me.IPPort)
                          Return New UdpClient(endpoint)
                      End Function

    Dim finalizer = Function(client As UdpClient)
                        client.Close()
                    End Function

    Dim generator = Function(client As UdpClient) As Task(Of UdpReceiveResult)
                        Return client.ReceiveAsync()
                    End Function

    Return ObservableAsyncSeq(initializer, generator, finalizer).Select(Function(r) ProcessBytes(r.Buffer))

End Function

3 个答案:

答案 0 :(得分:4)

您可以使用Observable.Using作为提及的Enigmativity,或者只使用接受Observable.Create作为返回参数的常规IDisposable方法 - 这足以安全处置。

使用迭代器或异步非常好。我已经列出了更多Rx-ish方法:

Public Shared Function UdpStream(Of T)(endpoint As IPEndPoint, processor As Func(Of Byte(), T)) As IObservable(Of T)
    Return Observable.Using(Of T, UdpClient)(
        Function() New UdpClient(endpoint),
        Function(udpClient) _
            Observable.Defer(Function() udpClient.ReceiveAsync().ToObservable()) _
            .Repeat() _
            .Select(Function(result) processor(result.Buffer))
    )
End Function

传统方式:

Public Shared Function UdpStream(Of T)(endpoint As IPEndPoint, processor As Func(Of Byte(), T)) As IObservable(Of T)
    Return Observable.Using(
        Function() New UdpClient(endpoint),
        Function(udpClient) Observable.Defer( _
        Observable.FromAsyncPattern(
            AddressOf udpClient.BeginReceive,
            Function(iar)
                Dim remoteEp = TryCast(iar.AsyncState, IPEndPoint)
                Return udpClient.EndReceive(iar, remoteEp)
            End Function)
        ).Repeat() _
         .Select(processor)
    )
End Function

测试:

Shared Sub Main()
    Using UdpStream(New IPEndPoint(IPAddress.Loopback, 13200),
                    Function(bytes) String.Join(",", bytes)
                    ).Subscribe(AddressOf Console.WriteLine)
        Console.ReadLine()
    End Using

    Console.WriteLine("Done")
    Console.ReadKey()
End Sub

答案 1 :(得分:2)

查看Observable.Using - 它专门用于创建一个使用一次性资源生成其值的observable,完成后会自动处理资源。

您会发现UdpClient具有相同的Close& Dispose方法实施,因此如果您致电Close,则无需致电Dispose

来自反射器:

void IDisposable.Dispose()
{
    this.Dispose(true);
}

public void Close()
{
    this.Dispose(true);
}

以下是Using的签名:

Public Shared Function Using(Of TSource, TResource As IDisposable)(
    ByVal resourceFactory As Func(Of TResource),
    ByVal observableFactory As Func(Of TResource, IObservable(Of TSource)))
        As IObservable(Of TSource)

答案 2 :(得分:1)

之前我没有使用过UDPClient,但看起来您正在使用Tasks(Cardinality = 1)来尝试接收数据流(Cardinality = many)。似乎解决了这个问题,你已经对你的查询重复了一遍。这意味着您的查询将执行此操作

  1. 创建UDPClient
  2. 调用数据请求
  3. 收到第一批数据
  4. 推送序列上的数据
  5. 关闭序列
  6. 处理UDPClient
  7. 创建UDPClient(返回步骤1)
  8. 调用数据请求
  9. 收到第一批数据
  10. ....直到你处理连接。
  11. 我认为您应该能够通过拉入字节流来读取套接字/网络连接。我在博客文章中向您展示了如何做到这一点:

    http://introtorx.com/Content/v1.0.10621.0/15_SchedulingAndThreading.html#CreatingYourOwnIterator

    通过这种方式,您只需打开一个连接,并在收到字节时按下它们。

    快速谷歌我也发现有人担心UDPClient实施的可靠性。 http://www.codeproject.com/Articles/1938/Issues-with-UdpClient-Receive

    HTH

    using System;
    using System.IO;
    using System.Reactive.Concurrency;
    using System.Reactive.Disposables;
    using System.Reactive.Linq;
    
    namespace MyLib
    {
        public static class ObservableExtensions
        {
            //TODO: Could potentially upgrade to using tasks/Await-LC
            public static IObservable<byte> ToObservable(
                this Stream source,
                int buffersize,
                IScheduler scheduler)
            {
                var bytes = Observable.Create<byte>(o =>
                {
                    var initialState = new StreamReaderState(source, buffersize);
                    var currentStateSubscription = new SerialDisposable();
                    Action<StreamReaderState, Action<StreamReaderState>> iterator =
                    (state, self) =>
                        currentStateSubscription.Disposable = state.ReadNext()
                            .Subscribe(
                                bytesRead =>
                                {
                                    for (int i = 0; i < bytesRead; i++)
                                    {
                                        o.OnNext(state.Buffer[i]);
                                    }
                                    if (bytesRead > 0)
                                        self(state);
                                    else
                                        o.OnCompleted();
                                },
                                o.OnError);
                    var scheduledWork = scheduler.Schedule(initialState, iterator);
                    return new CompositeDisposable(currentStateSubscription, scheduledWork);
                });
                return Observable.Using(() => source, _ => bytes);
            }
    
            private sealed class StreamReaderState
            {
                private readonly int _bufferSize;
                private readonly Func<byte[], int, int, IObservable<int>> _factory;
                public StreamReaderState(Stream source, int bufferSize)
                {
                    _bufferSize = bufferSize;
                    _factory = Observable.FromAsyncPattern<byte[], int, int, int>(
                    source.BeginRead,
                    source.EndRead);
                    Buffer = new byte[bufferSize];
                }
                public IObservable<int> ReadNext()
                {
                    return _factory(Buffer, 0, _bufferSize);
                }
                public byte[] Buffer { get; set; }
            }
        }
    }