Используя Protobuf-net, я неожиданно получил исключение о неизвестном проводном типе - PullRequest
53 голосов
/ 28 января 2010

(это повторное сообщение вопроса, который я видел в своем RSS, но который был удален OP. Я повторно добавил его, потому что видел этот вопрос несколько раз в разных местах; вики) за "хорошую форму")

Внезапно я получаю ProtoException при десериализации и сообщение: неизвестный проводной тип 6

  • Что такое проводной тип?
  • Каковы различные значения типа провода и их описание?
  • Я подозреваю, что поле вызывает проблему, как это отладить?

Ответы [ 8 ]

52 голосов
/ 28 января 2010

Первое, что нужно проверить:

ВХОДНЫЕ ДАННЫЕ ЗАЩИТЫ ДАННЫХ? Если вы попытаетесь разобрать другой формат (json, xml, csv, binary-formatter) или просто испорченные данные (например, текстовая страница HTML-заполнителя html «внутренняя ошибка сервера»), не будет работать .


Что такое проводной тип?

Это 3-битный флаг, который сообщает ему (в общих чертах; всего 3 бита), как будут выглядеть следующие данные.

Каждому полю в буферах протокола предшествует заголовок, который сообщает ему, какое поле (число) оно представляет, и какой тип данных будет дальше; этот «какой тип данных» необходим для поддержки случая, когда непредвиденные данные находятся в потоке (например, вы добавили поля к типу данных на одном конце), как он позволяет сериализатору узнать, как считывать эти данные (или, если необходимо, сохранять их для передачи в оба конца).

Каковы различные значения типа провода и их описание?

  • 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);

Другая возможная причина

Вы (случайно) десериализуете поток в другой тип, чем тот, который был сериализован. Стоит перепроверить обе стороны разговора, чтобы убедиться, что этого не происходит.

38 голосов
/ 16 июня 2013

Поскольку трассировка стека ссылается на этот вопрос StackOverflow, я подумал, что я могу указать, что вы также можете получить это исключение, если вы (случайно) десериализовали поток в тип, отличный от сериализованного. Поэтому стоит дважды проверить обе стороны разговора, чтобы убедиться, что этого не происходит.

9 голосов
/ 13 сентября 2012

Это также может быть вызвано попыткой записи более одного сообщения 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
}
4 голосов
/ 17 апреля 2015

Предыдущие ответы уже объясняют проблему лучше, чем я. Я просто хочу добавить еще более простой способ воспроизвести исключение.

Эта ошибка также будет возникать просто, если тип сериализованного 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.

Надеюсь, это поможет кому-нибудь отладить приложение.

1 голос
/ 29 сентября 2017

Если вы используете 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)
}
1 голос
/ 16 июня 2014

Также проверьте, очевидно, что все ваши подклассы имеют атрибут [ProtoContract]. Иногда вы можете пропустить это, когда у вас есть богатый DTO.

0 голосов
/ 06 августа 2018

Это произошло в моем случае, потому что у меня было что-то вроде этого:

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);
0 голосов
/ 01 сентября 2016

Я видел эту проблему при использовании неправильного типа Encoding для преобразования байтов в строки и из строк.

Нужно использовать Encoding.Default, а не Encoding.UTF8.

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