72 votes

En utilisant Protobuf-net, j'ai soudainement eu une exception concernant un type de fil inconnu

(Ceci est une nouvelle publication d'une question que j'ai vue dans mon RSS mais qui a été supprimée par le PO. Je l'ai ajoutée de nouveau parce que j'ai vu cette question posée à plusieurs reprises à différents endroits; wiki pour "bien" forme")

Soudain, je reçois un ProtoException lors de la désérialisation et le message est: type de fil inconnu 6

  • Qu'est-ce qu'un type de fil?
  • Quelles sont les différentes valeurs de type de fil et leur description?
  • Je soupçonne qu'un champ est à l'origine du problème, comment le déboguer?

63voto

Marc Gravell Points 482669

Qu'est ce qu'un fil-type?

C'est un 3-bit indicateur qui le dit (dans un sens large; il n'est que de 3 bits, après tout) ce que la nouvelle apparence des données.

Chaque champ protocol buffers est précédé d'un en-tête qui l'informe de champ (nombre) qu'il représente, et quel type de données est venue prochaine; ce "type de données" est indispensable pour prendre en charge le cas où imprévue des données dans le flux de données (par exemple, vous avez ajouté des champs au type de données à une extrémité), comme il permet à l'sérialiseur de savoir comment lire les dernières données (ou d'un magasin pour un aller-retour si besoin).

Quels sont les différents fil-type des valeurs et de leur description?

  • 0: variante de longueur entier (jusqu'à 64 bits) - de base-128 codé avec le MSB indiquant la poursuite (utilisé par défaut pour les types integer, y compris les enums)
  • 1: 64-bit - 8 octets de données (utilisé pour l' double, ou lors d'une chirurgie élective pour long/ulong)
  • 2: longueur de préfixe de lecture d'un nombre entier à l'aide de la variante de codage par longueur; cela vous indique le nombre d'octets de données de suivi (utilisée pour les chaînes, byte[], "emballé" des tableaux, et que la valeur par défaut pour les objets enfants des propriétés ou des listes)
  • 3: "démarrage de groupe" - un autre mécanisme pour l'encodage de l'enfant des objets qui utilise de début/fin des balises largement désapprouvée par Google, il est plus coûteux d'ignorer l'ensemble d'un enfant-objet, champ puisque vous ne pouvez pas simplement "rechercher" dans le passé, un objet inattendu
  • 4: "la fin d'un groupe", jumelée avec 3
  • 5: 32 bits (4 octets de données (utilisé pour l' float, ou lors d'une chirurgie élective pour int/uint et d'autres petits types d'entiers)

Je soupçonne un champ est à l'origine du problème, comment faire pour déboguer ce?

Êtes-vous de la sérialisation d'un fichier? Le plus probable cause (dans mon expérience), c'est que vous avez effacé un fichier existant, mais n'ont pas tronquée; c'est à dire qu'il était de 200 octets; vous avez ré-écrit-il, mais avec seulement 182 octets. Il y a maintenant 18 octets d'ordures sur la fin de votre flux, qui est le déclenchement des. Les fichiers doivent être tronqués lors de la ré-écriture du protocole de tampons. Vous pouvez faire cela avec FileMode:

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

ou, sinon, par SetLength après l'écriture de vos données:

file.SetLength(file.Position);

46voto

Kirk Woll Points 34601

Étant donné que la trace de pile fait référence à cette question StackOverflow, je pensais préciser que vous pouvez également recevoir cette exception si vous désérialisez (accidentellement) un flux dans un type différent de celui qui a été sérialisé. Il est donc utile de vérifier les deux côtés de la conversation pour vous assurer que cela ne se produit pas.

11voto

Chriseyre2000 Points 1671

Cela peut aussi être causée par une tentative d'écrire plus d'un protobuf message à un seul flux. La solution est d'utiliser SerializeWithLengthPrefix et DeserializeWithLengthPrefix.


Pourquoi ce qui se passe:

Le protobuf spécification prend en charge un nombre relativement faible de fil-types (le binaire formats de stockage) et les types de données (l' .NET, etc types de données). En outre, ce n'est pas 1:1, ni 1:plusieurs ou plusieurs:1 - un seul fil-type peut être utilisé pour de multiples types de données, et un seul type de données peuvent être codées en utilisant des multiples de fil-types. En conséquence, vous ne peut pas comprendre pleinement un protobuf fragment, sauf si vous connaissez déjà le scema, de sorte que vous savez comment interpréter chaque valeur. Lorsque vous êtes, disons, à la lecture d'un Int32 -type de données, le fil-types peuvent être "varint", "fixed32" et "fixed64", où-comme lors de la lecture d'un String -type de données, la seule prise en charge de la fil-de type "string".

Si il n'est pas compatible carte entre le type de données et le fil-type, les données ne puissent être lues, et cette erreur est générée.

Maintenant, regardons pourquoi cela se produit dans le scénario ici:

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

Ci-dessus, deux messages sont écrits directement après les uns les autres. La complication est: protobuf est un appendable format, avec append sens "fusion". Un protobuf message ne connaît pas sa propre longueur, de sorte que le défaut de la lecture d'un message est: lire jusqu'à ce que les expressions du FOLKLORE. Cependant, ici, nous avons ajouté deux différents types. Si nous lisons ce retour, il ne sait pas quand nous avons fini de lire le premier message, il garde la lecture. Lorsqu'il reçoit les données de la deuxième message, nous nous trouvons à la lecture d'une "chaîne" de fil-type, mais nous sommes encore à essayer de remplir un Data1 de l'instance, pour laquelle les 1 Int32. Il n'y a pas de correspondance entre la chaîne "string" et Int32, alors qu'elle n'explose.

L' *WithLengthPrefix méthodes permettent le sérialiseur de savoir où chaque message se termine; donc, si nous sérialiser un Data1 et Data2 à l'aide de l' *WithLengthPrefix, alors désérialiser un Data1 et Data2 à l'aide de l' *WithLengthPrefix méthodes, puis il correctement divise les données entrantes entre les deux cas, seule la lecture de la juste valeur dans le droit de l'objet.

En outre, lors de l'entreposage de données hétérogènes comme ça, vous pourriez voulez également affecter (via *WithLengthPrefix) un autre domaine-le nombre de chaque catégorie; ce qui assure une plus grande visibilité de quel type est désérialisé. Il y a aussi une méthode en Serializer.NonGeneric qui peut ensuite être utilisé pour désérialiser les données sans avoir besoin de connaître à l'avance ce que nous sommes désérialisation:

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

1voto

Tomasito Points 416

Vérifiez également que toutes vos sous-classes ont l'attribut [ProtoContract] . Parfois, vous pouvez le manquer lorsque vous avez un DTO riche.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X