具有可变计数的Rx缓冲

时间:2018-08-03 16:35:44

标签: reactive-programming system.reactive reactive

我有一个IObservable<byte>想要分解为IObservable<byte[]>,大概是结合使用Buffer(),Window(),Scan()等。

但是,我很难找到合适的Rx函数组成来处理我的特定情况。关于该主题的大多数问答均以答案结尾,您可以通过测试以查看项目(字节/字符)是否是定界符,并以此方式拆分缓冲区。我的问题是,不仅仅是一个字节是分隔符。在我的情况下,我正在读取4个字节(即长度),然后要从后面的数据中缓冲该数量作为返回字节[]。

我尝试的一种方法是我可以制作一个IObservable<int>来表示数据包的长度,其他一些用户可以用它来将其分解为缓冲的byte [] s。也许像这样:

IObservable<int> lengthsObservable = byteObservable
    .Buffer(4)
    .Select((b) => BitConverter.ToInt32(b.ToArray(), 0))
    ...

但这有一个问题,就是我不确定在进行int转换后如何插入逻辑以跳过数据。如何缓冲4,转换为int,跳过该数量,然后重复缓冲(4),然后继续?

我自欺欺人地试图用API编写一些解决方案,但是没有真正的运气。我的核心选择是使用一个非常自定义的累加器类创建一个非常古怪的Scan()调用,但是我觉得有一种更好的犹太洁食方式。

TLDR Rx退伍军人是否知道使用不只是一个单位的定界符到Buffer()的常见构图模式?

编辑: 该问题的一个更具体的示例是将IObservable<byte>与输出分开:

0B 00 00 00 48 65 6C 6C 6F 20 57 6F 72 6C 64 12 00 00 00 54 68 61 6E 6B 73 20 66 6F 72 20 68 65 6C 70 69 6E 67

并将其处理为具有两个数组输出的IObservable<byte[]>

48 65 6C 6C 6F 20 57 6F 72 6C 64 // Hello world

54 68 61 6E 6B 73 20 66 6F 72 20 68 65 6C 70 69 6E 67 // Thanks for helping

开头的0B 00 00 00是其后的字节块的长度。它们之后紧接着是另一个长度12 00 00 00,带有另一个字节块。

2 个答案:

答案 0 :(得分:1)

这是“用非常自定义的累加器类进行.Scan()调用”的示例。我认为那不是那么糟糕。至少它能很好地抽象出来:

class AccumulatorState
{
    public AccumulatorState()
        : this(4, 0, true, new byte[0])
    {}

    public AccumulatorState(int bytesToConsume, int bytesConsumed, bool isHeaderState, IEnumerable<byte> accumulatedBytes)
    {
        this.TotalBytesToConsume = bytesToConsume;
        this.BytesConsumed = bytesConsumed;
        this.IsHeaderState = isHeaderState;
        this.AccumulatedBytes = accumulatedBytes;
    }

    public int TotalBytesToConsume { get; }
    public int BytesConsumed { get; }
    public bool IsHeaderState { get; }
    public bool IsComplete => BytesConsumed == TotalBytesToConsume;
    public IEnumerable<byte> AccumulatedBytes { get; }

    public AccumulatorState ConsumeByteToNewState(byte b)
    {
        if(IsComplete)
        {
            if (IsHeaderState)
                return new AccumulatorState(ConvertHeaderToByteCount(AccumulatedBytes), 1, false, new[] { b });
            else
                return new AccumulatorState(4, 1, true, new[] { b });
        }

        return new AccumulatorState(TotalBytesToConsume, BytesConsumed + 1, IsHeaderState, AccumulatedBytes.Concat(new[] { b }));
    }

    private int ConvertHeaderToByteCount(IEnumerable<byte> bytes)
    {
        return bytes
            .Select(b => (int)b)
            .Reverse()
            .Aggregate(0, (total, i) => total * 16 + i);
    }
}

然后您可以这样称呼它:

var bytes = new byte[] { 0x0B, 0x00, 0x00, 0x00, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x57, 
                         0x6F, 0x72, 0x6C, 0x64, 0x12, 0x00, 0x00, 0x00, 0x54, 0x68, 0x61, 
                         0x6E, 0x6B, 0x73, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x68, 0x65, 0x6C,
                         0x70, 0x69, 0x6E, 0x67 };
bytes.ToObservable()
    .Scan (new AccumulatorState(), (state, b) => state.ConsumeByteToNewState(b))
    .Where(s => s.IsComplete && !s.IsHeaderState)
    .Select(s => s.AccumulatedBytes.ToArray())
    .Dump(); //Linqpad. Switch to a .Subscribe(a => {}) if you're using something else.

这将输出带有两个输出的IObservable<byte[]>

48 65 6C 6C 6F 20 57 6F 72 6C 64 
54 68 61 6E 6B 73 20 66 6F 72 20 68 65 6C 70 69 6E 67 

答案 1 :(得分:0)

我想到了另一种解决方案,尽管我更喜欢基于.Scan的解决方案:

public static class X
{
    public static IObservable<byte[]> RecurseHeaders(this IObservable<byte> source)
    {
        return source.Publish(_bytes => _bytes
                .Take(4)
                .ToArray()
                .Select(a => ConvertHeaderToByteCount(a))
                .SelectMany(count => count == 0
                    ? Observable.Empty<byte[]>()
                    : _bytes.Take(count).ToArray().Concat(_bytes.RecurseHeaders())
                )
            );
    }

    private static int ConvertHeaderToByteCount(IEnumerable<byte> bytes)
    {
        return bytes
            .Select(b => (int)b)
            .Reverse()
            .Aggregate(0, (total, i) => total * 16 + i);
    }

}

然后调用它很简单:

var bytes = new byte[] { 0x0B, 0x00, 0x00, 0x00, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x57, 
                         0x6F, 0x72, 0x6C, 0x64, 0x12, 0x00, 0x00, 0x00, 0x54, 0x68, 0x61, 
                         0x6E, 0x6B, 0x73, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x68, 0x65, 0x6C,
                         0x70, 0x69, 0x6E, 0x67 };

var result = bytes.ToObservable()
    .RecurseHeaders();

这与Scan细分并重新实现非常接近。您可以按以下方式实现Scan

public static class RxReimplementations
{
    //This is a functional implementation of Observable.Scan
    public static IObservable<TState> FunctionalScan<TSource, TState>(this IObservable<TSource> source, TState initialState, Func<TState, TSource, TState> f)
    {
        return source.Publish(_source => _source
            .Take(1)
            .SelectMany(item => f(initialState, item)
                .Using(newState => Observable.Return(newState)
                    .Concat(_source.FunctionalScan(newState, f))
                )
            )
        );
    }

    //A functional way to re-use a function result.
    public static U Using<T, U>(this T t, Func<T, U> f)
    {
        return f(t);
    }
}