System.IO.Pipelines 与“零拷贝”:在 .NET 打造高吞吐二进制 RPC

打造高吞吐二进制RPC的.NET实践

System.IO.Pipelines 与“零拷贝”:在 .NET 打造高吞吐二进制 RPC 🚀



0. TL;DR —— 为什么选 Pipelines 🎯

  • PipeReader.ReadAsync() 返回 ReadOnlySequence<byte>,天然支持跨段缓冲半帧,搭配 AdvanceTo(consumed, examined) 实现背压
  • SequenceReader<byte> 可以在不拷贝到托管数组的情况下解析协议字段;BinaryPrimitives 操作 Span/ReadOnlySpan 更高效。
  • Kestrel 暴露 BodyReader/BodyWriter,HTTP 形态也能享受 Pipes 的收益。
  • 配合内存池、写合并、并发限流,能在吞吐、延迟、分配三项上显著优于传统 Stream

1. 帧协议 📦

大端(网络序)固定头 8 字节

len:uint32   // 含头,总长度
type:uint16  // 0=Ping, 1=Echo, 2=Sum, 0xFFFF=Error
flags:uint16 // bit0=压缩; 其他保留
payload: len-8
  • Ping:空载
  • Echo:原样返回 payload(示例中演示第一段 Span 回声)
  • Sum:payload 为 N 个 int32(BE),返回 int32(BE)之和
  • Error:返回错误码/消息(演示版为简单文本)

2. 代码公共部分:帧编解码 🧑‍💻

src/Rpc.Protocol/Frame.cs

using System;
using System.Buffers;
using System.Buffers.Binary;
using System.IO.Pipelines;
using System.Runtime.CompilerServices;

namespace Rpc.Protocol;

public static class Frame
{
   
   
    public const int HeaderSize = 8;
    public const ushort TypePing  = 0;
    public const ushort TypeEcho  = 1;
    public const ushort TypeSum   = 2;
    public const ushort TypeError = 0xFFFF;

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool TryParseFrame(
        ref ReadOnlySequence<byte> buffer,
        out ushort type,
        out ushort flags,
        out ReadOnlySequence<byte> payload)
    {
   
   
        type = 0; flags = 0; payload = default;

        if (buffer.Length < HeaderSize) return false;

        Span<byte> header = stackalloc byte[HeaderSize];
        buffer.Slice(0, HeaderSize).CopyTo(header);

        uint len = BinaryPrimitives.ReadUInt32BigEndian(header);
        type  = BinaryPrimitives.ReadUInt16BigEndian(header.Slice(4));
        flags = BinaryPrimitives.ReadUInt16BigEndian(header.Slice(6));

        if (len < HeaderSize) throw new InvalidOperationException("Invalid length");
        if (buffer.Length < len) return false; // 半帧

        var frame = buffer.Slice(0, len);
        payload = frame.Slice(HeaderSize, len - HeaderSize);

        buffer = buffer.Slice(len);
        return true;
    }

    public static void WriteFrame(PipeWriter writer, ushort type, ushort flags, ReadOnlySpan<byte> payload)
    {
   
   
        int len = HeaderSize + payload.Length;
        Span<byte> span = writer.GetSpan(len);

        BinaryPrimitives.WriteUInt32BigEndian(span, (uint)len);
        BinaryPrimitives.WriteUInt16BigEndian(span.Slice(4), type);
        BinaryPrimitives.WriteUInt16BigEndian(span.Slice(6), flags);

        payload.CopyTo(span.Slice(HeaderSize));
        writer.Advance(len);
    }
}

要点

  • 解析时仅拷头部到栈;payload 始终是原始 ReadOnlySequence<byte> 的切片(零拷贝)。
  • 写入时一次性拿到足够 Span,减少 Advance/Flush 次数。
  • len下限校验防御异常输入。

3. Demo A:TCP + Pipelines 🌐

src/Rpc.TcpServer/Program.cs

using System.Buffers;
using System.Diagnostics;
using System.IO.Pipelines;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Threading.Channels;
using Rpc.Protocol;

var listener = new TcpListener(IPAddress.Loopback, 5001);
listener.Start();
Console.WriteLine("TCP RPC listening on 127.0.0.1:5001");

while (true)
{
   
   
    var client = await listener.AcceptTcpClientAsync();
    _ = Task.Run(() => Handle(client));
}

static async Task Handle(TcpClient client)
{
   
   
    const int MaxInFlight = 32;
    var workQueue = Channel.CreateBounded<(ushort type, ReadOnlySequence<byte> payload)>(new BoundedChannelOptions(MaxInFlight)
    {
   
   
        SingleReader = true,
        SingleWriter = true
    });

    using var _ = client;
    client.NoDelay = true;

    var stream = client.GetStream();
    var reader = PipeReader.Create(stream, new StreamPipeReaderOptions(bufferSize: 64 * 1024));
    var networkWriter = PipeWriter.Create(stream, new StreamPipeWriterOptions(MemoryPool<byte>.Shared, 64 * 1024, leaveOpen: true));

    var sendPipe = new Pipe(new PipeOptions(
        pool: MemoryPool<byte>.Shared,
        pauseWriterThreshold: 256 * 1024,
        
System.IO.Pipelines是Microsoft开发的用于处理高性能I/O操作的库。它提供了一种简化的方式来读写数据流,并且在处理大量数据时具有出色的性能。 串口通信是一种用于在计算机和外部设备之间传输数据的通信方式。通过串口,计算机可以各种外部设备进行通信,如传感器、机器人、打印机等。 System.IO.Pipelines库可以在串口通信中发挥重要作用。使用该库,我们可以通过创建一个Pipeline对象,来轻松处理从串口接收到的数据,并对接收和发送的数据进行高效的处理。 首先,我们可以使用System.IO.Pipelines.PipelineReader从串口中读取数据。通过调用ReadAsync方法,我们可以异步地读取串口中的数据,并将其放入到一个缓冲区中。然后,我们可以通过提供的Read方法来处理这些数据,比如解析、处理或存储。 其次,我们可以使用System.IO.Pipelines.PipelineWriter向串口发送数据。通过调用WriteAsync方法,我们可以异步地将数据写入到串口中。该方法会返回一个可用于链式编程的WritableBuffer对象,我们可以使用其提供的方法来构建数据流,并最终将数据发送到串口。 最后,System.IO.Pipelines库还提供了一些高级功能,以帮助我们更好地处理串口通信。例如,我们可以使用PipeScheduler来调度读写操作,以充分利用系统资源。我们还可以使用MemoryPoolOptions来自定义内存池的大小和数量,以适应不同的数据量和性能要求。 总之,System.IO.Pipelines是一个非常有用且高性能的库,可用于处理串口通信。它提供了简单易用的API,并具有出色的性能和灵活性,可以帮助我们更好地处理和管理串口数据的读写操作。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Kookoos

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值