74

(これは、RSSで見た質問の再投稿ですが、OPによって削除されました。この質問がさまざまな場所で何度か尋ねられたのを見たので、再度追加しました。形")

突然、ProtoException逆シリアル化するとが表示され、メッセージは次のようになります。不明なワイヤタイプ6

  • ワイヤータイプとは?
  • さまざまなワイヤタイプの値とその説明は何ですか?
  • フィールドが問題を引き起こしていると思われますが、これをデバッグするにはどうすればよいですか?
4

8 に答える 8

64

最初に確認すること:

入力データはPROTOBUFデータですか?別の形式(json、xml、csv、binary-formatter)、または単に壊れたデータ(たとえば、「内部サーバーエラー」のhtmlプレースホルダーテキストページ)を解析しようとすると、機能しません


ワイヤータイプとは?

これは、次のデータがどのように見えるかを(大まかに言えば、結局3ビットだけ)伝える3ビットのフラグです。

プロトコルバッファの各フィールドには、それが表すフィールド(番号)と次に来るデータのタイプを示すヘッダーがプレフィックスとして付けられます。この「どのタイプのデータ」は、 予期しないデータがストリームにある場合(たとえば、一方の端でデータ型にフィールドを追加した場合)をサポートするために不可欠です。これにより、シリアライザーはそれを超えて読み取る方法を知ることができます。データ(または必要に応じてラウンドトリップ用に保存)。

さまざまなワイヤタイプの値とその説明は何ですか?

  • 0:バリアント長整数(最大64ビット)-継続を示すMSBでエンコードされたbase-128(列挙型を含む整数型のデフォルトとして使用されます)
  • 1:64ビット-8バイトのデータ( 、、doubleまたは選択的long/に使用ulong
  • 2:長さプレフィックス-最初にバリアント長エンコーディングを使用して整数を読み取ります。これにより、データのバイト数がわかります(文字列、byte[]「パックされた」配列に使用され、子オブジェクトのプロパティ/リストのデフォルトとして使用されます)
  • 3:「開始グループ」-開始/終了タグを使用する子オブジェクトをエンコードするための代替メカニズム-Googleによって大幅に廃止されました。予期しないものを単に「シーク」することはできないため、子オブジェクトフィールド全体をスキップする方がコストがかかります。物体
  • 4:「エンドグループ」-3と姉妹都市
  • 5:32ビット-4バイトのデータ( 、、floatまたは/およびその他の小整数型に選択的に使用)intuint

フィールドが問題を引き起こしていると思われますが、これをデバッグするにはどうすればよいですか?

ファイルにシリアル化していますか?(私の経験では)最も可能性の高い原因は、既存のファイルを上書きしたが、それを切り捨てていないことですつまり、200バイトでしたあなたはそれを書き直しましたが、182バイトしかありません。ストリームの最後に18バイトのガベージがあり、それがトリップしています。プロトコルバッファを再書き込みするときは、ファイルを切り捨てる必要があります。あなたはこれを行うことができますFileMode

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

または、データを書き込んSetLength だ後、次のようにします。

file.SetLength(file.Position);

その他の考えられる原因

あなたは(偶然に)ストリームをシリアル化されたものとは異なるタイプに逆シリアル化しています。これが発生していないことを確認するために、会話の両側を再確認する価値があります。

于 2010-01-28T07:38:48.513 に答える
46

スタックトレースはこのStackOverflowの質問を参照しているため、(誤って)ストリームをシリアル化されたものとは異なるタイプに逆シリアル化した場合にも、この例外を受け取る可能性があることを指摘しておきます。したがって、会話の両側を再確認して、これが発生していないことを確認する価値があります。

于 2013-06-15T21:39:37.093 に答える
11

これは、1つのストリームに複数のprotobufメッセージを書き込もうとした場合にも発生する可能性があります。解決策は、SerializeWithLengthPrefixとDeserializeWithLengthPrefixを使用することです。


これが発生する理由:

protobuf仕様は、かなり少数のワイヤタイプ(バイナリストレージ形式)とデータタイプ(.NETなどのデータタイプ)をサポートします。さらに、これは1:1でも、1:manyまたはmany:1でもありません。単一のワイヤタイプを複数のデータタイプに使用でき、単一のデータタイプを複数のワイヤタイプのいずれかを介してエンコードできます。 。結果として、scemaをすでに知っていない限り、protobufフラグメントを完全に理解することはできないため、各値を解釈する方法を知っています。たとえば、Int32データ型を読み取る場合、サポートされるワイヤタイプは「varint」、「fixed32」、および「fixed64」である可能性がありますが、Stringデータ型を読み取る場合、サポートされるワイヤタイプは「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);
    }
}

上記では、2つのメッセージが次々に書き込まれます。複雑なのは次のとおりです。protobufは追加可能な形式であり、appendは「マージ」を意味します。protobufメッセージはそれ自体の長さを知らないため、メッセージを読み取るデフォルトの方法は次のとおりです。EOFまで読み取る。ただし、ここでは2つの異なるタイプを追加しました。これを読み返してみると、最初のメッセージをいつ読み終えたかわからないので、読み続けます。2番目のメッセージからデータを取得すると、「文字列」ワイヤタイプを読み取っていData1ますが、メンバー1がであるインスタンスにデータを入力しようとしていますInt32。「文字列」との間にマップがないInt32ため、爆発します。

これらの*WithLengthPrefixメソッドにより、シリアライザーは各メッセージがどこで終了するかを知ることができます。したがって、aData1Data2を使用して*WithLengthPrefixシリアル化してから、メソッドを使用してaData1とaを逆シリアル化すると、受信データが2つのインスタンス間で正しく分割され、適切な値が適切なオブジェクトに読み込まれるだけになります。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
}
于 2012-09-13T09:16:39.960 に答える
5

以前の回答は、私ができるよりも問題をすでによく説明しています。例外を再現するためのさらに簡単な方法を追加したいだけです。

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 Bartoを逆シリアル化します。string FooProtoBuf.ProtoException

これが誰かがアプリケーションをデバッグするのに役立つことを願っています。

于 2015-04-17T01:17:56.403 に答える
1

また、すべてのサブクラスに[ProtoContract]属性があることを確認してください。豊富なDTOがあると、見逃してしまうことがあります。

于 2014-06-16T10:59:43.677 に答える
1

不適切なEncodingタイプを使用してバイトを文字列に変換したり、文字列から変換したりすると、この問題が発生します。

使用する必要があり、使用する必要はEncoding.DefaultありませんEncoding.UTF8

using (var ms = new MemoryStream())
{
    Serializer.Serialize(ms, obj);
    var bytes = ms.ToArray();
    str = Encoding.Default.GetString(bytes);
}
于 2016-08-31T21:25:41.930 に答える
1

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)
}
于 2017-09-29T10:43:05.987 に答える
1

私の場合、これは次のようなものがあったために発生しました。

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);
于 2018-08-06T07:12:34.940 に答える