باستخدام protobuf-net ، حصلت فجأة على استثناء حول نوع غير معروف من نوعه

StackOverflow https://stackoverflow.com/questions/2152978

  •  23-09-2019
  •  | 
  •  

سؤال

(هذا هو إعادة نشر لسؤال رأيته في RSS ، ولكن تم حذفه من قبل المرجع. شكل")

فجأة ، أتلقى أ ProtoException عند إلغاء التسلسل والرسالة هي: نوع غير معروف من النوع 6

  • ما هو نوع السلك؟
  • ما هي القيم المختلفة من نوع الأسلاك ووصفها؟
  • أظن أن الحقل يسبب المشكلة ، وكيفية تصحيح هذا؟
هل كانت مفيدة؟

المحلول

أول شيء للتحقق:

هل بيانات Protobuf بيانات الإدخال؟ إذا حاولت تحليل تنسيق آخر (JSON ، XML ، CSV ، ثنائي الشكل) ، أو ببساطة البيانات المكسورة (صفحة نصية "خادم داخلي" HTML ، على سبيل المثال) ، على سبيل المثال) ، ثم لن ينجح.


ما هو نوع السلك؟

إنها علامة ذات 3 بت التي تخبرها (بعبارات عريضة ؛ إنها فقط 3 بتات بعد كل شيء) كيف تبدو البيانات التالية.

كل حقل في المخازن المؤقتة البروتوكول مسبوقة برأس يخبره أي حقل (رقم) يمثله ، ونوع البيانات القادمة ؛ هذا "نوع البيانات" ضروري لدعم الحالة التيغير متوقع البيانات موجودة في الدفق (على سبيل المثال ، قمت بإضافة حقول إلى نوع البيانات في نهاية واحدة) ، لأنها تتيح لـ Serializer معرفة كيفية قراءة تلك البيانات (أو تخزينها في رحلة ذهابًا وإيابًا).

ما هي القيم المختلفة من نوع الأسلاك ووصفها؟

  • 0: عدد صحيح بطول المتغير (ما يصل إلى 64 بت)-Base-128 مشفرة مع MSB التي تشير إلى استمرار (تستخدم كأن أنواع عدد صحيح ، بما في ذلك التعداد)
  • 1: 64 بت - 8 بايت من البيانات (تستخدم ل double, ، أو اختياريا ل long/ulong)
  • 2: الطول المتراجع-اقرأ أولاً عددًا صحيحًا باستخدام ترميز طول المتغير ؛ هذا يخبرك بعدد بايتات البيانات المتابعة (المستخدمة للسلاسل ، byte[], ، "المصفوفات" المعبأة "، وكما هو افتراضي لخصائص / قوائم كائنات الطفل)
  • 3: "Group Group" - آلية بديلة لترميز كائنات الأطفال التي تستخدم علامات البدء/النهاية - التي تم إهمالها إلى حد كبير من قبل Google ، من الأبعاد تخطي حقل كائن الطفل بأكمله نظرًا لأنه لا يمكنك فقط "البحث" عن "مسابقة" غير متوقعة. هدف
  • 4: "المجموعة النهائية" - التوأم مع 3
  • 5: 32 بت - 4 بايت من البيانات (تستخدم ل float, ، أو اختياريا ل int/uint وغيرها من أنواع عدد صحيح صغير)

أظن أن الحقل يسبب المشكلة ، وكيفية تصحيح هذا؟

هل تقوم بتسلسل إلى ملف؟ ال على الأرجح السبب (في تجربتي) هو أنك قمت بإعداد ملف موجود ، لكنك لم تقطعه ؛ أي ذلك كان 200 بايت لقد قمت بإعادة كتابتها ، ولكن مع 182 بايت فقط. يوجد الآن 18 بايت من القمامة في نهاية التيار الخاص بك الذي يعثر عليه. يجب اقتطاع الملفات عند إعادة كتابة المخازن المؤقتة للبروتوكول. يمكنك القيام بذلك مع FileMode:

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

أو بدلاً من ذلك SetLength بعد كتابة بياناتك:

file.SetLength(file.Position);

سبب ممكن آخر

أنت (بطريق الخطأ) تفرط في الدفق في نوع مختلف عن ما تم تسلسله. الأمر يستحق التحقق المزدوج على جانبي المحادثة لضمان عدم حدوث ذلك.

نصائح أخرى

نظرًا لأن Trace Trace يشير إلى سؤال Stackoverflow هذا ، فقد اعتقدت أنني أوضح أنه يمكنك أيضًا تلقي هذا الاستثناء إذا كنت (عن طريق الخطأ) تفضي إلى دفق في نوع مختلف عن ما تم تسلسله. لذلك يستحق التحقق من جانبي المحادثة لضمان عدم حدوث ذلك.

يمكن أن يكون سبب ذلك أيضًا محاولة كتابة أكثر من رسالة protobuf إلى دفق واحد. الحل هو استخدام serializewithlengthprefix و deserializewithlengthprefix.


لماذا يحدث هذا:

تدعم مواصفات protobuf عددًا صغيرًا إلى حد ما من أنواع الأسلاك (تنسيقات التخزين الثنائية) وأنواع البيانات (نوع البيانات .NET etc). بالإضافة إلى ذلك ، هذا ليس 1: 1 ، ولا هو 1: كثير أو كثير: 1-يمكن استخدام نوع سلك واحد لأنواع متعددة من البيانات ، ويمكن تشفير نوع واحد من البيانات عبر أي من أنواع الأسلاك المتعددة . نتيجة لذلك ، أنت لا تستطيع افهم تمامًا جزء protobuf إلا إذا كنت تعرف بالفعل Scema ، حتى تعرف كيفية تفسير كل قيمة. عندما تكون ، على سبيل المثال ، قراءة 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 لا يعرف طوله, ، وبالتالي فإن الطريقة الافتراضية لقراءة الرسالة هي: اقرأ حتى EOF. ومع ذلك ، هنا قمنا بإلحاق اثنين مختلف الأنواع. إذا قرأنا هذا مرة أخرى ، فهو لا يعرف عندما انتهينا من قراءة الرسالة الأولى ، فإنها تستمر في القراءة. عندما يصل إلى البيانات من الرسالة الثانية ، نجد أنفسنا نقرأ "سلسلة" من نوع الأسلاك ، لكننا ما زلنا نحاول ملء أ Data1 مثال ، أي عضو 1 هو Int32. لا توجد خريطة بين "سلسلة" و Int32, ، لذلك ينفجر.

ال *WithLengthPrefix أساليب السماح المسلسل لمعرفة أين تنتهي كل رسالة ؛ لذلك ، إذا قمنا بتسلسل أ Data1 و Data2 باستخدام *WithLengthPrefix, ، ثم تخلص من أ Data1 و 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 ل string Foo الذي يسبب الشيء نفسه ProtoBuf.ProtoException.

آمل أن يساعد هذا شخص ما في تصحيح طلبه.

تحقق أيضًا من أن جميع الفئات الفرعية لديك [ProtoContract] ينسب. في بعض الأحيان يمكنك تفويتك عندما يكون لديك DTO RICH.

إذا كنت تستخدم 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)
}

لقد رأيت هذه المشكلة عند استخدام غير لائق Encoding اكتب لتحويل البايتات داخل وخارج السلاسل.

تحتاج إلى استخدام Encoding.Default و لا Encoding.UTF8.

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

حدث هذا في حالتي لأنني حصلت على شيء من هذا القبيل:

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

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

لذلك كنت في الأساس وضع قاعدة 64 في قائمة انتظار ، وبعد ذلك ، على جانب المستهلك:

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

لذلك على الرغم من نوع كل myqueuitem كان صحيحا ، لقد نسيت أنني حولت سلسلة. كان الحل هو تحويله مرة أخرى:

var bytes = Convert.FromBase64String(myQueueItem);
var stream = new MemoryStream(bytes);
var batch = Serializer.Deserialize<List<EventData>>(stream);
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top