protobuf:从C#中读取C++中的消息

lor*_*ger 7 c# protocol-buffers protobuf-net

我将读取从C#服务器发送的C++客户端中连续存储的消息.我希望我能读出这样一条消息的大小:

google::protobuf::uint32 m;
coded_input->ReadVarint32(&m);
cout << m << endl;
Run Code Online (Sandbox Code Playgroud)

然后我想阅读消息:

Person person;
CodedInputStream::Limit limit = coded_input->PushLimit(m);
person.ParseFromCodedStream(coded_input);
coded_input->PopLimit(limit);
Run Code Online (Sandbox Code Playgroud)

C#代码如下:

var person = new Person { Id = 123456, Name = "Fred", Address = new Address { Line1 = "Flat 1", Line2 = "The Meadows ar gar? " } };
Stream str = new NetworkStream(socket);
Serializer.SerializeWithLengthPrefix(str, person, PrefixStyle.Fixed32);
Run Code Online (Sandbox Code Playgroud)

它不起作用.

我得到C++错误(43是cout << m << endl;的结果)(id,name,address都是Person中的所有字段)

43

libprotobuf错误google/protobuf/message_lite.cc:123]无法解析"Person"类型的消息,因为它缺少必填字段:id,name,address

当我没有阅读变体并直接解析消息时coded_input,一切都很好(当我更改SerializeWithLengthPrefix为服务器代码中的序列化时).但是我需要一种方法来区分连续的消息,所以我需要知道我要阅读的消息的大小.我只是不知道如何发送大小.

我试过了:

Stream buf = new MemoryStream();
Serializer.Serialize(buf, person);
ProtoWriter writer = new ProtoWriter(str, null);
ProtoWriter.WriteInt32((int)buf.Length, writer);
Run Code Online (Sandbox Code Playgroud)

但后来我得到:

未处理的异常:ProtoBuf.ProtoException:无效的序列化操作,在ProtoBuf.ProtoWriter.WriteInt32(Int32值,ProtoBuf.ProtoWriter writer)[0x00000]的位置0处使用wire-type None,位于:0
,protobuftest.MainClass.Main(System.String [ ] arg)[0x00097] /home/lorddidger/studia/csharp/protobuf-test/protobuf-test/Main.cs:31

我无法以这种方式发送任何int.怎么了?


更新:实际上,我的目标是找到一种使用protobuf传输整数(大小)的方法.我需要任何示例(包括C++和C#)如何处理,因为我不了解protobuf中的所有细节.

我注意到SerializeWithLengthPrefix发送前缀(4 uint32)看起来像:size 0 0 0.大小是序列化后消息的字节数(我猜).我以为PrefixStyle.Fixed32说消息之前只有一个uint32,但有4个!

最后,我认为你应该使用ProtoWriter.WriteInt32((int)buf.Length,writer)来传输整数,因为我遵循了互联网somwhere的建议,我想这是一个与C++相关的解决方法.现在我看到你不应该写varints - 它们与引擎有关,而且要花时间去复杂.

我应该发送带有int的消息吗?我应该如何区分下一条消息,即大小存储在第一条消息中?

我看到C#可以使用前缀,但是C++和C#的方式是否与消息的状态大小相当?否则,没有**方法来排队消息,我会觉得不合理.

Mar*_*ell 3

您的第一个示例仅包含长度;“带有长度前缀”实际上在 protobuf 兼容的流中进行编码。

如果您从 C++ 解码,请读取两个varints;第一个是字段编号和线路类型;第二是长度。第一个被打包为 3 位线类型,其余的是字段号。您也许还可以指定要跳过的字段 0 - 我无法在不检查的情况下回忆起来。


更新; 我检查了这一点,写入不带字段号的数据(只是一个 varint 长度前缀),然后使用两个不同的 API 在 C# 中读回它 - 单独使用 Deserialize,并通过 DeserializeItems 作为可枚举块。我必须修复后者中的一个错误,但以下内容将从下一个代码推送开始工作(注意:只有读取代码有修复,所以如果您使用 C# 编写并使用 C++ 读取,这不会影响您):

using (var ms = new MemoryStream())
{
    // write data with a length-prefix but no field number
    Serializer.SerializeWithLengthPrefix(ms, new Foo { Bar = 1 }, PrefixStyle.Base128, 0);
    Serializer.SerializeWithLengthPrefix(ms, new Foo { Bar = 2 }, PrefixStyle.Base128, 0);
    Serializer.SerializeWithLengthPrefix(ms, new Foo { Bar = 3 }, PrefixStyle.Base128, 0);

    ms.Position = 0;
    Assert.AreEqual(9, ms.Length, "3 lengths, 3 headers, 3 values");

    // read the length prefix and use that to limit each call
    TypeModel model = RuntimeTypeModel.Default;
    int len, fieldNumber, bytesRead;
    List<Foo> foos = new List<Foo>();
    do
    {
        len = ProtoReader.ReadLengthPrefix(ms, false, PrefixStyle.Base128, out fieldNumber, out bytesRead);
        if (bytesRead <= 0) continue;

        foos.Add((Foo)model.Deserialize(ms, null, typeof(Foo), len));

        Assert.IsTrue(foos.Count <= 3, "too much data!");
    } while (bytesRead > 0);

    Assert.AreEqual(3, foos.Count);
    Assert.AreEqual(1, foos[0].Bar);
    Assert.AreEqual(2, foos[1].Bar);
    Assert.AreEqual(3, foos[2].Bar);

    // do it using DeserializeItems
    ms.Position = 0;

    foos.Clear();
    foreach (var obj in model.DeserializeItems<Foo>(ms, PrefixStyle.Base128, 0))
    {
        foos.Add(obj);
        Assert.IsTrue(foos.Count <= 3, "too much data!");
    }
    Assert.AreEqual(3, foos.Count);
    Assert.AreEqual(1, foos[0].Bar);
    Assert.AreEqual(2, foos[1].Bar);
    Assert.AreEqual(3, foos[2].Bar);
}
Run Code Online (Sandbox Code Playgroud)