Programing

Protobuf-net을 사용하면서 갑자기 알 수없는 와이어 유형에 대한 예외가 발생했습니다.

lottogame 2020. 12. 24. 23:17
반응형

Protobuf-net을 사용하면서 갑자기 알 수없는 와이어 유형에 대한 예외가 발생했습니다.


(이것은 내 RSS에서 본 질문의 재 게시물이지만 OP에 의해 삭제되었습니다.이 질문이 다른 장소에서 여러 번 질문을 봤기 때문에 다시 추가했습니다. 형태")

갑자기 ProtoException역 직렬화 할 때 메시지가 표시되고 다음과 같은 메시지가 표시됩니다. unknown wire-type 6

  • 와이어 형이란?
  • 다른 와이어 유형 값과 설명은 무엇입니까?
  • 필드가 문제를 일으키는 것으로 의심됩니다. 디버깅 방법은 무엇입니까?

먼저 확인해야 할 사항 :

입력 데이터 프로토콜 데이터입니까? 다른 형식 (json, xml, csv, binary-formatter) 또는 단순히 손상된 데이터 (예 : "내부 서버 오류"html 자리 표시 자 텍스트 페이지)를 구문 분석하면 작동하지 않습니다 .


와이어 형이란?

다음 데이터가 어떻게 생겼는지 알려주는 3 비트 플래그입니다 (광범위한 용어로, 결국 3 비트에 불과 함).

프로토콜 버퍼의 각 필드에는 자신이 나타내는 필드 (숫자)와 다음에 오는 데이터 유형을 알려주는 헤더가 접두사로 붙습니다. 이 "어떤 유형의 데이터"는 예상치 못한 데이터가 스트림에 있는 경우를 지원하는 데 필수적입니다 (예 : 한쪽 끝에서 데이터 유형에 필드를 추가 한 경우). 직렬 변환기가 과거를 읽는 방법을 알 수 있기 때문입니다. 데이터 (또는 필요한 경우 왕복을 위해 저장).

다른 와이어 유형 값과 설명은 무엇입니까?

  • 0 : 가변 길이 정수 (최대 64 비트)-연속을 나타내는 MSB로 인코딩 된 base-128 (열거 형을 포함한 정수 유형의 기본값으로 사용됨)
  • 1 : 64 비트 - 데이터의 8 바이트 (사용 double하거나 electively 위한 long/ ulong)
  • 2 : 길이 접두사-먼저 가변 길이 인코딩을 사용하여 정수를 읽습니다. 이것은 따라 오는 데이터의 바이트 수를 알려줍니다 (문자열 byte[],, "포장 된"배열에 사용되며 자식 개체 속성 / 목록의 기본값으로 사용됨 ).
  • 3 : "시작 그룹"-시작 / 종료 태그를 사용하는 하위 개체를 인코딩하는 대체 메커니즘-Google에서 거의 사용하지 않습니다. 예상치 못한 부분을 "검색"할 수 없기 때문에 전체 하위 개체 필드를 건너 뛰는 것이 더 비쌉니다. 목적
  • 4 : "최종 그룹"-3 명과 자매 결연
  • 5 : 32 비트 - 4 바이트의 데이터 (사용 float하거나 electivelyint/ uint및 다른 작은 정수형)

필드가 문제를 일으키는 것으로 의심됩니다. 디버깅 방법은 무엇입니까?

파일로 직렬화하고 있습니까? 대부분 (내 경험) 원인은 기존 파일을 덮어 가지고 있지만, 그것을 절단하지 않은 것입니다; 즉, 200 바이트 였습니다 . 다시 작성했지만 182 바이트에 불과합니다. 이제 스트림 끝에 18 바이트의 가비지가 있습니다. 프로토콜 버퍼를 다시 쓸 때 파일을 잘라야합니다. 다음과 FileMode같이 할 수 있습니다 .

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

또는 데이터를 작성한 SetLength :

file.SetLength(file.Position);

기타 가능한 원인

(실수로) 직렬화 된 것과 다른 유형으로 스트림을 역 직렬화하고 있습니다. 이것이 일어나지 않는지 확인하기 위해 대화의 양쪽을 다시 확인하는 것이 좋습니다.


스택 추적이이 StackOverflow 질문을 참조하기 때문에 직렬화 된 것과 다른 유형으로 스트림을 (우연히) 역 직렬화하는 경우에도이 예외를받을 수 있다고 지적했습니다. 따라서 이것이 일어나지 않도록 대화의 양쪽을 다시 확인하는 것이 좋습니다.


하나 이상의 protobuf 메시지를 단일 스트림에 쓰려는 시도로 인해 발생할 수도 있습니다. 해결책은 SerializeWithLengthPrefix 및 DeserializeWithLengthPrefix를 사용하는 것입니다.


이런 일이 발생하는 이유 :

protobuf 사양은 상당히 적은 수의 와이어 유형 (이진 저장 형식)과 데이터 유형 (.NET 등 데이터 유형)을 지원합니다. 또한 이것은 1 : 1이 아니며 일대 다 또는 다 : 1도 아닙니다. 단일 와이어 유형은 여러 데이터 유형에 사용할 수 있으며 단일 데이터 유형은 여러 와이어 유형을 통해 인코딩 할 수 있습니다. . 결과적으로 scema를 이미 알고 있지 않으면 protobuf 조각을 완전히 이해할 수 없으므로 각 값을 해석하는 방법을 알고 있습니다. Int32데이터 유형을 읽을 때 지원되는 와이어 유형은 "varint", "fixed32"및 "fixed64"일 수 있습니다. 여기서 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);
    }
}

위의 두 메시지는 서로 바로 뒤에 작성됩니다. 합병증은 다음과 같습니다. protobuf는 추가 가능한 형식이며 추가는 "병합"을 의미합니다. protobuf 메시지 는 자신의 길이를 알지 못 하므로 메시지를 읽는 기본 방법은 read until EOF입니다. 그러나 여기에 두 가지 다른 유형 이 추가되었습니다 . 이것을 다시 읽으면 첫 번째 메시지를 언제 읽었 는지 알 수 없으므로 계속 읽습니다. 두 번째 메시지에서 데이터에 도달하면 "문자열"와이어 유형을 읽지 만 Data1멤버 1이 Int32. "문자열"과 사이에 맵이 없으므로 Int32폭발합니다.

The *WithLengthPrefix methods allow the serializer to know where each message finishes; so, if we serialize a Data1 and Data2 using the *WithLengthPrefix, then deserialize a Data1 and a Data2 using the *WithLengthPrefix methods, then it correctly splits the incoming data between the two instances, only reading the right value into the right object.

Additionally, when storing heterogeneous data like this, you might want to additionally assign (via *WithLengthPrefix) a different field-number to each class; this provides greater visibility of which type is being deserialized. There is also a method in Serializer.NonGeneric which can then be used to deserialize the data without needing to know in advance what we are deserializing:

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

Previous answers already explain the problem better than I can. I just want to add an even simpler way to reproduce the exception.

This error will also occur simply if the type of a serialized ProtoMember is different from the expected type during deserialization.

For instance if the client sends the following message:

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

But what the server deserializes the message into is the following class:

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

Then this will result in the for this case slightly misleading error message

ProtoBuf.ProtoException: Invalid wire-type; this usually means you have over-written a file without truncating or setting the length

It will even occur if the property name changed. Let's say the client sent the following instead:

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

This will still cause the server to deserialize the int Bar to string Foo which causes the same ProtoBuf.ProtoException.

I hope this helps somebody debugging their application.


Also check the obvious that all your subclasses have [ProtoContract] attribute. Sometimes you can miss it when you have rich DTO.


If you are using SerializeWithLengthPrefix, please mind that casting instance to object type breaks the deserialization code and causes 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)
}

I've seen this issue when using the improper Encoding type to convert the bytes in and out of strings.

Need to use Encoding.Default and not Encoding.UTF8.

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

This happened in my case because I had something like this:

var ms = new MemoryStream();
Serializer.Serialize(ms, batch);

_queue.Add(Convert.ToBase64String(ms.ToArray()));

So basically I was putting a base64 into a queue and then, on the consumer side I had:

var stream = new MemoryStream(Encoding.UTF8.GetBytes(myQueueItem));
var batch = Serializer.Deserialize<List<EventData>>(stream);

So though the type of each myQueueItem was correct, I forgot that I converted a string. The solution was to convert it once more:

var bytes = Convert.FromBase64String(myQueueItem);
var stream = new MemoryStream(bytes);
var batch = Serializer.Deserialize<List<EventData>>(stream);

ReferenceURL : https://stackoverflow.com/questions/2152978/using-protobuf-net-i-suddenly-got-an-exception-about-an-unknown-wire-type

반응형