You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将Protobuf反序列化为自定义对象?gRPC流场景优化

gRPC流数据传输优化方案分析

问题背景

我正在研究gRPC的流数据传输实现,需要从gRPC服务器向客户端发送流数据。已定义greet.proto如下:

message protoTagValueDouble {
        int64 TagId = 1;
         google.protobuf.Timestamp DateTime = 2;
         google.protobuf.Timestamp DateTimeUtc = 3;
         google.protobuf.Timestamp TimeStampUtc = 4;
         int32 OpcQuality = 5;
         string Tagname = 6;
         double Value =7;
}

message Response {
    repeated protoTagValueDouble items = 1;
}

服务端与客户端均使用标记了[ProtoContract]的自定义TagValueDouble类(更新于2024.02.15):

[ProtoContract] 
public class TagValueDouble
{
    [ProtoMember(1)]
    public long TagId { get; set; }

    [ProtoMember(2)]
    public DateTime? DateTime { get; set; }

    [ProtoMember(3)]
    public DateTime? DateTimeUtc { get; set; }

    [ProtoMember(4)]
    public DateTime? TimeStampUtc { get; set; }

    [ProtoMember(5)]
    public int OpcQuality { get; set; }

    [ProtoMember(6)]
    public string Tagname { get; set; }

    [ProtoMember(7)]
    public double? Value { get; set; }
}

目前通过New()逐个将Protobuf消息映射为TagValueDouble对象,虽能正常运行但性能不足,希望采用更高效的序列化/反序列化方式实现Response到List<TagValueDouble>的转换。客户端尝试了如下写法:

await foreach (var response in serverData.ResponseStream.ReadAllAsync(stoppingToken))
{
    var bytes = response.ToByteArray();
    using var stream = new MemoryStream(bytes);
    {
        var x = Serializer.Deserialize<List<TagValueDouble>>(stream);
    }
}

但不清楚服务端该如何对应实现,想确认此思路是否可行,或是方案存在错误?

方案分析与修正

你的思路存在几个关键问题,直接这样实现会失败:

  • 序列化格式不匹配:客户端用Serializer.Deserialize<List<TagValueDouble>>是protobuf-net的序列化格式,但服务端发送的是gRPC自动生成的Response对象(基于Google.Protobuf序列化),两者结构完全不同,直接转换会导致反序列化失败。
  • 冗余操作拖慢性能:客户端已经拿到gRPC自动反序列化后的Response对象,再调用ToByteArray()重新序列化、再用protobuf-net反序列化,完全是多此一举,反而会降低性能。

正确的优化方向

方向一:直接复用gRPC生成的类

如果追求性能,最直接的方式是放弃自定义TagValueDouble类,直接使用gRPC根据.proto生成的protoTagValueDouble和Response类。gRPC代码生成器已做过大量性能优化,序列化/反序列化效率远高于手动映射。

方向二:让自定义类与Protobuf定义兼容

如果必须使用自定义TagValueDouble类,需确保它的protobuf-net标记与.proto定义完全对齐,然后调整gRPC服务定义直接传输目标类型:

  1. 修改.proto,将响应改为流形式的单个数据对象:
service GreetService {
  rpc StreamData (Empty) returns (stream protoTagValueDouble);
}
  1. 配置gRPC使用protobuf-net作为序列化器,服务端直接发送TagValueDouble对象:
public async Task StreamData(Empty request, IServerStreamWriter<TagValueDouble> responseStream, ServerCallContext context)
{
    while (!context.CancellationToken.IsCancellationRequested)
    {
        var dataList = GetTagValueDoubleList(); // 获取待发送数据
        foreach (var item in dataList)
        {
            await responseStream.WriteAsync(item);
        }
        await Task.Delay(1000, context.CancellationToken);
    }
}
  1. 客户端直接读取流中的TagValueDouble对象,无需额外序列化操作:
await foreach (var item in client.StreamDataAsync(new Empty(), cancellationToken: stoppingToken))
{
    // 直接使用item
}

方向三:优化手动映射性能

如果以上方式不可行,可通过以下手段优化手动映射:

  • 使用对象池复用TagValueDouble实例,减少GC开销
  • 手动编写映射代码或用表达式树生成映射逻辑,替代反射工具
  • 批量处理protoTagValueDouble对象,减少循环次数

内容的提问来源于stack exchange,提问作者Roman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 02:35:54