1. protocolbuffer(以下简称PB)是google 的一种数据交换的格式,它独立于语言,独立于平台。google 提供了多种语言的实现:java、c#、c++、go 和 python,每一种实现都包含了相应语言的编译器以及库文件。由于它是一种二进制的格式,比使用xml进行数据交换快许多。可以把它用于分布式应用之间的数据通信或者异构环境下的数据交换。作为一种效率和兼容性都很优秀的二进制数据传输格式,可以用于诸如网络传输、配置文件、数据存储等诸多领域。
2.protocol buffer git下载地址 https://github.com/protocolbuffers/protobuf
C++ protocol buffer 文档地址 https://developers.google.com/protocol-buffers/docs/cpptutorial
3. 首先,你需要通过在.proto文件中定义protocol buffer的message类型来指定你想要序列化的数据结构,每一个protocol buffer message是一个逻辑上的信息记录,它包含一系列的键值对。这里展示一个最基本的.ptoto文件的例子,它定义了一个包含Student信息的message:
message Student{
required string name = 1;
required int32 id = 2;
optional string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
required string number = 1;
optional PhoneType type = 2 [default = HOME];
}
repeated PhoneNumber phone = 4;
}
message的格式非常简单--每一个message类型都有一个或多个带有唯一编号的字段,每一个字段有一个字段名和一个字段类型,字段类型可以是数值类型(比如整形或浮点型)、booleans(布尔类型)、strings(字符串类型)、raw bytes、甚至还可以是其他的protocol buffer message类型,这允许你可以分层次的组织你的数据结构。你可以单独指定每一个字段为optional fields(可选字段)、required fields(必须字段)、repeated fields(可重复字段)。
一旦定义了你的message,你就可以根据你所使用的语言(如JAVA、C++、Python等)使用protocol buffer提供的编译工具编译.proto文件生成数据访问类。这些类为每一个字段都提供了简单的访问器(比如name()和set_name()),同时还提供了将整个结构化数据序列化为原始字节数据以及从原始字节数据反序列化为结构化数据的方法(译注:C++中称之为函数)。例如,如果你使用的语言是C++,运行编译器编译上述的例子将生成一个名为Student的类,在你的应用程序中你可以使用这个类来填充、序列化和反序列化Student protocol buffer messages。之后你可能会写下如下类似的代码(译注:序列化):
Student student;
student.set_name("Zhang san");
student.set_id(111);
student.set_email("zhang@example.com");
fstream output("file", ios::out | ios::binary);
student.SerializeToOstream(&output);
之后,你可以将你的message读回(反序列化):
fstream input("file", ios::in | ios::binary);
Student student;
student.ParseFromIstream(&input);
cout << "Name: " << student.name() << endl;
cout << "E-mail: " << student.email() << endl;
你可以向你的message中添加新的字段而不会破坏前向兼容性;在解析时旧的二进制文件会简单的忽略掉新字段,所以,如果你的通信协议中使用protocol buffers作为数据交换格式,那么你可以扩展你的协议而不用担心会打乱现有的代码。
4.语法格式
1)// Filename: student.proto 这一行是注释,语法类似于C++
2)syntax="proto2"; 表明使用protobuf的编译器版本为v2,目前最新的版本为v3
3)package student; 声明了一个包名,用来防止不同的消息类型命名冲突,类似于 namespace
4)import "src/student.proto"; 导入了一个外部proto文件中的定义,类似于C++中的 include
5)message 是Protobuf中的结构化数据,类似于C++中的类,可以在其中定义需要处理的数据
6)required string name = 1; 声明了一个名为name,数据类型为string的required字段,字段的标识号为1
7)protobuf一共有三个字段修饰符:
- required:该值是必须要设置的;
- optional :该字段可以有0个或1个值(不超过1个);
- repeated:该字段可以重复任意多次(包括0次),类似于C++中的list
8)string是一种标量类型,protobuf的所有标量类型请参考文末的标量类型列表,name是字段名,1 是字段的标识号,在消息定义中,每个字段都有唯一的一个数字标识号,这些标识号是用来在消息的二进制格式中识别各个字段的,一旦开始使用就不能够再改变。
9)Student内部声明了一个enum和一个message,这类似于C++中的类内声明,Student外部的结构可以用 Student.PhoneType 的方式来使用PhoneType。当使用外部package中的结构时,要使用 pkgName.msgName.typeName 的格式,每两层之间使用'.'来连接,类似C++中的"::"。
10)optional PhoneType type = 2 [default = HOME]; 为type字段指定了一个默认值,当没有为type设值时,其值为HOME。
5. 生成C++文件
protoc是proto文件的编译器,目前可以将proto文件编译成C++、Java、Python三种代码文件,命令格式如下:
protoc -I=$SRC_DIR --cpp_out=$DST_DIR /path/to/file.proto
上面的命令会生成xxx.pb.h 和xxx.pb.cc两个C++文件。
6.常用API
protoc为message的每个required字段和optional字段都定义了以下几个函数(不限于这几个):
TypeName xxx() const; //获取字段的值
bool has_xxx(); //判断是否设值
void set_xxx(const TypeName&); //设值
void clear_xxx(); //使其变为默认值
为每个repeated字段定义了以下几个:
TypeName* add_xxx(); //增加结点
TypeName xxx(int) const; //获取指定序号的结点,类似于C++的"[]"运算符
TypeName* mutable_xxx(int); //类似于上一个,但是获取的是指针
int xxx_size(); //获取结点的数量
另外,下面几个是常用的序列化函数:
bool SerializeToOstream(std::ostream * output) const; //输出到输出流中
bool SerializeToString(string * output) const; //输出到string
bool SerializeToArray(void * data, int size) const; //输出到字节流
与之对应的反序列化函数:
bool ParseFromIstream(std::istream * input); //从输入流解析
bool ParseFromString(const string & data); //从string解析
bool ParseFromArray(const void * data, int size); //从字节流解析