سؤال

لدي تطبيقان متصلان بالشبكة يجب أن يرسلا رسائل protobuf-net متسلسلة لبعضهما البعض.يمكنني إجراء تسلسل للكائنات وإرسالها، ومع ذلك، لا أستطيع معرفة كيفية إلغاء تسلسل البايتات المستلمة.

لقد حاولت إلغاء التسلسل باستخدام هذا وفشلت مع NullReferenceException.

// Where "ms" is a memorystream containing the serialized
// byte array from the network.
Messages.BaseMessage message =
  ProtoBuf.Serializer.Deserialize<Messages.BaseMessage>(ms);

أقوم بتمرير رأس قبل البايتات المتسلسلة التي تحتوي على معرف نوع الرسالة، والذي يمكنني استخدامه في بيان التبديل العملاق لإرجاع نوع الطبقة الفرعية المتوقعة.مع الكتلة أدناه، أتلقى الخطأ:System.Reflection.TargetInvocationException ---> System.NullReferenceException.

//Where "ms" is a memorystream and "messageType" is a
//Uint16.
Type t = Messages.Helper.GetMessageType(messageType);
System.Reflection.MethodInfo method =
  typeof(ProtoBuf.Serializer).GetMethod("Deserialize").MakeGenericMethod(t);
message = method.Invoke(null, new object[] { ms }) as Messages.BaseMessage;

إليك الوظيفة التي أستخدمها لإرسال رسالة عبر الشبكة:

internal void Send(Messages.BaseMessage message){
  using (System.IO.MemoryStream ms = new System.IO.MemoryStream()){
    ProtoBuf.Serializer.Serialize(ms, message);
    byte[] messageTypeAndLength = new byte[4];
    Buffer.BlockCopy(BitConverter.GetBytes(message.messageType), 0, messageTypeAndLength, 0, 2);
    Buffer.BlockCopy(BitConverter.GetBytes((UInt16)ms.Length), 0, messageTypeAndLength, 2, 2);
    this.networkStream.Write(messageTypeAndLength);
    this.networkStream.Write(ms.ToArray());
  }
}

هذا الفصل، مع الفئة الأساسية، أقوم بتسلسله:

[Serializable,
ProtoContract,
ProtoInclude(50, typeof(BeginRequest))]
abstract internal class BaseMessage
{
  [ProtoMember(1)]
  abstract public UInt16 messageType { get; }
}

[Serializable,
ProtoContract]
internal class BeginRequest : BaseMessage
{
    [ProtoMember(1)]
    public override UInt16 messageType
    {
        get { return 1; }
    }
}


مُثَبَّت باستخدام اقتراح مارك جرافيل.لقد قمت بإزالة سمة ProtoMember من خصائص القراءة فقط.تحول أيضًا إلى استخدام SerializeWithLengthPrefix.إليك ما لدي الآن:

[Serializable,
ProtoContract,
ProtoInclude(50, typeof(BeginRequest))]
abstract internal class BaseMessage
{
  abstract public UInt16 messageType { get; }
}

[Serializable,
ProtoContract]
internal class BeginRequest : BaseMessage
{
    public override UInt16 messageType
    {
        get { return 1; }
    }
}

لتلقي كائن:

//where "this.Ssl" is an SslStream.
BaseMessage message =
  ProtoBuf.Serializer.DeserializeWithLengthPrefix<BaseMessage>(
    this.Ssl, ProtoBuf.PrefixStyle.Base128);

لإرسال كائن:

//where "this.Ssl" is an SslStream and "message" can be anything that
// inherits from BaseMessage.
ProtoBuf.Serializer.SerializeWithLengthPrefix<BaseMessage>(
  this.Ssl, message, ProtoBuf.PrefixStyle.Base128);
هل كانت مفيدة؟

المحلول

أولاً؛لاستخدام الشبكة، هناك SerializeWithLengthPrefix و DeserializeWithLengthPrefix التي تتعامل مع الطول بالنسبة لك (اختياريًا مع علامة).ال MakeGenericMethod يبدو جيدًا للوهلة الأولى؛وهذا في الواقع يرتبط ارتباطًا وثيقًا بالالتزام المعلق للعمل الذي كنت أقوم به لتنفيذ مكدس RPC:الكود المعلق has an override of DeserializeWithLengthPrefix يأخذ (في الأساس) أ Func<int,Type>, ، لتحليل علامة إلى نوع ما لتسهيل إلغاء تسلسل البيانات غير المتوقعة بسرعة.

إذا كان نوع الرسالة يتعلق بالفعل بالميراث بين BaseMessage و BeginRequest, ، فأنت لا تحتاج إلى هذا؛ينتقل دائمًا إلى نوع العقد الأعلى في التسلسل الهرمي ويعمل في طريقه إلى الأسفل (بسبب بعض تفاصيل الأسلاك).

أيضًا - لم تتح لي الفرصة لاختباره، ولكن ما يلي قد يزعجه:

[ProtoMember(1)]
public override UInt16 messageType
{
    get { return 1; }
}

تم وضع علامة عليه للتسلسل، لكن ليس لديه آلية لتعيين القيمة.ربما هذه هي القضية؟حاول إزالة [ProtoMember] هنا، بما أنني لا أعتقد أن هذا مفيد - فهو (فيما يتعلق بالتسلسل)، إلى حد كبير نسخة مكررة من [ProtoInclude(...)] علامة.

نصائح أخرى

Serializer.NonGeneric.Deserialize(Type, Stream); //Thanks,  Marc.

أو

RuntimeTypeModel.Default.Deserialize(Stream, null, Type); 

هناك طريقة أخرى للتعامل مع هذا وهي استخدام protobuf-net "للرفع الثقيل"، ولكن باستخدام رأس الرسالة الخاصة بك.تكمن مشكلة معالجة رسائل الشبكة في إمكانية اختراقها عبر الحدود.يتطلب هذا عادةً استخدام مخزن مؤقت لتجميع القراءات.إذا كنت تستخدم رأسك الخاص، فيمكنك التأكد من وجود الرسالة بالكامل قبل تسليمها إلى protobuf-net.

كمثال:

لترسل

using (System.IO.MemoryStream ms = new System.IO.MemoryStream())
{
    MyMessage message = new MyMessage();
    ProtoBuf.Serializer.Serialize<BaseMessage>(ms, message);
    byte[] buffer = ms.ToArray();

    int messageType = (int)MessageType.MyMessage;
    _socket.Send(BitConverter.GetBytes(messageType));
    _socket.Send(BitConverter.GetBytes(buffer.Length));
    _socket.Send(buffer);
}

لاستقبال

protected bool EvaluateBuffer(byte[] buffer, int length)
{
    if (length < 8)
    {
        return false;
    }

    MessageType messageType = (MessageType)BitConverter.ToInt32(buffer, 0);
    int size = BitConverter.ToInt32(buffer, 4);
    if (length < size + 8)
    {
        return false;
    }

    using (MemoryStream memoryStream = new MemoryStream(buffer))
    {
        memoryStream.Seek(8, SeekOrigin.Begin);
        if (messageType == MessageType.MyMessage)
        {
            MyMessage message = 
                ProtoBuf.Serializer.Deserialize<MyMessage>(memoryStream);
        }
    }
}

سيتم "تجربة" الطريقة الأخيرة على المخزن المؤقت المتراكم حتى تتوفر بيانات كافية.بمجرد استيفاء متطلبات الحجم، يمكن إلغاء تسلسل الرسالة.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top