使用 Protobuf-net,我突然遇到一个关于未知线路类型的异常
(这是我在 RSS 中看到的一个问题的重新发布,但已被 OP 删除。我重新添加了它,因为我在不同的地方看到这个问题被问了好几次;wiki 上的“好” form")
突然,我在反序列化时收到 ProtoException
,消息是:unknownwire-type 6
- 什么是wire-type?
- 有哪些不同的电线类型值及其描述?
- 我怀疑是某个字段导致了问题,如何调试?
(this is a re-post of a question that I saw in my RSS, but which was deleted by the OP. I've re-added it because I've seen this question asked several times in different places; wiki for "good form")
Suddenly, I receive a ProtoException
when deserializing and the message is: unknown wire-type 6
- What is a wire-type?
- What are the different wire-type values and their description?
- I suspect a field is causing the problem, how to debug this?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(10)
首先要检查:
输入数据是 PROTOBUF 数据吗?如果您尝试解析其他格式(json、xml、csv、二进制格式化程序),或者只是损坏数据(例如“内部服务器错误”html 占位符文本页面),那么它将无法工作。
它是一个 3 位标志,告诉它(广义上讲;毕竟它只有 3 位)下一个数据是什么样的。
协议缓冲区中的每个字段都有一个标头作为前缀,该标头告诉它代表哪个字段(数字),
以及接下来会出现什么类型的数据;这个“什么类型的数据”对于支持以下情况至关重要
意外数据位于流中(例如,您在一端向数据类型添加了字段),如下所示
它让序列化器知道如何读取该数据(或在需要时存储它以进行往返)。
double
,或选择性用于long
/ulong
)byte[]
、“打包”数组,并作为子对象属性/列表的默认值)float
,或选择性用于int
/uint
和其他小整数类型)您正在序列化到文件吗? 最可能的原因(根据我的经验)是您覆盖了现有文件,但没有截断它;即是 200 字节;你已经重写了它,但只有 182 字节。现在,流的末尾有 18 字节的垃圾,导致流崩溃。重写协议缓冲区时必须截断文件。您可以使用
FileMode
来完成此操作:或者在写入数据之后通过
SetLength
来完成此操作:您(意外地)将流反序列化为与序列化类型不同的类型。值得仔细检查对话双方以确保这种情况不会发生。
First thing to check:
IS THE INPUT DATA PROTOBUF DATA? If you try and parse another format (json, xml, csv, binary-formatter), or simply broken data (an "internal server error" html placeholder text page, for example), then it won't work.
It is a 3-bit flag that tells it (in broad terms; it is only 3 bits after all) what the next data looks like.
Each field in protocol buffers is prefixed by a header that tells it which field (number) it represents,
and what type of data is coming next; this "what type of data" is essential to support the case where
unanticipated data is in the stream (for example, you've added fields to the data-type at one end), as
it lets the serializer know how to read past that data (or store it for round-trip if required).
double
, or electively forlong
/ulong
)byte[]
, "packed" arrays, and as the default for child objects properties / lists)float
, or electively forint
/uint
and other small integer types)Are you serializing to a file? The most likely cause (in my experience) is that you have overwritten an existing file, but have not truncated it; i.e. it was 200 bytes; you've re-written it, but with only 182 bytes. There are now 18 bytes of garbage on the end of your stream that is tripping it up. Files must be truncated when re-writing protocol buffers. You can do this with
FileMode
:or alternatively by
SetLength
after writing your data:You are (accidentally) deserializing a stream into a different type than what was serialized. It's worth double-checking both sides of the conversation to ensure this is not happening.
由于堆栈跟踪引用了此 StackOverflow 问题,因此我想我应该指出,如果您(意外地)将流反序列化为与序列化类型不同的类型,您也可能会收到此异常。因此,值得仔细检查对话双方,以确保这种情况不会发生。
Since the stack trace references this StackOverflow question, I thought I'd point out that you can also receive this exception if you (accidentally) deserialize a stream into a different type than what was serialized. So it's worth double-checking both sides of the conversation to ensure this is not happening.
这也可能是由于尝试将多个 protobuf 消息写入单个流而引起的。解决方案是使用 SerializeWithLengthPrefix 和 DeserializeWithLengthPrefix。
为什么会发生这种情况:
protobuf 规范支持相当少量的线路类型(二进制存储格式)和数据类型(.NET 等数据类型)。此外,这不是 1:1,也不是 1:many 或 Many:1 - 单个线类型可用于多种数据类型,并且单个数据类型可以通过多种线类型中的任何一种进行编码。因此,除非您已经了解场景,否则您无法完全理解 protobuf 片段,从而知道如何解释每个值。例如,当您读取
Int32
数据类型时,支持的线类型可能是“varint”、“fixed32”和“fixed64”,而读取String< /code> 数据类型,唯一支持的连线类型是“string”。
如果数据类型和线路类型之间没有兼容的映射,则无法读取数据,并引发此错误。
现在让我们看看为什么会在此处的场景中发生这种情况
:上面,两条消息是直接写在对方之后的。复杂的是:protobuf 是一种可追加的格式,append 的意思是“合并”。 protobuf消息不知道自己的长度,因此读取消息的默认方式是:读取直到EOF。但是,这里我们附加了两种不同类型。如果我们读回来,它不知道我们什么时候读完第一条消息,所以它会继续读。当它从第二条消息获取数据时,我们发现自己正在读取“字符串”线类型,但我们仍在尝试填充
Data1
实例,其中成员 1 是一个Int32
。 “string”和Int32
之间没有映射,所以它爆炸了。*WithLengthPrefix
方法允许序列化器知道每条消息的结束位置;因此,如果我们使用*WithLengthPrefix
序列化Data1
和Data2
,然后反序列化Data1
和>Data2
使用*WithLengthPrefix
方法,然后它正确在两个实例之间分割传入数据,仅将正确的值读入正确的对象。此外,当存储这样的异构数据时,您可能希望为每个类另外分配(通过
*WithLengthPrefix
)不同的字段编号;这可以更好地了解正在反序列化的类型。Serializer.NonGeneric
中还有一个方法,可用于反序列化数据而无需提前知道我们要反序列化的内容:This can also be caused by an attempt to write more than one protobuf message to a single stream. The solution is to use SerializeWithLengthPrefix and DeserializeWithLengthPrefix.
Why this happens:
The protobuf specification supports a fairly small number of wire-types (the binary storage formats) and data-types (the .NET etc data-types). Additionally, this is not 1:1, nor is is 1:many or many:1 - a single wire-type can be used for multiple data-types, and a single data-type can be encoded via any of multiple wire-types. As a consequence, you cannot fully understand a protobuf fragment unless you already know the scema, so you know how to interpret each value. When you are, say, reading an
Int32
data-type, the supported wire-types might be "varint", "fixed32" and "fixed64", where-as when reading aString
data-type, the only supported wire-type is "string".If there is no compatible map between the data-type and wire-type, then the data cannot be read, and this error is raised.
Now let's look at why this occurs in the scenario here:
In the above, two messages are written directly after each-other. The complication is: protobuf is an appendable format, with append meaning "merge". A protobuf message does not know its own length, so the default way of reading a message is: read until EOF. However, here we have appended two different types. If we read this back, it does not know when we have finished reading the first message, so it keeps reading. When it gets to data from the second message, we find ourselves reading a "string" wire-type, but we are still trying to populate a
Data1
instance, for which member 1 is anInt32
. There is no map between "string" andInt32
, so it explodes.The
*WithLengthPrefix
methods allow the serializer to know where each message finishes; so, if we serialize aData1
andData2
using the*WithLengthPrefix
, then deserialize aData1
and aData2
using the*WithLengthPrefix
methods, then it correctly splits the incoming data between the two instances, only reading the right value into the right object.Additionally, when storing heterogeneous data like this, you might want to additionally assign (via
*WithLengthPrefix
) a different field-number to each class; this provides greater visibility of which type is being deserialized. There is also a method inSerializer.NonGeneric
which can then be used to deserialize the data without needing to know in advance what we are deserializing:之前的答案已经比我更好地解释了这个问题。我只是想添加一种更简单的方法来重现异常。
如果序列化的
ProtoMember
的类型与反序列化期间的预期类型不同,也会发生此错误。例如,如果客户端发送以下消息:
但是服务器将消息反序列化为以下类:
那么这将导致在这种情况下略有误导性的错误消息
。如果属性名称发生更改,甚至会发生这种情况。假设客户端发送了以下内容:
这仍然会导致服务器将
int
Bar
反序列化为string
Foo
这会导致相同的ProtoBuf.ProtoException
。我希望这可以帮助人们调试他们的应用程序。
Previous answers already explain the problem better than I can. I just want to add an even simpler way to reproduce the exception.
This error will also occur simply if the type of a serialized
ProtoMember
is different from the expected type during deserialization.For instance if the client sends the following message:
But what the server deserializes the message into is the following class:
Then this will result in the for this case slightly misleading error message
It will even occur if the property name changed. Let's say the client sent the following instead:
This will still cause the server to deserialize the
int
Bar
tostring
Foo
which causes the sameProtoBuf.ProtoException
.I hope this helps somebody debugging their application.
还要检查所有子类是否都有
[ProtoContract]
属性。有时当你拥有丰富的DTO时你可能会错过它。Also check the obvious that all your subclasses have
[ProtoContract]
attribute. Sometimes you can miss it when you have rich DTO.当使用不正确的
Encoding
类型将字节转入和转出字符串时,我发现了此问题。需要使用
Encoding.Default
而不是Encoding.UTF8
。I've seen this issue when using the improper
Encoding
type to convert the bytes in and out of strings.Need to use
Encoding.Default
and notEncoding.UTF8
.如果您使用 SerializeWithLengthPrefix,请注意将实例转换为
object
类型会破坏反序列化代码并导致ProtoBuf.ProtoException:无效的线型
。If you are using SerializeWithLengthPrefix, please mind that casting instance to
object
type breaks the deserialization code and causesProtoBuf.ProtoException : Invalid wire-type
.这发生在我的例子中,因为我有这样的事情:
所以基本上我将一个base64放入队列中,然后,在消费者方面我有:
所以虽然每个myQueueItem的类型是正确的,但我忘记我转换了一个字符串。解决方案是再次转换它:
This happened in my case because I had something like this:
So basically I was putting a base64 into a queue and then, on the consumer side I had:
So though the type of each myQueueItem was correct, I forgot that I converted a string. The solution was to convert it once more:
当您序列化
ProtoContract
与 1 然后再序列化时,也会发生这种情况使用另一个
ProtoContract
与 2 反序列化结果。例如,您更改了
ProtoMember
的订单数量或类型。不过,您可以使用新数字添加
ProtoMember
,而不会破坏反序列化。It can also happen when you serialize
ProtoContract
vs. 1 and thendeserialize the result with another
ProtoContract
vs. 2.E.g. you have changed a
ProtoMember
's number of order or perhaps it's type.You can however add
ProtoMember
's with new numbers without breaking the deserialiazation.就我而言,Brotli 流正在使用源流。反序列化后放置 Brotlistream 后,异常得到修复
In my case the Brotli stream was using source stream. Exception got fixed once I placed Brotlistream after Deserialization