【问题标题】:Using Protobuf-net, I suddenly got an exception about an unknown wire-type使用 Protobuf-net,我突然得到一个关于未知电线类型的异常
【发布时间】:2011-01-10 07:29:56
【问题描述】:

(这是我在我的 RSS 中看到的一个问题的重新发布,但被 OP 删除了。我重新添加了它,因为我在不同的地方看到这个问题被问了好几次;wiki对于“好形式”)

突然,我在反序列化时收到ProtoException,消息是:unknown wire-type 6

  • 什么是线型?
  • 有哪些不同的电线类型值及其描述?
  • 我怀疑是某个字段导致了问题,如何调试?

【问题讨论】:

  • Google 图书馆的异常消息向我发送了这里。挺好笑的……
  • @Zach 实际上是谷歌图书馆吗?我知道我从 protobuf-net 链接,但那不是谷歌。它是谷歌协议的独立实现。
  • 大概就是这样~谢谢
  • 在您进一步查看之前很容易检查:两端是否使用匹配类?我的不是,就我而言,因为 我不小心对服务器进行了错误的调用

标签: c# protobuf-net


【解决方案1】:

首先要检查:

输入数据是 PROTOBUF 数据吗?如果您尝试解析另一种格式(json、xml、csv、二进制格式化程序),或者只是破坏数据(例如,“内部服务器错误”html 占位符文本页面),那么 它将无法工作强>。


什么是线型?

它是一个 3 位标志,它告诉它(从广义上讲;它毕竟只有 3 位)下一个数据是什么样的。

protocol buffers 中的每个字段都有一个标题前缀,告诉它它代表哪个字段(数字), 以及接下来会出现什么类型的数据;这个“什么类型的数据”对于支持以下情况至关重要 意外数据在流中(例如,您已在数据类型的一端添加了字段),如 它让序列化程序知道如何读取过去的数据(或在需要时将其存储以进行往返)。

有哪些不同的线型值及其描述?

  • 0:变体长度整数(最多 64 位)- base-128 编码,MSB 表示延续(用作整数类型的默认值,包括枚举)
  • 1:64 位 - 8 字节数据(用于double,或选择性用于long/ulong
  • 2:长度前缀——首先使用变长编码读取一个整数;这告诉您后面有多少字节的数据(用于字符串、byte[]、“打包”数组,并作为子对象属性/列表的默认值)
  • 3:“开始组”——一种使用开始/结束标签对子对象进行编码的替代机制——在很大程度上被 Google 弃用了,跳过整个子对象字段的成本更高,因为你不能只是“寻找”经过一个意想不到的物体
  • 4:“端组” - 与 3 结对
  • 5:32 位 - 4 字节数据(用于float,或选择性用于int/uint 和其他小整数类型)

我怀疑是某个字段导致了问题,如何调试?

您是否要序列化到文件? 最有可能的原因(根据我的经验)是您覆盖了现有文件,但没有截断它;即它 200字节;你已经重写了它,但只有 182 个字节。现在,流的末尾有 18 个字节的垃圾导致它出错。重写协议缓冲区时必须截断文件。你可以通过FileMode 做到这一点:

using(var file = new FileStream(path, FileMode.Truncate)) {
    // write
}

或者SetLength写入你的数据之后:

file.SetLength(file.Position);

其他可能的原因

您正在(意外地)将流反序列化为与序列化不同的类型。值得仔细检查对话的双方以确保不会发生这种情况。

【讨论】:

  • @Nigel 我对规范非常熟悉;但是,此处描述的问题与不修剪文件导致的意外损坏有关。
  • +1 Nigel,将 protobuf-net 的创建者带入 protobuf 规范 :),就像一个老板!我得到了例外,但它与文件无关。这是因为我在客户端更新了一个 protobuffed 对象,却忘记发布到 Web 服务器。 (不确定这是否值得添加到可能的原因中)
  • @Joe 不应导致此错误;出乎意料的字段不是问题 - 字段标题(实际上,特别是线类型)包含足够的信息,可以跳过未知字段而无需理解它。
  • @MarcGravell 只是跟进您的 cmets。当发送方使用类class Foo { DateTime OccurredOn { get; set; } } 而接收方有class Foo { long OccurredOn { get; set; } } 时,我收到了这个异常。注意对象类型之间的区别:DateTimelong。不确定您是否要解决这个问题,但这支持@Joe 的发现。我使用 protobuf-net 2.0.0.668。我通过在两端使用相同的类型来修复它(这很有意义),但这种情况下的异常与文件无关。
【解决方案2】:

由于堆栈跟踪引用了这个 StackOverflow 问题,我想我会指出,如果您(意外)将流反序列化为与序列化不同的类型,您也会收到此异常。因此,值得仔细检查对话的双方以确保不会发生这种情况。

【讨论】:

  • 公平点 - 可能 - 不,绝对 - 首先要检查。
  • 是的,这就是我不小心做的——更改了现有字段的类型。好吧,这是一个非常明显的错误,但我仍然花了几分钟才意识到我做错了什么(需要检查一些 proto 文件和东西的差异)。有没有办法获得完整的反序列化日志?它可以很快让我知道在哪里寻找损坏的字段。
【解决方案3】:

这也可能是由于尝试将多个 protobuf 消息写入单个流而导致的。解决方法是使用 SerializeWithLengthPrefix 和 DeserializeWithLengthPrefix。


为什么会这样:

protobuf 规范支持相当少量的连线类型(二进制存储格式)和数据类型(.NET 等数据类型)。此外,这不是 1:1,也不是 1:many 或 many:1 - 单个线型可用于多种数据类型,并且单个数据类型可以通过多种线型中的任何一种进行编码.因此,除非您已经知道 scema,否则您无法完全理解 protobuf 片段,因此您知道如何解释每个值。例如,当您读取 Int32 数据类型时,支持的线类型可能是“varint”、“fixed32”和“fixed64”,而在读取 String 数据类型时,唯一支持的线型是“字符串”。

如果data-type和wire-type之间没有兼容的映射,则无法读取数据,报此错误。

现在让我们看看为什么会出现这种情况:

[ProtoContract]
public class Data1
{
    [ProtoMember(1, IsRequired=true)]
    public int A { get; set; }
}

[ProtoContract]
public class Data2
{
    [ProtoMember(1, IsRequired = true)]
    public string B { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        var d1 = new Data1 { A = 1};
        var d2 = new Data2 { B = "Hello" };
        var ms = new MemoryStream();
        Serializer.Serialize(ms, d1); 
        Serializer.Serialize(ms, d2);
        ms.Position = 0;
        var d3 = Serializer.Deserialize<Data1>(ms); // This will fail
        var d4 = Serializer.Deserialize<Data2>(ms);
        Console.WriteLine("{0} {1}", d3, d4);
    }
}

在上面,两条消息是直接写在彼此之后的。复杂之处在于:protobuf 是一种可附加的格式,附加的意思是“合并”。 protobuf 消息不知道自己的长度,因此读取消息的默认方式是:读取直到 EOF。但是,这里我们附加了两个 不同的 类型。如果我们读回来,它不知道我们什么时候读完第一条消息,所以它一直在读。当它从第二条消息中获取数据时,我们发现自己正在读取“字符串”线型,但我们仍在尝试填充 Data1 实例,其中成员 1 是 Int32。 “string”和Int32之间没有映射,所以会爆炸。

*WithLengthPrefix 方法允许序列化程序知道每条消息在哪里结束;因此,如果我们使用*WithLengthPrefix 序列化Data1Data2,然后使用*WithLengthPrefix 方法反序列化Data1Data2,那么它正确拆分传入的两个实例之间的数据,只将正确的值读入正确的对象。

此外,当像这样存储异构数据时,您可能还想为每个类另外分配(通过*WithLengthPrefix)不同的字段编号;这可以更好地了解正在反序列化的类型。 Serializer.NonGeneric 中还有一个方法可以用来反序列化数据而无需提前知道我们在反序列化什么

// Data1 is "1", Data2 is "2"
Serializer.SerializeWithLengthPrefix(ms, d1, PrefixStyle.Base128, 1);
Serializer.SerializeWithLengthPrefix(ms, d2, PrefixStyle.Base128, 2);
ms.Position = 0;

var lookup = new Dictionary<int,Type> { {1, typeof(Data1)}, {2,typeof(Data2)}};
object obj;
while (Serializer.NonGeneric.TryDeserializeWithLengthPrefix(ms,
    PrefixStyle.Base128, fieldNum => lookup[fieldNum], out obj))
{
    Console.WriteLine(obj); // writes Data1 on the first iteration,
                            // and Data2 on the second iteration
}

【讨论】:

  • Hmmmm.... 不,这不应该导致此错误。 protobuf 规范设计 是可附加的,因此编写第二个对象将导致完全有效且合法的 protobuf 流,因此 不应 导致此错误(指损坏的流)。 *WithLengthPrefix 的意义在于没有它,你绝对无法知道第一条消息在哪里结束,第二条消息从哪里开始。如果您有一个示例显示正在写入导致此错误的多条消息,我非常希望看到它。
  • 我使用两次对 Serialize 的调用(它是一个键值对)将两个不同的对象按顺序序列化到同一个流中。反序列化会导致未知的线型错误。我在两个对象中重用了相同的 id(每个都从 1 开始)。在第一次反序列化(返回第二个对象的数据)后,将两个相同的对象放在同一个流中,将流留在最后,第二次读取会为您提供一个默认对象。
  • 我需要重新制作它,但我强烈怀疑这是一条不同但相似的消息,特别是发现的线路类型对预期数据无效。从那以后就可以了,因为您本质上是在合并一个字符串和一个 int。不过,我稍后会检查。
  • 我道歉;它确实提出了相同的信息,但上下文不同。如果您不介意,我会在您的答案中添加更多上下文。
  • @Philip 试试 Serializer.NonGeneric
【解决方案4】:

以前的答案已经比我更好地解释了这个问题。我只是想添加一种更简单的方法来重现异常。

如果序列化 ProtoMember 的类型与反序列化期间的预期类型不同,也会发生此错误。

例如,如果客户端发送以下消息:

public class DummyRequest
{
    [ProtoMember(1)]
    public int Foo{ get; set; }
}

但是服务器将消息反序列化成如下类:

public class DummyRequest
{
    [ProtoMember(1)]
    public string Foo{ get; set; }
}

那么这将导致对于这种情况略有误导的错误消息

ProtoBuf.ProtoException:线型无效;这通常意味着您在没有截断或设置长度的情况下覆盖了文件

即使属性名称更改也会发生。假设客户端发送了以下内容:

public class DummyRequest
{
    [ProtoMember(1)]
    public int Bar{ get; set; }
}

这仍然会导致服务器将int Bar 反序列化为string Foo,从而导致相同的ProtoBuf.ProtoException

我希望这有助于调试他们的应用程序的人。

【讨论】:

    【解决方案5】:

    还要检查所有子类是否都具有[ProtoContract] 属性。有时,当您拥有丰富的 DTO 时,您可能会错过它。

    【讨论】:

    • 不过,这通常会显示不同的错误消息(关于意外类型)
    • @MarcGravell 一旦我有SerializationException: ProtoBufServiceClient: Error deserializing: Invalid wire-type; 和这个页面地址,但有时,当我没有放置属性时,我有:SerializationException: ProtoBufServiceClient: Error deserializing: No serializer defined for type: Foo ---&gt; System.InvalidOperationException: No serializer defined for type: Foo. 第一种情况我有:class A { public List&lt;Bar&gt; bars {get;set;}},第二种情况是单一的Foo 类型的字段。我将在简单的控制台应用程序中检查问题。
    【解决方案6】:

    我在使用不正确的Encoding 类型将字节转换为字符串输入和输出时遇到了这个问题。

    需要使用Encoding.Default 而不是Encoding.UTF8

    using (var ms = new MemoryStream())
    {
        Serializer.Serialize(ms, obj);
        var bytes = ms.ToArray();
        str = Encoding.Default.GetString(bytes);
    }
    

    【讨论】:

    • Need to use Encoding.Default ...不,只是不;你从不 - 从不 - 想用Encoding.Default 做任何事Encoding 在这里根本不正确;如果您需要二进制作为文本,请使用 base-64 或类似的; 不是编码。并且编码用于将文本存储为二进制,不是相反。
    • 我希望所有阅读本文的人都知道@MarcGravell 比我好几个数量级,请接受他的回答,感谢您不反对我。尽管它在我们将对象序列化到 redis 的代码中为我们工作了多年。希望这不会让马克双倍颤抖。他值得更好。他赚到了。谢谢,社区
    • 你知道redis可以说二进制吗?你可以给 redis 你的 byte[]
    • 我在使用 Encoding.UTF8 时遇到了同样的问题。通过从 Base64String 转换到 Base64String 来解决它。
    【解决方案7】:

    如果您使用的是 SerializeWithLengthPrefix,请注意将实例转换为 object 类型会破坏反序列化代码并导致 ProtoBuf.ProtoException : Invalid wire-type

    using (var ms = new MemoryStream())
    {
        var msg = new Message();
        Serializer.SerializeWithLengthPrefix(ms, (object)msg, PrefixStyle.Base128); // Casting msg to object breaks the deserialization code.
        ms.Position = 0;
        Serializer.DeserializeWithLengthPrefix<Message>(ms, PrefixStyle.Base128)
    }
    

    【讨论】:

    • 好吧,我想这是真的……但是……你为什么要那样做?顺便说一句,Serializer.NonGeneric 中有一个非通用 API,可以与 object 一起正常工作
    【解决方案8】:

    这发生在我的案例中,因为我有这样的事情:

    var ms = new MemoryStream();
    Serializer.Serialize(ms, batch);
    
    _queue.Add(Convert.ToBase64String(ms.ToArray()));
    

    所以基本上我将 base64 放入队列中,然后在消费者方面我有:

    var stream = new MemoryStream(Encoding.UTF8.GetBytes(myQueueItem));
    var batch = Serializer.Deserialize<List<EventData>>(stream);
    

    所以虽然每个 myQueueItem 的类型都是正确的,但我忘记了我转换了一个字符串。解决方案是再次转换它:

    var bytes = Convert.FromBase64String(myQueueItem);
    var stream = new MemoryStream(bytes);
    var batch = Serializer.Deserialize<List<EventData>>(stream);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-10-15
      • 1970-01-01
      • 1970-01-01
      • 2016-07-04
      • 1970-01-01
      • 1970-01-01
      • 2012-05-01
      相关资源
      最近更新 更多