(これは、RSSで見た質問の再投稿ですが、OPによって削除されました。この質問がさまざまな場所で何度か尋ねられたのを見たので、再度追加しました。形")
突然、ProtoException
逆シリアル化するとが表示され、メッセージは次のようになります。不明なワイヤタイプ6
- ワイヤータイプとは?
- さまざまなワイヤタイプの値とその説明は何ですか?
- フィールドが問題を引き起こしていると思われますが、これをデバッグするにはどうすればよいですか?
(これは、RSSで見た質問の再投稿ですが、OPによって削除されました。この質問がさまざまな場所で何度か尋ねられたのを見たので、再度追加しました。形")
突然、ProtoException
逆シリアル化するとが表示され、メッセージは次のようになります。不明なワイヤタイプ6
最初に確認すること:
入力データはPROTOBUFデータですか?別の形式(json、xml、csv、binary-formatter)、または単に壊れたデータ(たとえば、「内部サーバーエラー」のhtmlプレースホルダーテキストページ)を解析しようとすると、機能しません。
ワイヤータイプとは?
これは、次のデータがどのように見えるかを(大まかに言えば、結局3ビットだけ)伝える3ビットのフラグです。
プロトコルバッファの各フィールドには、それが表すフィールド(番号)と次に来るデータのタイプを示すヘッダーがプレフィックスとして付けられます。この「どのタイプのデータ」は、 予期しないデータがストリームにある場合(たとえば、一方の端でデータ型にフィールドを追加した場合)をサポートするために不可欠です。これにより、シリアライザーはそれを超えて読み取る方法を知ることができます。データ(または必要に応じてラウンドトリップ用に保存)。
さまざまなワイヤタイプの値とその説明は何ですか?
double
または選択的にlong
/に使用ulong
)byte[]
「パックされた」配列に使用され、子オブジェクトのプロパティ/リストのデフォルトとして使用されます)float
または/およびその他の小整数型に選択的に使用)int
uint
フィールドが問題を引き起こしていると思われますが、これをデバッグするにはどうすればよいですか?
ファイルにシリアル化していますか?(私の経験では)最も可能性の高い原因は、既存のファイルを上書きしたが、それを切り捨てていないことです。つまり、200バイトでした。あなたはそれを書き直しましたが、182バイトしかありません。ストリームの最後に18バイトのガベージがあり、それがトリップしています。プロトコルバッファを再書き込みするときは、ファイルを切り捨てる必要があります。あなたはこれを行うことができますFileMode
:
using(var file = new FileStream(path, FileMode.Truncate)) {
// write
}
または、データを書き込んSetLength
だ後、次のようにします。
file.SetLength(file.Position);
その他の考えられる原因
あなたは(偶然に)ストリームをシリアル化されたものとは異なるタイプに逆シリアル化しています。これが発生していないことを確認するために、会話の両側を再確認する価値があります。
スタックトレースはこのStackOverflowの質問を参照しているため、(誤って)ストリームをシリアル化されたものとは異なるタイプに逆シリアル化した場合にも、この例外を受け取る可能性があることを指摘しておきます。したがって、会話の両側を再確認して、これが発生していないことを確認する価値があります。
これは、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
メソッドにより、シリアライザーは各メッセージがどこで終了するかを知ることができます。したがって、aData1
とData2
を使用して*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
}
以前の回答は、私ができるよりも問題をすでによく説明しています。例外を再現するためのさらに簡単な方法を追加したいだけです。
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
toを逆シリアル化します。string
Foo
ProtoBuf.ProtoException
これが誰かがアプリケーションをデバッグするのに役立つことを願っています。
また、すべてのサブクラスに[ProtoContract]
属性があることを確認してください。豊富なDTOがあると、見逃してしまうことがあります。
不適切なEncoding
タイプを使用してバイトを文字列に変換したり、文字列から変換したりすると、この問題が発生します。
使用する必要があり、使用する必要はEncoding.Default
ありませんEncoding.UTF8
。
using (var ms = new MemoryStream())
{
Serializer.Serialize(ms, obj);
var bytes = ms.ToArray();
str = Encoding.Default.GetString(bytes);
}
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)
}
私の場合、これは次のようなものがあったために発生しました。
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);