Udp 顺序反应性扩展事件

Udp 顺序反应性扩展事件,udp,system.reactive,reactive-programming,Udp,System.reactive,Reactive Programming,我在多个线程中通过UDP接收消息。每次接收后,我都会发出MessageReceived.OnNext(message) 因为我使用了多个线程,所以消息引发的无序是一个问题 如何通过消息计数器命令消息的提升? (假设存在message.counter属性) 必须记住,一条消息可能会在通信中丢失(假设在X条消息之后有一个计数器孔,该孔未填满,我将发出下一条消息) 必须尽快发出消息(如果收到下一个计数器)在说明检测丢失消息的要求时,您没有考虑最后一条消息未到达的可能性;我已经添加了一个时间超时,它刷新

我在多个线程中通过UDP接收消息。每次接收后,我都会发出
MessageReceived.OnNext(message)

因为我使用了多个线程,所以消息引发的无序是一个问题

如何通过消息计数器命令消息的提升? (假设存在message.counter属性)

必须记住,一条消息可能会在通信中丢失(假设在X条消息之后有一个计数器孔,该孔未填满,我将发出下一条消息)


必须尽快发出消息(如果收到下一个计数器)

在说明检测丢失消息的要求时,您没有考虑最后一条消息未到达的可能性;我已经添加了一个<代码>时间超时,它刷新缓冲消息,如果在给定的时间内什么都没有到达,你可能会想考虑这个错误,看看如何做这件事的评论。 我将通过定义具有以下签名的扩展方法来解决此问题:

public static IObservable<TSource> Sort<TSource>(
    this IObservable<TSource> source,
    Func<TSource, int> keySelector,
    TimeSpan timeoutDuration = new TimeSpan(),
    int gapTolerance = 0)
安排超时 现在,如果请求超时,我们将用一个副本替换源,如果消息没有在
timeoutDuration
中到达,该副本将简单地终止并发送
OnCompleted

if(timeoutDuration != TimeSpan.Zero)
    source = source.Timeout(
        timeoutDuration,
        Observable.Empty<TSource>(),
        scheduler);
初始化一些变量 我们将在
nextKey
中跟踪下一个预期的键值,并创建一个
SortedDictionary
来保存无序消息,直到它们可以发送

int nextKey = 0;  
var buffer = new SortedDictionary<int, TSource>();
我们调用
keySelector
函数从消息中提取密钥:

var key = keySelector(x);
if(key == nextKey)
{
    nextKey++;
    o.OnNext(x);                    
}
如果消息有一个旧密钥(因为它超出了我们对无序消息的容忍度),我们将删除它并处理此消息(您可能希望采取不同的操作):

或者,我们可能有一个无序的未来消息,在这种情况下,我们必须将它添加到我们的缓冲区。如果我们这样做,我们还必须确保我们的缓冲区没有超出存储无序消息的容差-在这种情况下,我们还会将
nextKey
跳到缓冲区中的第一个键,因为它是
SortedDictionary
是下一个最低的键:

else if(key > nextKey)
{
    buffer.Add(key, x);
    if(gapTolerance != 0 && buffer.Count > gapTolerance)
        nextKey = buffer.First().Key;
}
现在,不管上面的结果如何,我们都需要清空缓冲区中现在准备就绪的所有键。为此,我们使用一个助手方法。请注意,它会调整
nextKey
,因此我们必须小心地通过引用传递它。我们只需在缓冲区上循环读取、删除和发送消息,只要键彼此连续,每次递增
nextKey

private static void SendNextConsecutiveKeys<TSource>(
    ref int nextKey,
    IObserver<TSource> observer,
    SortedDictionary<int, TSource> buffer)
{
    TSource x;
    while(buffer.TryGetValue(nextKey, out x))
    {
        buffer.Remove(nextKey);
        nextKey++;
        observer.OnNext(x);                        
    }
}
全面实施 下面是完整的实现

public static IObservable<TSource> Sort<TSource>(
    this IObservable<TSource> source,
    Func<TSource, int> keySelector,
    int gapTolerance = 0,
    TimeSpan timeoutDuration = new TimeSpan(),
    IScheduler scheduler = null)
{       
    scheduler = scheduler ?? Scheduler.Default;

    if(timeoutDuration != TimeSpan.Zero)
        source = source.Timeout(
            timeoutDuration,
            Observable.Empty<TSource>(),
            scheduler);

    return Observable.Create<TSource>(o => {
        int nextKey = 0;  
        var buffer = new SortedDictionary<int, TSource>();

        return source.Subscribe(x => {
            var key = keySelector(x);

            // drop stale keys
            if(key < nextKey) return;

            if(key == nextKey)
            {
                nextKey++;
                o.OnNext(x);                    
            }
            else if(key > nextKey)
            {
                buffer.Add(key, x);
                if(gapTolerance != 0 && buffer.Count > gapTolerance)
                    nextKey = buffer.First().Key;
            }
            SendNextConsecutiveKeys(ref nextKey, o, buffer);
        },
        o.OnError,
        () => {
            // empty buffer on completion
            foreach(var item in buffer)
                o.OnNext(item.Value);                
            o.OnCompleted();
        });
    });
}

private static void SendNextConsecutiveKeys<TSource>(
    ref int nextKey,
    IObserver<TSource> observer,
    SortedDictionary<int, TSource> buffer)
{
    TSource x;
    while(buffer.TryGetValue(nextKey, out x))
    {
        buffer.Remove(nextKey);
        nextKey++;
        observer.OnNext(x);                        
    }
}
闭幕词 这里有各种有趣的替代方法。我之所以选择这种基本上是强制性的方法,是因为我认为这是最容易遵循的——但可能有一些花哨的分组把戏可以用来实现这一点。关于Rx,我知道有一件事是一贯正确的——给猫剥皮的方法总是很多的


我对这里的超时概念也不太满意——在生产系统中,我想实现一些检查连接的方法,比如心跳或类似的方法。我没有涉及到这一点,因为它显然是特定于应用程序的。此外,心跳已经在这些委员会和其他地方讨论过了()),

< P>强烈地考虑使用TCP,如果你想要可靠的排序——那就是它的目的;否则,您将被迫使用UDP玩猜测游戏,有时您会出错

例如,假设您按以下顺序接收以下数据报:[A、B、D]

当您收到D时,在按下D之前,您应该等待C到达多长时间

无论你选择什么时间,你都可能是错的:

  • 如果C在传输过程中丢失了,它将永远不会到达呢
  • 如果您选择的持续时间太短,最后按D键,但收到C,该怎么办
  • 也许您可以选择一个启发式效果最好的持续时间,但为什么不直接使用TCP呢

    旁注:


    MessageReceived.OnNext
    表示您正在使用
    主题,这可能是不必要的。考虑将ASYNC代码> UDPcli客< /Cult>方法直接转换成可观测值,或者通过编写一个异步迭代器来转换它们,通过<代码>可观察到.CREATE(ASYNC(观察者,取消)= {{}})

    我曾经考虑过创建一个Rxx的
    OrderBy
    操作符的
    OrderByUntil
    变体,它本可以用类似的方式解决这个问题,但最终我觉得很奇怪。你认为这是值得实施的,还是仅仅是一个解决一个糟糕问题的好办法?请看我对你答案的评论!主要是后者:)我完全同意Dave关于TCP的观点——但排序问题很有趣,它偶尔也会有有效的应用程序——我在一个自定义Tibco消息传递场景中遇到了它,我们从多台机器收集消息+1 DaveWell,我绝对同意这很有趣,特别是因为Rx没有提供任何真正直接的方法来解决这个问题。尽管我认为类似于以下的东西可以工作:
    datagrams.Buffer(time).Scan(…).Select(…)
    尽管最后一个查询意味着所有通知都会延迟。看起来,
    Observable.Create
    实际上更简单。另外,不要忘了WCF的许多功能,它们在这方面很有帮助。例如
    if(key == nextKey)
    {
        nextKey++;
        o.OnNext(x);                    
    }
    
    else if(key > nextKey)
    {
        buffer.Add(key, x);
        if(gapTolerance != 0 && buffer.Count > gapTolerance)
            nextKey = buffer.First().Key;
    }
    
    private static void SendNextConsecutiveKeys<TSource>(
        ref int nextKey,
        IObserver<TSource> observer,
        SortedDictionary<int, TSource> buffer)
    {
        TSource x;
        while(buffer.TryGetValue(nextKey, out x))
        {
            buffer.Remove(nextKey);
            nextKey++;
            observer.OnNext(x);                        
        }
    }
    
    () => {
        // empty buffer on completion
        foreach(var item in buffer)
            o.OnNext(item.Value);                
        o.OnCompleted();
    });
    
    public static IObservable<TSource> Sort<TSource>(
        this IObservable<TSource> source,
        Func<TSource, int> keySelector,
        int gapTolerance = 0,
        TimeSpan timeoutDuration = new TimeSpan(),
        IScheduler scheduler = null)
    {       
        scheduler = scheduler ?? Scheduler.Default;
    
        if(timeoutDuration != TimeSpan.Zero)
            source = source.Timeout(
                timeoutDuration,
                Observable.Empty<TSource>(),
                scheduler);
    
        return Observable.Create<TSource>(o => {
            int nextKey = 0;  
            var buffer = new SortedDictionary<int, TSource>();
    
            return source.Subscribe(x => {
                var key = keySelector(x);
    
                // drop stale keys
                if(key < nextKey) return;
    
                if(key == nextKey)
                {
                    nextKey++;
                    o.OnNext(x);                    
                }
                else if(key > nextKey)
                {
                    buffer.Add(key, x);
                    if(gapTolerance != 0 && buffer.Count > gapTolerance)
                        nextKey = buffer.First().Key;
                }
                SendNextConsecutiveKeys(ref nextKey, o, buffer);
            },
            o.OnError,
            () => {
                // empty buffer on completion
                foreach(var item in buffer)
                    o.OnNext(item.Value);                
                o.OnCompleted();
            });
        });
    }
    
    private static void SendNextConsecutiveKeys<TSource>(
        ref int nextKey,
        IObserver<TSource> observer,
        SortedDictionary<int, TSource> buffer)
    {
        TSource x;
        while(buffer.TryGetValue(nextKey, out x))
        {
            buffer.Remove(nextKey);
            nextKey++;
            observer.OnNext(x);                        
        }
    }
    
    public static void Main()
    {
        var tests = new Tests();
        tests.Test();
    }
    
    public class Tests : ReactiveTest
    {
        public void Test()
        {
            var scheduler = new TestScheduler();
    
            var xs = scheduler.CreateColdObservable(
                OnNext(100, 0),
                OnNext(200, 2),
                OnNext(300, 1),
                OnNext(400, 4),
                OnNext(500, 5),
                OnNext(600, 3),
                OnNext(700, 7),
                OnNext(800, 8),
                OnNext(900, 9),            
                OnNext(1000, 6),
                OnNext(1100, 12),
                OnCompleted(1200, 0));
    
            //var results = scheduler.CreateObserver<int>();
    
            xs.Sort(
                keySelector: x => x,
                gapTolerance: 2,
                timeoutDuration: TimeSpan.FromTicks(200),
                scheduler: scheduler).Subscribe(Console.WriteLine);
    
            scheduler.Start();
        }
    }