Это также может быть вызвано попыткой записи более одного сообщения protobuf в один поток. Решением является использование SerializeWithLengthPrefix и DeserializeWithLengthPrefix.
Почему это происходит:
Спецификация protobuf поддерживает довольно небольшое количество типов проводов (двоичные форматы хранения) и типов данных (типы данных .NET и т. Д.). Кроме того, это не 1: 1 и не 1: много или много: 1 - один тип проводника может использоваться для нескольких типов данных, а один тип данных может кодироваться через любой из нескольких типов проводов. , Как следствие, вы не можете полностью понять фрагмент 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);
}
}
В приведенном выше примере два сообщения пишутся непосредственно друг за другом. Сложность заключается в следующем: protobuf - это добавляемый формат, с добавлением, означающим «слияние». Сообщение protobuf не знает своей собственной длины , поэтому способ чтения сообщения по умолчанию: чтение до EOF. Однако здесь мы добавили два разных типа. Если мы прочитаем это обратно, он не знает , когда мы закончили читать первое сообщение, поэтому он продолжает читать. Когда он получает данные из второго сообщения, мы обнаруживаем, что читаем проводной тип «string», но мы все еще пытаемся заполнить экземпляр Data1
, для которого элемент 1 является Int32
. Между строкой и Int32
нет карты, поэтому она взрывается.
Методы *WithLengthPrefix
позволяют сериализатору знать, где заканчивается каждое сообщение; Итак, если мы сериализовали Data1
и Data2
с использованием *WithLengthPrefix
, а затем десериализовали Data1
и 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
}