protobuf c++入门
深入 ProtoBuf - 简介
[翻译] ProtoBuf 官方文档(一)- 开发者指南
[翻译] ProtoBuf 官方文档(二)- 语法指引(proto2)
[翻译] ProtoBuf 官方文档(三)- 语法指引(proto3)
[翻译] ProtoBuf 官方文档(四)- 规范指引
[翻译] ProtoBuf 官方文档(五)- 编码
[翻译] ProtoBuf 官方文档(六)- 相关技术
[翻译] ProtoBuf 官方文档(七)- 第三方组件
[翻译] ProtoBuf 官方文档(八)- (开发)教程
[翻译] ProtoBuf 官方文档(九)- (C++开发)教程
根据我使用中发现的问题,protobuf的使用需要慎重使用ParseFromString来实现反序列化的。
有兴趣深究protobuf反序列化原理的兄台可以瞅瞅序列化之后的内容,好像是如下格式
一个字节的转译字符 | member name| member value如果有一个memory buffer,现在需要反序列化为一个类,万不可使用ParseFromString,隐藏着潜在的bug。如果buffer中有非字符内容,肯定反序列化的结果是有问题的。截断的情况是隐藏的,视buffer内容而定,最关键的一点,发生截断的时候也没有异常。其场景如下代码(错误用法)
char * buf ;...获取内存快内容string str = buf ;proto.ParseFromString(str)
此时需要做如下的操作:
用memory buffer实例化stringstream对象,
调用stringstream的pubsetbuf(basebuf, length)函数将内存块填充进去,注意,此处不可以将buffer赋值给一个string对象,然后用string对象构造一个stringstream。这时候会发生截断。
看到github上面的很多人的使用都是错误的。本来想找一个epoll+protobuf的基础工程的代码的,看来这个工作还得自己去做。
其问题可以简化用以下代码描述。(错误用法)
char * buf ;...string str = buf ;stringstream s(str) ;proto.ParseFromIstream(s);
如下代码段的用法为正确用法
stringstream s;s.rdbuf()->pubsetbuf(buf, length);proto.ParseFromIstream(&s) ;
注意上面的length不能用strlen(buf)来获取。
--------------------------------------------------------------------------------------------------------------------------------
工作中Protobuf有两种常见用法:1,将数据序列化到buffer中,通过消息发送出去;2,将数据序列化到文件中,通过文件进行数据传递。在第一种使用场景下,一般都会注意到数据的大小,因为消息过大会导致发送和接收时处理多个分片,降低效率,几百KB就算多的了;而在第二种场景下不知不觉序列化后的文件就增长到了几十MB。
肆意增大的文件导致解析时执行ParseFromString()失败。一开始没想到是数据太大导致的,反复调试才意识到是数据大小影响了解析结果。在网上搜索发现protobuf有一个默认大小限制,要解析的数据不能超过默认的64MB,超出后解析失败。我奢望着别的反序列化接口没有这个限制,但是事实证明parseFromArray(), ParseFromFileDescriptor()都有这种问题。
大家给出的解决办法就是通过CodedInputStream类的SetTotalBytesLimit()方法提高对数据大小的限制,此处仅以解析文件为例编写示例代码:
#include
#include
DataProtoBuf::DataArray dataArray;
int fd = open("data_paras", O_RDONLY);//文件 data_paras里记录了序列化的protobuf数据
::google::protobuf::io::FileInputStream input(fd));
::google::protobuf::io::CodedInputStream decoder(&input);
decoder.SetTotalBytesLimit(128*1024*1024, -1);//能解析的数据最大为128M
bool success = dataArray.ParseFromCodedStream(&decoder) && decoder.ConsumedEntireMessage();
-----------------------
protobuf是后台开发中,比较常用的数据通信协议。相对于json,具有数据压缩率高等优点。但是,在某些情况下,稍不留神容易用错。
最近使用protobuf的时候,使用了相对陌生的复合类型的赋值。结果用错了,然后就莫名其妙core dump了。
使用的数据类型简化如下:
message Answer
{
optional uint32 choice = 1;
optional uint32 id = 2;
}
message Detail
{
optional Answer answer = 1;
optional uint32 id = 2;
}
message Rsp
{
repeated Detail detail = 1;
}
message Info
{
repeated Answer answer = 1
}
大概的使用过程是这样的:需要在一个for循环里面,把一个Class中的每一个student拷贝到一个局部的Family对象中的owner中。查看了protoc生成的pb.h文件之后,发现有一个set_allocated_owner(::Person* person)这样的函数。所以,我就使用了如下的代码:
//info 是一个已经定义好的Info对象Rsp rsp;for(int index = 0;index 然后就coredump了,用gdb调试的时候,发现一堆和protobuf相关的东西,猜测应该是protobuf用错了,但是想了很久都没有想到是什么。 看到了这篇文章:http://blog.csdn.net/xiaxiazls/article/details/50118161 主要的意思就是,那个temp_ans是局部变量,会在for循环中一个循环结束之后被析构的,然而,set_allocated_answer中放进去的是这个局部变量的指针。所以,后面这个rsp使用的时候,里面的detail的answer的指针已经指向了非法的内存空间了。coredump妥妥的。 如果需要使用set_allocated这种方法,参数里面的指针需要指向一个在使用到这个数据的时候还没有被析构的对象(好绕口)。例如,那个指针参数是new 出来的,或者是在一个更外层的嵌套中定义的。但是,protobuf中会负责析构这个传进去的指针指向的对象,所以,最好传进去一个new出来的对象的地址,因为如果传进去一个原来就定义好的对象的地址,可能因为这个析构,而导致原有对象失效,造成一些潜在的问题(set_allocated_xxx中的allocated已经表明,最好是那种分配出来的对象) 像上面的例子,进行复合类型的赋值,用copyFrom比较好,例子如下:
但是凭借直觉,这种coredump一般都是和指针相关,想到也就这个地方用了指针,就果断google了一发protobuf的set_allocated相关的函数,发现果然是用错了。Rsp rsp;
for(int index = 0;index
---------------------