(这是我在我的RSS中看到的一个问题的重新发布,但被OP删除了.我重新添加了它,因为我已经在不同的地方多次询问过这个问题; wiki为"好"形成")
突然间,我ProtoException
在反序列化时收到了一个消息:未知的线型6
Mar*_*ell 56
首先要检查:
是输入数据PROTOBUF数据?如果您尝试解析另一种格式(json,xml,csv,binary-formatter),或者只是破坏数据(例如"内部服务器错误"html占位符文本页面),那么它将无法工作.
什么是线型?
它是一个3位标志,告诉它(广义上说,它毕竟只有3位)下一个数据是什么样的.
协议缓冲区中的每个字段都以一个标头为前缀,该标头告诉它它代表哪个字段(数字),以及接下来会有哪种类型的数据; 这种"什么类型的数据"对于支持流中未预料到的数据的情况是必不可少 的(例如,您在一端向数据类型添加了字段),因为它让序列化器知道如何读取过去数据(如果需要,可将其存储为往返).
有哪些不同的线型值及其描述?
double
或选择用于long
/ ulong
)byte[]
,"打包"数组,以及作为子对象属性/列表的默认值)float
,或择为int
/ uint
和其他小整数类型)我怀疑一个字段导致了问题,如何调试这个?
你在序列化文件吗?在最有可能的原因(在我的经验)是,已覆盖现有文件,但还没有被截断它; 即它是 200字节; 你重写了它,但只有182个字节.现在流的末尾有18个字节的垃圾正在绊倒它.重写协议缓冲区时必须截断文件.你可以这样做FileMode
:
using(var file = new FileStream(path, FileMode.Truncate)) {
// write
}
Run Code Online (Sandbox Code Playgroud)
或者SetLength
在写完你的数据之后:
file.SetLength(file.Position);
Run Code Online (Sandbox Code Playgroud)
其他可能的原因
您(意外地)将流反序列化为与序列化不同的类型.值得仔细检查对话的双方,以确保不会发生这种情况.
Kir*_*oll 41
由于堆栈跟踪引用了这个StackOverflow问题,我想我会指出,如果您(意外地)将流反序列化为与序列化不同的类型,您也可以收到此异常.所以值得仔细检查对话的双方,以确保不会发生这种情况.
Chr*_*000 10
这也可能是由于尝试将多个protobuf消息写入单个流.解决方案是使用SerializeWithLengthPrefix和DeserializeWithLengthPrefix.
为什么会这样:
protobuf规范支持相当少量的线类型(二进制存储格式)和数据类型(.NET等数据类型).此外,这不是1:1,也不是1:很多或很多:1 - 单个线型可用于多种数据类型,单个数据类型可通过多种线型中的任何一种进行编码.因此,除非您已经知道了scema,否则无法完全理解protobuf片段,因此您知道如何解释每个值.当您读取Int32
数据类型时,支持的线型可能是"varint","fixed32"和"fixed64",其中 - 当读取String
数据类型时,唯一支持的线型是"字符串" ".
如果数据类型和线型之间没有兼容的映射,则无法读取数据,并引发此错误.
现在让我们看一下为什么会出现这种情况:
[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);
}
}
Run Code Online (Sandbox Code Playgroud)
在上面,两个消息直接写在彼此之后.复杂的是:protobuf是一种可附加格式,附加意思是"合并".protobuf消息不知道自己的长度,因此读取消息的默认方式是:读取直到EOF.但是,这里我们附加了两种不同的类型.如果我们回过头来看,它不知道我们何时读完第一条消息,所以它一直在阅读.当它从第二条消息中获取数据时,我们发现自己正在读取一个"字符串"有线类型,但我们仍在尝试填充一个Data1
实例,其中成员1是一个实例Int32
."string"之间没有映射Int32
,因此它会爆炸.
这些*WithLengthPrefix
方法允许序列化器知道每条消息的完成位置; 所以,如果我们序列化Data1
并Data2
使用*WithLengthPrefix
,然后使用方法反序列化a Data1
和a ,那么它正确地在两个实例之间拆分传入数据,只将正确的值读入正确的对象.Data2
*WithLengthPrefix
此外,在存储这样的异构数据时,您可能还需要*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
}
Run Code Online (Sandbox Code Playgroud)
以前的答案已经比我更好地解释了这个问题。我只想添加一种更简单的方法来重现异常。
如果ProtoMember
在反序列化期间序列化的类型与预期类型不同,也会发生此错误。
例如,如果客户端发送以下消息:
public class DummyRequest
{
[ProtoMember(1)]
public int Foo{ get; set; }
}
Run Code Online (Sandbox Code Playgroud)
但是服务器将消息反序列化为以下类:
public class DummyRequest
{
[ProtoMember(1)]
public string Foo{ get; set; }
}
Run Code Online (Sandbox Code Playgroud)
那么这将导致这种情况下稍微误导性的错误消息
ProtoBuf.ProtoException:线类型无效;这通常意味着您在没有截断或设置长度的情况下覆盖了文件
如果属性名称更改,它甚至会发生。假设客户端发送了以下内容:
public class DummyRequest
{
[ProtoMember(1)]
public int Bar{ get; set; }
}
Run Code Online (Sandbox Code Playgroud)
这仍然会导致服务器反序列化int
Bar
到string
Foo
这将导致相同的ProtoBuf.ProtoException
。
我希望这有助于某人调试他们的应用程序。