C# 基于System.IO.Pipelines长度字段的TCP解码

C# 基于System.IO.Pipelines长度字段的TCP解码,c#,tcp,.net-core,C#,Tcp,.net Core,我正在尝试将代码从dotnety移植到System.IO.Pipelines。在DotNetty中,我利用LengthFieldBasedFrameDecoder对TCP消息进行解码,其中前两个字节表示一个整数,表示整个消息的长度 我看到的所有演示都依赖于基于字符串的EOL指示器。我觉得这应该很容易,但我不知道如何抓取前两个字节,然后抓取X个字节,如长度前缀所示 下面是一个来自David Fowler的例子。如果前两个字节表示消息大小,而不是表示消息结束的EOL字符,我如何重写此代码以解析消息

我正在尝试将代码从dotnety移植到System.IO.Pipelines。在DotNetty中,我利用LengthFieldBasedFrameDecoder对TCP消息进行解码,其中前两个字节表示一个整数,表示整个消息的长度

我看到的所有演示都依赖于基于字符串的EOL指示器。我觉得这应该很容易,但我不知道如何抓取前两个字节,然后抓取X个字节,如长度前缀所示

下面是一个来自David Fowler的例子。如果前两个字节表示消息大小,而不是表示消息结束的EOL字符,我如何重写此代码以解析消息

 private static async Task ReadPipeAsync(Socket socket, PipeReader reader)
    {
        while (true)
        {
            ReadResult result = await reader.ReadAsync();

            ReadOnlySequence<byte> buffer = result.Buffer;
            SequencePosition? position = null;

            do
            {
                // Find the EOL
                position = buffer.PositionOf((byte)'\n');

                if (position != null)
                {
                    var line = buffer.Slice(0, position.Value);
                    ProcessLine(socket, line);

                    // This is equivalent to position + 1
                    var next = buffer.GetPosition(1, position.Value);

                    // Skip what we've already processed including \n
                    buffer = buffer.Slice(next);
                }
            }
            while (position != null);

            // We sliced the buffer until no more data could be processed
            // Tell the PipeReader how much we consumed and how much we left to process
            reader.AdvanceTo(buffer.Start, buffer.End);

            if (result.IsCompleted)
            {
                break;
            }
        }

        reader.Complete();
    }
private静态异步任务ReadPipeAsync(套接字套接字,管道读取器)
{
while(true)
{
ReadResult result=wait reader.ReadAsync();
ReadOnlySequence缓冲区=结果缓冲区;
SequencePosition?位置=空;
做
{
//找到下线
position=buffer.PositionOf((字节)'\n');
如果(位置!=null)
{
var line=buffer.Slice(0,position.Value);
进程线(插座、线路);
//这相当于位置+1
var next=buffer.GetPosition(1,position.Value);
//跳过我们已处理的内容,包括\n
buffer=buffer.Slice(下一步);
}
}
while(位置!=null);
//我们对缓冲区进行切片,直到无法处理更多数据
//告诉管道阅读器我们消耗了多少,还有多少需要处理
reader.AdvanceTo(buffer.Start、buffer.End);
如果(结果已完成)
{
打破
}
}
reader.Complete();
}
是我最后得到的:

private const int lengthPrefixSize = 2; // number of bytes in the length prefix
private static ushort ParseLengthPrefix(ReadOnlySpan<byte> buffer) => BinaryPrimitives.ReadUInt16LittleEndian(buffer);

private static ushort ParseLengthPrefix(in ReadOnlySequence<byte> buffer)
{
    if (buffer.First.Length >= lengthPrefixSize)
        return ParseLengthPrefix(buffer.First.Span.Slice(0, lengthPrefixSize));

    Span<byte> lengthPrefixBytes = stackalloc byte[lengthPrefixSize];
    buffer.Slice(0, lengthPrefixSize).CopyTo(lengthPrefixBytes);
    return ParseLengthPrefix(lengthPrefixBytes);
}

private static async Task ReadPipeAsync(Socket socket, PipeReader reader)
{
    ushort? lengthPrefix = null;

    while (true)
    {
        ReadResult result = await reader.ReadAsync();

        ReadOnlySequence<byte> buffer = result.Buffer;

        while (true)
        {
            if (lengthPrefix == null)
            {
                // If we don't have enough for the length prefix, then wait for more data.
                if (buffer.Length < lengthPrefixSize)
                    break;

                // Read and parse the length prefix
                lengthPrefix = ParseLengthPrefix(buffer);
                buffer = buffer.Slice(lengthPrefixSize);
            }

            // If we haven't read the entire packet yet, then wait.
            if (buffer.Length < lengthPrefix.Value)
                break;

            // Read the data packet
            var line = buffer.Slice(0, lengthPrefix.Value);
            ProcessLine(socket, line);

            buffer = buffer.Slice(lengthPrefix.Value);
            lengthPrefix = null;
        }

        // We sliced the buffer until no more data could be processed
        // Tell the PipeReader how much we consumed and how much we left to process
        reader.AdvanceTo(buffer.Start, buffer.End);

        if (result.IsCompleted)
        {
            break;
        }
    }

    reader.Complete();
}
private const int lengthPrefixSize=2;//长度前缀中的字节数
私有静态ushort-ParseLengthPrefix(ReadOnlySpan缓冲区)=>BinaryPrimitives.ReadUInt16LittleEndian(缓冲区);
专用静态ushort ParseLengthPrefix(在只读序列缓冲区中)
{
if(buffer.First.Length>=lengthPrefixSize)
返回ParseLengthPrefix(buffer.First.Span.Slice(0,lengthPrefixSize));
Span lengthPrefixBytes=stackalloc字节[lengthPrefixSize];
Slice(0,lengthPrefixSize).CopyTo(lengthPrefixBytes);
返回ParseLengthPrefix(lengthPrefixBytes);
}
专用静态异步任务ReadPipeAsync(套接字、PipeReader阅读器)
{
ushort?lengthPrefix=null;
while(true)
{
ReadResult result=wait reader.ReadAsync();
ReadOnlySequence缓冲区=结果缓冲区;
while(true)
{
if(lengthPrefix==null)
{
//如果没有足够的长度前缀,请等待更多数据。
if(buffer.Length

此解决方案确实有一个长度前缀缓冲区,但仅当长度前缀跨跨度拆分时才使用。我认为有一种方法可以使这个拷贝完全减少,尽管在长度前缀的情况下(只有很少的字节,没有缓冲区分配),差异可能很小。

缓冲区拷贝!下面是GRPC是如何做到的,SequenceReader是根据Stephen的评论添加的。