“Invalid field in source data: 0” error with Proto

2020-07-05 05:41发布

问题:

Is anyone aware of any issues when using ProtoBuf-Net to serialize/deserialize between compact framework and the full .Net framework? I have a class called LogData that I am serializing under compact framework 3.5, transmitting to a server (running .Net framework 4.0), which then deserializes. Sometimes it works and sometimes it throws the above error and I have yet to narrow it down to any specific cause. I've done many many tests with different values and can't seem to find any rhyme or reason to when the error occurs. I'm including my classes below (minus the various constructors). I have looked at the byte buffer on either side on multiple occasions and have yet to find a difference in the data being sent over the wire from one side to the other.

[ProtoContract]
public class LogData
{

  [ProtoContract]
  public enum LogSeverity
  {
     [ProtoEnum(Name = "Information", Value = 0)]
     Information,
     [ProtoEnum(Name = "Warning", Value = 1)]
     Warning,
     [ProtoEnum(Name = "Error", Value = 2)]
     Error,
     [ProtoEnum(Name = "Critical", Value = 3)]
     Critical
  }

  [ProtoMember(1)]
  public string UserID { get; set; }
  [ProtoMember(2)]
  public string ComputerName { get; set; }
  [ProtoMember(3)]
  public ExceptionProxy Exception { get; set; }
  [ProtoMember(4)]
  public LogData.LogSeverity Severity { get; set; }
  [ProtoMember(5)]
  public string Source { get; set; }
  [ProtoMember(6)]
  public string Caption { get; set; }
  [ProtoMember(7)]
  public string Description { get; set; }
  [ProtoMember(8)]
  public DateTime TimeOfOccurrence { get; set; }
  [ProtoMember(9)]
  public Guid SessionID { get; set; }
  [ProtoMember(10)]
  public string MethodName { get; set; }
  [ProtoMember(11)]
  public string OSVersion { get; set; }
  [ProtoMember(12)]
  public string Category { get; set; }
  [ProtoMember(13)]
  public string Location { get; set; }
}

[ProtoContract]
public class ExceptionProxy
{

  [ProtoMember(1)]
  public Type ExceptionType { get; set; }
  [ProtoMember(2)]
  public string Message { get; set; }
  [ProtoMember(3)]
  public string StackTrace { get; set; }
  [ProtoMember(4)]
  public ExceptionProxy InnerException { get; set; }

}

Here is my code that does the serialization and sending

  private void WriteLogDataToServer(LogData data)
  {
     using (var client = new TcpClient())
     {
        client.Connect(Host, SignalLineServerPort);
        using (var stream = client.GetStream())
        {
           using (var ms = new MemoryStream())
           {
              Serializer.Serialize<LogData>(ms, data);
              var buffer = ms.GetBuffer();
              int position = 0;
              WriteFrameMarkers(stream);
              byte[] frameLengthBuffer = BitConverter.GetBytes(buffer.Length);
              stream.Write(frameLengthBuffer, 0, IntByteSize);
              while (position < buffer.Length)
              {
                 int length = Math.Min(ChunkSize, buffer.Length - position);
                 stream.Write(buffer, position, length);
                 position += ChunkSize;
              }
           }
        }
        client.Close();
     }         
  }

And this is the code that reads the data on the server

  public override LogData ReadData(NetworkStream stream)
  {
     if (stream.DataAvailable)
     {
        try
        {
           const int chunkSize = 250;
           byte[] buffer = new byte[IntByteSize];
           int messageSize = 0;
           int totalBytesRead = 0;
           LogData data;
           using (var ms = new MemoryStream())
           {
              if (!ReadFrameMarkers(stream))
                 return null;
              totalBytesRead = stream.Read(buffer, 0, IntByteSize);
              if (totalBytesRead != IntByteSize)
                 return null;
              messageSize = BitConverter.ToInt32(buffer, 0);
              totalBytesRead = 0;
              while ((totalBytesRead < messageSize))
              {
                 int bufferSize = Math.Min(chunkSize, messageSize - totalBytesRead);
                 buffer = new byte[bufferSize];
                 int bytesRead = stream.Read(buffer, 0, bufferSize);
                 if (bytesRead != 0)
                 {
                    totalBytesRead += bytesRead;
                    ms.Write(buffer, 0, bytesRead);
                 }
              }
              ms.Seek(0, SeekOrigin.Begin);
              data = Serializer.Deserialize<LogData>(ms);
           }
           return data;
        }
        catch (Exception ex)
        {
           Console.WriteLine(string.Format("Error occurred: {0}", ex.Message));
           return null;
        }
     }
     return null;
  }

回答1:

Easy one: you use:

var buffer = ms.GetBuffer();

And then buffer.Length. That means you are using the oversized, padded buffer. If you do that you need to use ms.Length, which will tell you the actual length. Alternatively, ms.ToArray() may be used, but that involves an extra copy.

My advice: keep using GetBuffer(), but only write ms.Length bytes, not buffer.Length bytes.

Once you have removed these extra incorrect zeros, I expect you'll find it works.



回答2:

I realize the main developer @MarcGravell already answered but I just wanted to share my own $0.02 that helped me on this issue. If I have a fixed size byte[] and get a count of bytes read in return, I can just specify that in the MemoryStream declaration and it solves the issue. Also, as it applies to the OP, don't declare the MemoryStream until you're done reading.

byte[] msg = new byte[4096];
int bytesRead = someStreamClass.Read(msg, 0, 4096);
using (MemoryStream ms = new MemoryStream(msg, 0, bytesRead))
{    
    logData = Serializer.Deserialize<LogData>(ms);
}

@MarcGravell: Thanks for this great library!