protobuf-net 和重复字段

Posted

技术标签:

【中文标题】protobuf-net 和重复字段【英文标题】:protobuf-net and repeated field 【发布时间】:2012-06-18 09:27:17 【问题描述】:

在 protobuf 中使用 List 作为重复字段的对应项是否正确?我正在尝试这个并且总是遇到异常:

线型无效;这通常意味着您在没有截断或设置长度的情况下覆盖了文件;见Using Protobuf-net, I suddenly got an exception about an unknown wire-type

整个缓冲区是(第一个消息,下一个到达的都附加到这个): 9 8 5 26 5 24 238 98 32 1

java protobuf 文件:

package XXX;

option java_package = "XXX";
option java_outer_classname = "Protos";

option optimize_for = SPEED;

message V3DDelta 
  optional int32 bid = 1;
  optional int32 bidSize = 2;
  optional int32 ask = 3;
  optional int32 askSize = 4;


message Request 
  optional int32 type = 1;
  optional string request = 2;


message Response 
  optional int32 type = 1;
  optional string response = 2;
  repeated V3DDelta v3dDelta = 3;

和 protbuf-net 类:

[ProtoContract]
public class V3DDelta 
    [ProtoMember(1)]
    public double bid  get; set; 
    [ProtoMember(2)]
    public int bidSize  get; set; 
    [ProtoMember(3)]
    public double ask  get; set; 
    [ProtoMember(4)]
    public int askSize  get; set; 


[ProtoContract]
public class Request 
    [ProtoMember(1)]
    public int Type  get; set; 
    [ProtoMember(2)]
    public string Rq  get; set; 


[ProtoContract]
public class Response 
    [ProtoMember(1)]
    public int Type  get; set; 
    [ProtoMember(2)]
    public string Rsp  get; set; 
    [ProtoMember(3)]
    public List<V3DDelta> v3dDelta  get; set; 
    public Response() 
        v3dDelta = new List<V3DDelta>();
    

我试过 V3DDelta[] 但结果是一样的。 阅读留言:

Response rsp = Serializer.DeserializeWithLengthPrefix<Response>(rcvstream, PrefixStyle.Base128);

并且在 java 中使用 writeDelimitedTo 发送消息。 c# 中的缓冲区与 java 中的缓冲区完全相同。 当有 na v3dDelta 字段时,一切都按预期工作。

【问题讨论】:

一切顺利......这很有趣! 【参考方案1】:

是的,List&lt;T&gt; 或数组 (T[]) 都适用于 repeated。顺便说一句,有一个工具可以从 .proto 定义中生成 protobuf-net 类。

您正在尝试“使用长度前缀”读取它,但是:9 无效作为 varint 前缀(9,作为字段标题,表示“字段 1,固定的 64 位数据”,但是:在这种情况下,它应该是一个 varint)。

实际上,没有您的数据与 9 作为字段标头兼容,因为您在定义为字段 1 时没有任何 64 位值。您确实有一个double 作为字段3,它可以很好地完成这项工作 - 但是,它将73 作为字段标题。

我会告诉你序列 9,8,5,... 代表什么,但是 - 我们会:

9 : field 1, fixed 64-bit
    8 5 26 5 24 238 98 32 <== payload for above
1 : field 0, fixed 64-bit
    ^^ not *really* valid, but fields <= 0 generally mean "stop" - but
       frankly this is not a clean/defined/expected exit condition

再说一遍:请检查您的数据。这看起来不像一个 protobuf 流,或者至少不是一个与您的架构匹配的流。


编辑:可能 java writeDelimitedTo 包含 只是 长度,没有标题)使其在技术上不是一致的 protobuf 文件,但是......嗯),所以让我们调查一下:

int len = ProtoReader.DirectReadVarintInt32(ms);

这给了我们9,我们还剩下9字节,所以看起来不错......

8 : Field 1, varint
  5 = payload of above
26 : Field 3, length-delimited
  5 = length of payload
   24 238 98 32 1 = payload of ^^^
      24 : Field 3, varint
          238, 98 = payload of ^^^ = 12654
      32 : Field 4, varint
          1 = payload of ^^^ = 1

现在看起来它应该解析...调查为什么它不是...


编辑 2:经过更多调试,部分是因为您(很抱歉)破坏了 V3DDelta 属性。您在 proto 中将它们定义为 int32(并且字段 3 是数据中的 varint),但您将它们实现为 double... 和 doubleint32 不是朋友。

所以:

[ProtoContract]
public class V3DDelta

    [ProtoMember(1)]
    public int bid  get; set; 
    [ProtoMember(2)]
    public int bidSize  get; set; 
    [ProtoMember(3)]
    public int ask  get; set; 
    [ProtoMember(4)]
    public int askSize  get; set; 

然后以下工作正常:

using (var ms = new MemoryStream(buffer))

    int len = ProtoReader.DirectReadVarintInt32(ms);
    var resp = (Response)model.Deserialize(ms, null, typeof(Response), len);

    Assert.AreEqual(5, resp.Type);
    Assert.AreEqual(1, resp.v3dDelta.Count);
    Assert.AreEqual(12654, resp.v3dDelta[0].ask);
    Assert.AreEqual(1, resp.v3dDelta[0].askSize);

从技术上讲,我可以让 protobuf-net 接受 varint 以获得 double 值,但这高度表明架构并不真正匹配,所以我认为正确在这种情况下,事情是改变类型。

【讨论】:

是的,就是这样,现在它可以正常工作了。我只是在 proto 中更改字段类型后忘记了更改字段类型。谢谢您,很抱歉占用您的时间。

以上是关于protobuf-net 和重复字段的主要内容,如果未能解决你的问题,请参考以下文章

使用protobuf-net继承时如何选择字段号?

protobuf-net 保留未来字段

protobuf-net 缺少可选字段的 has_ 函数?

Protobuf-net / NetCore2:反序列化忽略带注释的私有字段

使用 protobuf-net 反序列化具有某些字段的派生类型的对象

如何通过 ProtoBuf-net 使用 DateTimeKind 选项序列化 DateTime 字段