为啥协议缓冲区 C++ 库不能正确读取二进制对象

Posted

技术标签:

【中文标题】为啥协议缓冲区 C++ 库不能正确读取二进制对象【英文标题】:Why protocol buffer c++ library not reading binary objects properly为什么协议缓冲区 C++ 库不能正确读取二进制对象 【发布时间】:2011-12-09 05:04:46 【问题描述】:

我使用 c++ 程序使用协议缓冲区创建了一个二进制文件。我在 C# 程序中读取二进制文件时遇到问题,因此我决定编写一个小型 c++ 程序来测试读取。

我的proto文件如下

message TradeMessage 
required double timestamp = 1;
required string ric_code = 2;
required double price = 3;
required int64 size = 4;
required int64 AccumulatedVolume = 5;
 

写入协议缓冲区时,我先写入对象类型,然后写入对象长度和对象本身。

coded_output->WriteLittleEndian32((int) ObjectType_Trade); 
coded_output->WriteLittleEndian32(trade.ByteSize()); 
trade.SerializeToCodedStream(coded_output);

现在,当我尝试在我的 c++ 程序中读取同一个文件时,我看到了奇怪的行为。

我的阅读代码如下:

coded_input->ReadLittleEndian32(&objtype);
coded_input->ReadLittleEndian32(&objlen);
tMsg.ParseFromCodedStream(coded_input);
cout << "Expected Size = " << objlen << endl;
cout<<" Trade message received for: "<< tMsg.ric_code() << endl;
cout << "TradeMessage Size = " << tMsg.ByteSize() << endl;

在这种情况下,我得到以下输出

Expected Size = 33
Trade message received for: .CSAP0104
TradeMessage Size = 42

当我写入文件时,我将 trade.ByteSize() 写为 33 个字节,但是当我读取同一个对象时,对象 ByteSize() 是 42 个字节,这会影响其余数据。我不确定这有什么问题。请指教。

问候, 阿洛克

【问题讨论】:

只是为了仔细检查.. 我比较了我的阅读器和编写器项目中的协议缓冲区生成的文件。生成的文件是相同的。所以我想,文件编码由于某种原因而不同。我不明白为什么它不同。 您是否有机会分享您的测试值,以便我检查哪个“更正确”? 还有 - 小点;如果你写了 ([objtype] @MarcGravell 我可以把二进制数据文件邮寄给你吗?恐怕没有其他方法可以从这里向您发送文件。我将按照您的建议对 objtype(和其他一些字段)进行更改。 @MarcGravell,刚刚给你发了一封电子邮件。 【参考方案1】:

这是基于上述情况的猜测:当您使用ParseFromCodedStream 时,您实际上并没有将其限制为您之前找到的objlen;因此,如果流包含比这更多的数据(即,这不是文件的结尾),引擎将尝试继续读取 EOF。您必须将长度限制在您的期望范围内。我不是 C++ 专家,因此无法提供直接指导,但如果这是 C#(使用 protobuf-net):

objType = ProtoReader.DirectReadLittleEndianInt32(file);
len = ProtoReader.DirectReadLittleEndianInt32(file);

// assume GetObjectType returns typeof(TradeMessage) for our objType
Type type = GetObjectType(objType);
msg = RuntimeTypeModel.Default.Deserialize(file, null, type, len, null);

【讨论】:

抱歉回复晚了。我在旅行。所以我用了你的小费,并在阅读时输入了指定的长度。后来我在文档本身中发现了这一点。这个想法是推动长度以在流中读取。 c++代码没问题。但是从我的 C# 代码中读取时我仍然遇到同样的问题 :( 使用上面的代码读取对象,它仍然停止在同一位置并出现以下错误.. ProtoBuf.ProtoException: ProtoBuf.Meta.TypeModel 消耗的字节数不正确.Deserialize(Stream source, Object value, Type type, Int32 length, SerializationContext context) 对于 c# 版本,我使用以下代码 TradeMessage tMsg = Serializer.DeserializeWithLengthPrefix&lt;TradeMessage&gt;(stream, PrefixStyle.Fixed32); 这本来应该可以工作,但它没有工作。在此之后,我编写了一个测试 C++ 程序来检查生成的文件是否正常。 C3 版还是不行。 看起来,即使我在内部函数调用msg = RuntimeTypeModel.Default.Deserialize(file, null, type, len, null); 中指定长度作为参数,protobuf-net 也无法读取 length 字节数。这可能是什么原因? c++ 库能够正确读取对象。谢谢。【参考方案2】:

显然,我在创建二进制文件时犯了一个非常愚蠢的错误。当我将 protobuf 数据写入文件时,我没有以二进制模式打开文件,导致它在中间添加了奇怪的 ascii 字符。这导致使用 protobuf-net 库读取数据时出现问题。问题在这里得到解决。不应该花这么长时间来解决这个问题。

【讨论】:

以上是关于为啥协议缓冲区 C++ 库不能正确读取二进制对象的主要内容,如果未能解决你的问题,请参考以下文章

为啥scanf不能接收键盘输入,被跳过???改成C++的cin>>后便可以接收并继续下去。全部换成C++可以运行。

为啥 c++ ifstream 不能从设备读取?

在协议缓冲区消息中存储二进制数据缓冲区

使用 c++ 时从二进制文件中读取 int 不正确

为啥我不能用 operator>> 读取 fstream 的二进制数据?

Java 中协议缓冲区分隔的 I/O 函数是不是有 C++ 等效项?