c# protobuf-net

c# - Usando Protobuf-net, de repente recibí una excepción sobre un tipo de cable desconocido



(7)

(Este es un re-post de una pregunta que vi en mi RSS, pero que fue borrado por el OP. Lo he vuelto a agregar porque he visto esta pregunta varias veces en diferentes lugares; wiki para "bueno" formar")

De repente, recibo una ProtoException al deserializar y el mensaje es: desconocido tipo de cable 6

  • ¿Qué es un tipo de cable?
  • ¿Cuáles son los diferentes valores de tipo de cable y su descripción?
  • Sospecho que un campo está causando el problema, ¿cómo depurar esto?

Dado que el seguimiento de la pila hace referencia a esta pregunta de , pensé que podría señalar que también puede recibir esta excepción si deserializa (accidentalmente) una transmisión en un tipo diferente al que se serializó. Por lo tanto, vale la pena verificar dos aspectos de la conversación para asegurarse de que esto no suceda.


Esto también puede deberse a un intento de escribir más de un mensaje protobuf en una sola transmisión. La solución es usar SerializeWithLengthPrefix y DeserializeWithLengthPrefix.

Por qué sucede esto:

La especificación protobuf admite una cantidad bastante pequeña de tipos de cable (los formatos de almacenamiento binarios) y tipos de datos (los tipos de datos .NET etc.). Además, esto no es 1: 1, ni es 1: muchos o muchos: 1 - se puede usar un solo tipo de cable para múltiples tipos de datos, y un solo tipo de datos se puede codificar a través de cualquiera de los múltiples tipos de cables . Como consecuencia, no puede comprender completamente un fragmento de protobuf a menos que ya conozca la escema, para que sepa cómo interpretar cada valor. Cuando está, por ejemplo, leyendo un tipo de datos Int32 , los tipos de cable soportados pueden ser "varint", "fixed32" y "fixed64", donde-como cuando se lee un tipo de datos String , el único tipo de cable soportado es "cuerda".

Si no hay un mapa compatible entre el tipo de datos y el tipo de cable, entonces los datos no se pueden leer y se genera este error.

Ahora veamos por qué ocurre esto en el escenario aquí:

[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); } }

En lo anterior, dos mensajes se escriben directamente uno después del otro. La complicación es: protobuf es un formato apilable, con append que significa "fusionar". Un mensaje protobuf no conoce su propia longitud , por lo que la forma predeterminada de leer un mensaje es: leer hasta EOF. Sin embargo, aquí hemos anexado dos tipos diferentes . Si leemos esto de nuevo, no sabe cuándo hemos terminado de leer el primer mensaje, por lo que sigue leyendo. Cuando llega a los datos del segundo mensaje, nos encontramos leyendo un tipo de cable "de cadena", pero aún estamos tratando de poblar una instancia de Data1 , para la cual el miembro 1 es un Int32 . No hay un mapa entre "cadena" e Int32 , por lo que explota.

Los métodos *WithLengthPrefix permiten que el serializador sepa dónde termina cada mensaje; entonces, si serializamos un Data1 y un Data2 usando el *WithLengthPrefix , luego deserializamos un Data1 y un Data2 usando los métodos *WithLengthPrefix , entonces divide correctamente los datos entrantes entre las dos instancias, solo leyendo el valor correcto en el objeto correcto.

Además, al almacenar datos heterogéneos como este, es posible que desee asignar (a través de *WithLengthPrefix ) un número de campo diferente para cada clase; esto proporciona una mayor visibilidad de qué tipo se está deserializando. También hay un método en Serializer.NonGeneric que luego se puede utilizar para deserializar los datos sin necesidad de saber de antemano qué estamos deserializando :

// 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 }


He visto este problema cuando uso el tipo de Encoding inadecuado para convertir los bytes dentro y fuera de cadenas.

Necesita usar Encoding.Default y no Encoding.UTF8 .

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


Primero que verifique:

¿LOS DATOS DE PROTOCOLO DE DATOS DE ENTRADA? Si intenta analizar otro formato (json, xml, csv, formateador binario) o simplemente datos rotos (una página de texto de marcador html "error de servidor interno", por ejemplo), no funcionará .

¿Qué es un tipo de cable?

Es una bandera de 3 bits que le dice (en términos generales, solo son 3 bits después de todo) cómo serán los siguientes datos.

Cada campo en los buffers de protocolo está precedido por un encabezado que le dice qué campo (número) representa y qué tipo de datos viene a continuación; este "qué tipo de datos" es esencial para respaldar el caso en el que los datos imprevistos están en la transmisión (por ejemplo, ha agregado campos al tipo de datos en un extremo), ya que le permite al serializador saber cómo leer más allá de ese datos (o guárdelo para ida y vuelta si es necesario).

¿Cuáles son los diferentes valores de tipo de cable y su descripción?

  • 0: entero de longitud de variante (hasta 64 bits) - base-128 codificado con el MSB que indica la continuación (se utiliza como valor predeterminado para los tipos de enteros, incluidas las enumeraciones)
  • 1: 64 bits: 8 bytes de datos (se usa para el double o de forma electiva para el long / ulong )
  • 2: con prefijo de longitud: primero lea un número entero utilizando la codificación de longitud de variante; esto le dice cuántos bytes de datos siguen (utilizados para cadenas, byte[] , matrices "empaquetadas" y como valor predeterminado para las propiedades / listas de objetos secundarios)
  • 3: "start group" - un mecanismo alternativo para codificar objetos secundarios que usa etiquetas de inicio / final - en gran parte desaprobado por Google, es más costoso omitir un campo de objetos secundarios completo ya que no se puede simplemente "buscar" un pasado inesperado objeto
  • 4: "grupo final" - hermanado con 3
  • 5: 32 bits: 4 bytes de datos (utilizados para float , o de manera electiva para int / uint y otros tipos enteros pequeños)

Sospecho que un campo está causando el problema, ¿cómo depurar esto?

¿Estás serializando un archivo? La causa más probable (en mi experiencia) es que haya sobrescrito un archivo existente, pero no lo haya truncado; es decir, era 200 bytes; lo has reescrito, pero con solo 182 bytes. En la actualidad, hay 18 bytes de basura al final de la secuencia que están provocando su desconexión. Los archivos se deben truncar al volver a escribir los búferes de protocolo. Puedes hacer esto con FileMode :

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

o alternativamente por SetLength después de escribir sus datos:

file.SetLength(file.Position);

Otra posible causa

Está (accidentalmente) deserializando un flujo en un tipo diferente al que se serializó. Vale la pena verificar dos aspectos de la conversación para asegurarse de que esto no suceda.


Si está utilizando SerializeWithLengthPrefix, tenga en cuenta que la instancia de conversión al tipo de object rompe el código de deserialización y causa 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) }


También compruebe lo obvio que todas sus subclases tienen el atributo [ProtoContract] . A veces puede perderlo cuando tiene DTO rico.


Las respuestas anteriores ya explican el problema mejor que yo. Solo quiero agregar una forma aún más simple de reproducir la excepción.

Este error también ocurrirá simplemente si el tipo de un ProtoMember serializado es diferente del tipo esperado durante la deserialización.

Por ejemplo, si el cliente envía el siguiente mensaje:

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

Pero lo que el servidor deserializa el mensaje es la siguiente clase:

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

Entonces, esto dará como resultado, en este caso, un mensaje de error ligeramente engañoso.

ProtoBuf.ProtoException: tipo de cable no válido; esto generalmente significa que ha sobrescrito un archivo sin truncar o establecer la longitud

Incluso ocurrirá si el nombre de la propiedad cambió. Digamos que el cliente envió lo siguiente en su lugar:

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

Esto aún causará que el servidor deserialice la Bar int para ProtoBuf.ProtoException Foo que causa la misma ProtoBuf.ProtoException .

Espero que esto ayude a alguien a depurar su aplicación.