protobuf-net을 사용하여 알 수 없는 유형 역직렬화
-
21-08-2019 - |
문제
직렬화된 protobuf-net 메시지를 서로 보내야 하는 2개의 네트워크 앱이 있습니다.객체를 직렬화하여 보낼 수는 있지만, 수신된 바이트를 역직렬화하는 방법을 알 수 없습니다..
이것을 역직렬화하려고 했으나 NullReferenceException으로 인해 실패했습니다.
// Where "ms" is a memorystream containing the serialized
// byte array from the network.
Messages.BaseMessage message =
ProtoBuf.Serializer.Deserialize<Messages.BaseMessage>(ms);
예상되는 하위 유형을 반환하기 위해 거대한 스위치 문에서 사용할 수 있는 메시지 유형 ID가 포함된 직렬화된 바이트 앞에 헤더를 전달합니다.아래 블록을 사용하면 오류가 발생합니다.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; }
}
}
결정된 Marc Gravell의 제안을 사용합니다.읽기 전용 속성에서 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);
}
}
}
후자의 방법은 충분한 데이터가있을 때까지 축합기 버퍼에서 "시도"됩니다. 크기 요구 사항이 충족되면 메시지를 사로화 할 수 있습니다.