protobuf是一种序列化方法.
1. message字段包括以下几种情况
(1)singular:包涵该字段一次或则零次
(2)repeated:可以重复任意多次
2.可以在.proto文件中定义多种message类型。但是当单个文件定义大量不同依赖关系的messages时,会导致依赖性膨胀。
建议每个.proto文件包含尽可能少的message类型。
3.对于C++,编译器从每个.proto生成一个.h和.cc文件,其中包含文件中描述的每种message类型对应的类。
4.保留值:如果通过删除枚举条目或则注释掉来更新枚举类型,未来的用户可能对message做出自己的修改或者重复使用这些值。如果以后加载相同的.proto的旧版本,可能导致数据损坏和隐藏错误。
5.如果希望message格式具有额外的字段,但仍然希望使用旧格式创建代码。
(1)不要更改现有字段和字段编号
(2)添加的新字段都应该是optional或则repeated
(3)在更新的message类型中不再使用字段编号,就可以删除非必填字段。可以将字段编号保留(Reserved),以便将来你的.proto的用户不会不小心重用了这个编号。
(4)optional和repeated兼容
5.扩展Extensions
通过扩展,可以声明message中的一些列字段编号用于第三方扩展。扩展名是那些未由原始.proto文件定义的字段的占位符。
6.Oneof
如果你的message包含许多可选字段,并且最多只能同时设置其中一个字段,则可以使用Oneof功能强制执行此行为并节省内存。
7.Packages
可以optional可选的包说明添加到.proto文件,防止protocol message类型之间的名称冲突。
package foo.bar;
message open{ ...}
可以在定义message类型的字段时使用包说明符
message Foo{
requird foo.bar.Open open=1;
}
8.定位服务
如果将messag类型与RPC(远程过程调用)系统一起使用,则可以在.proto文件中定义RPC服务接口,protocol buffer编译器将使用你选择的语言生成服务接口代码和存根。如果定义了一个RPC服务,其中一个获取SearchRequest并返回SearchResponse的方法,可以在.proto中定义。
service SearchService{
rpc Search(SearchRequest) returns (SearchResponse);
}
protocol编译器将生成一个名为SearchService的抽象接口和相应的“存根”实现。RpcChannel是一个抽象接口,必须根据自己的RPC系统自行定义。如果实现一个RpcChannel,它将message序列化并通过HTTP将其发送到服务器。
using google::protobuf;
protobuf::RpcChannel* channel;
protobuf::RpcController* controller;
SearchService* service;
SearchRequest request;
SearchResponse response;
void DoSearch(){
channel=new MyRpcChannel("somehost.example.com:1234");
controller=new MyRpcController;
service=new SearchService::Stub(channel);
request.set_query("protocol buffers");
service->Search(controller,request,response,protobuf::NewCallback(&Done));
}
void Done()
{
delete service;
delete channel;
delete controller;
}
在服务端,可以实现注册服务的RPC服务器。
using google::protobuf;
class ExampleSearchService:public SearchService{
public:
void Search(protobuf::RpcController* controller,
const SearchRequest* request,
SearchResponse* response,
protobuf::Closure* done){
if(request->query()=="google"){
response->add_result()->set_url("http://www.google.com");
}else if(request->query()=="protocol buffers"){
response->add_result()->set_url("http:...");
}
done->Run();
}
};
int main()
{
MyRpcServer server;
protobuf::Service* service=new ExampleSearchService;
service.ExportOnPort(1234,service);
server.Run();
delete service;
return 0;
}
9.使用其它消息类型
消息类型可以作为字段类型,当我们需要在SearchReponse消息中包含Result消息,可以将Result消息类型的定义放在同一个.proto文件中同时在SearchResponse消息中指定一个Result类型字段。
10.Map映射表
如果需要创建关联映射表作为定义的数据的一部分,protocol buffers提供了快捷的语法。
map<key_type,value_type>map_field =N;
11.Message和字段命名
使用驼峰命名法命名message,如:ServerRequest
使用下划线命名字段,如:shen_name
message ServerRequest{
required string shen_name=1;
}
生存的访问器如下所示:
const string & shen_name(){...}
void set_shen_name(const string& x){...}
12 枚举Enums
使用驼峰命名法命名枚举类型,使用"大写_下划线_大写"的方式命名枚举值。
enum Foo{
FIRST_VALUE=0;
SECOND_VALUE=1;
}
13多消息(message)流
如果将多条消息(Message)写入单个文件或流,则需要跟踪一条消息的结束位置和开始位置。解决的方法是在每条消息本身内容之前记录消息的大小或者长度。当重新读取消息时,可读取其大小,读取对应字节到单独的缓冲区,并解析消息内容。
1.TLV格式:Tag-length-Value,Tag作为该字段的唯一标识,Length代表Value数据域的长度,
最后的Value是数据本身。
2.每个message由一个个字段组成,每个字段可以划分为:Tag-[length]-Value。
3.Tag采用Varints编码方案进行编码.针对sint32、sin64采用sint64编码。
field_number:message定义字段时指定的字段编码。
wire_type:Protobuf编码类型,根据这个类型可以选择不同的Value编码方案。
4.每个field根据类型可以有2种格式:
Tag-Length-Value:Length-delimited编码类型使用这几个结构
Tag-Value:编码类型表中Varint、64-bit、32-bit使用这种结构。
5.Varints编码规则:
(1)在每个字节开头的bit设置了msb,标识是否需要继续读取下一个字节。
(2)存储数字对应的二进制补码。
(3)补码的低位排在前面。
通过Varints可以让小的数字用更少的字节表示,从而提高了空间利用率。
6.protobuf序列化使用过程:
(1)定义.proto文件
(2)protoc编译器编译.proto文件生成一系列接口代码
(3)调用生成的接口实现对.proto文件的字段的读取以及message对象的序列化、反序列化方法。
7.protobuf序列化的几个关键函数:
ByteSizeLong:计算对象序列化所需要的空间大小,在内存中开辟相应大小的空间。
WriteTagToArray:将Tag值写入到之前开辟的内存中
WriteStringWithSizeToArray:将Length+Value值写入到之前开辟的内存中。