(これはRSSで見た質問の再投稿ですが、OPによって削除されました。この質問がさまざまな場所で何度も尋ねられたのを見て、それを再追加しました。形")
突然、逆シリアル化するときにProtoException
を受け取り、メッセージは次のとおりです:unknown wire-type 6
最初に確認すること:
入力データはプロトバッファデータですか?別の形式(json、xml、csv、binary-formatter)、または単に破損したデータ(「内部サーバーエラー」などのhtmlプレースホルダーテキストページ)を解析しようとすると、動作しません。
ワイヤータイプとは何ですか?
これは、次のデータがどのようなものかを(大まかに言えば3ビットだけです)伝える3ビットのフラグです。
プロトコルバッファの各フィールドの先頭にはヘッダーが付いており、ヘッダーが表すフィールド(番号)と次に来るデータのタイプを示します。この「どのタイプのデータ」は、予期しないデータがストリームにある場合をサポートするために不可欠です(たとえば、一方の端でデータタイプにフィールドを追加しました) 、シリアライザーがそのデータを過ぎて読み込む方法を知ることができるようにするため(または必要に応じてラウンドトリップのために保存するため)。
異なるワイヤタイプの値とその説明は何ですか?
double
に使用、または選択的にlong
/ulong
に使用)byte[]
、「パック」配列、および子オブジェクトのプロパティ/リストのデフォルトとして)float
に使用、またはelectivelyint
/uint
およびその他の小さな整数型に使用)フィールドが問題を引き起こしていると思われますが、これをデバッグする方法は?
ファイルにシリアル化していますか? 最も可能性の高い原因(私の経験では)は、既存のファイルを上書きしたが、切り捨てていないことです。つまり、itwas200バイト;書き直しましたが、182バイトしかありません。これで、ストリームを終了するストリームの最後に18バイトのガベージがあります。プロトコルバッファを書き換えるときは、ファイルを切り捨てる必要があります。 FileMode
でこれを行うことができます:
using(var file = new FileStream(path, FileMode.Truncate)) {
// write
}
または、SetLength
afterデータの書き込み後:
file.SetLength(file.Position);
その他の考えられる原因
(誤って)ストリームをシリアル化されたものとは異なるタイプに逆シリアル化しています。これが行われていないことを確認するために、会話の両側を再確認する価値があります。
スタックトレースはこのStackOverflowの質問を参照するため、ストリームを(誤って)シリアル化されたものとは異なる型に逆シリアル化した場合にも、この例外を受け取ることができると指摘したいと思います。そのため、会話の両側を再確認して、これが発生していないことを確認する価値があります。
これは、1つのストリームに複数のprotobufメッセージを書き込もうとした場合にも発生する可能性があります。解決策は、SerializeWithLengthPrefixとDeserializeWithLengthPrefixを使用することです。
これが起こる理由:
Protobuf仕様は、かなり少数のワイヤタイプ(バイナリストレージ形式)とデータタイプ(.NETなどのデータタイプ)をサポートしています。さらに、これは1:1ではなく、1対多または多対1でもありません-単一のワイヤタイプを複数のデータタイプに使用でき、単一のデータタイプを複数のワイヤタイプのいずれかでエンコードできます。結果として、あなたはcannotスキーマをすでに知っていない限り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は追加可能な形式で、追加は「マージ」を意味します。 protobufメッセージ独自の長さを知らないであるため、メッセージのデフォルトの読み取り方法は、EOFまで読み取ります。ただし、ここでは2つのdifferentタイプを追加しています。これを読み返すと、最初のメッセージを読み終えたときにdoes not knowなので、読み続けます。 2番目のメッセージのデータに到達すると、「文字列」ワイヤータイプを読み取っていますが、Data1
インスタンスを作成しようとしています。このインスタンスのメンバー1はInt32
です。 「string」とInt32
の間にマップがないため、爆発します。
*WithLengthPrefix
メソッドallow各メッセージの終了位置を知るためのシリアライザー。したがって、Data1
を使用してData2
と*WithLengthPrefix
をシリアル化し、次にData1
メソッドを使用してData2
と*WithLengthPrefix
をシリアル化解除してから、 it 正しく 2つのインスタンス間で受信データを分割し、適切な値を適切なオブジェクトにのみ読み込みます。
さらに、このような異種データを保存する場合、mightを追加して(*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
をstring
Foo
に逆シリアル化し、同じProtoBuf.ProtoException
。
これがアプリケーションのデバッグに役立つことを願っています。
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)
}
また、すべてのサブクラスに[ProtoContract]
属性があることを確認してください。リッチDTOがある場合、見逃すことがあります。
私の場合、これは次のようなものだったために起こりました。
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);
不適切なEncoding
型を使用してバイトを文字列に変換したり、文字列から変換したりするときにこの問題が発生しました。
Encoding.Default
ではなくEncoding.UTF8
を使用する必要があります。
using (var ms = new MemoryStream())
{
Serializer.Serialize(ms, obj);
var bytes = ms.ToArray();
str = Encoding.Default.GetString(bytes);
}