环境:Ubantu,proto版本:proto3
protobuf的编译和安装
基于CMake工具编译和安装protobuf,CMake是一个根据CMake语法生成makefile的工具。步骤如下:
- 安装如下工具:
sudo apt-get install autoconf automake libtool curl make
- 下载一个protobuf的发行版本,这里选择protobuf-3.20.3
wget https://github.com/protocolbuffers/protobuf/releases/download/v3.20.3/protobuf-cpp-3.20.3.tar.gz
- 解压:
tar -zxvf protobuf-cpp-3.20.3.tar.gz
- 进入protobuf项目目录下:
cd protobuf-3.20.3/
- 生成配置文件:
./autogen.sh
- 配置环境:
./configure --prefix=xxx
,prefix
选项指定安装的目录 - 编译:
make -j 4
- 安装:
make install
- 刷新动态库:
ldconfig
- 检验安装是否成功:
protoc
,如果输出使用protoc工具的帮助信息则表示安装成功。
简介
- Protocol buffers是一种用于序列化结构数据的机制,序列化后的结构数一般用于网络传输、数据存储。
- Protocol buffers的优势
- 占用存储空间相比于json、xml等更少,因为它使用压缩存储
- 解析速度更快
- 独立于编程语言,Java、Js、Cpp等都可以使用
- 通过自动生成的类优化功能
- Protocol buffers如何工作的
Protocol Buffers的基础语法
- 字段类型
- 标量类型,如下表格所示
.proto type c++ type double double int32 int32 float float int64 int64 uint32 uint32 uint64 uint64 sint32 int32 sint64 int64 fixed32 uint32 fixed64 uint64 bool bool string string bytes string - enum:一个枚举定义示例如下
enum Response { // 第一个枚举常量的值必须是0 SUCCESS = 0; FAILED = 1; }
- 组合message类型:像其他编程语言一样,内嵌类类型的对象。
message SearchResponse { repeated Result results = 1; } message Result { string url = 1; string title = 2; repeated string snippets = 3; }
- 标量类型,如下表格所示
- 在消息定义中,每个字段需要有个字段号,范围从1到536870911
- 字段修饰符:主要有三个,分别是optional、repeated、map
- optional:对于使用optional修饰的字段,如果字段的值没有设置,则序列化时不会包含该字段,反序列化获取该字段的值为默认值。
- repeated:对于repeated修饰的字段可以包含任意数量的值,等同于动态分配的数组
- map:键值对类型
- required:proto 3中已经不支持。
- 字段的默认值:
- string:空字符串
- bool:false
- bytes:空字节数组
- 数字类型如int32等:0
- repeated修饰的字段:默认值为一个空列表
- enum:默认值为枚举中第一个定义的值
- 导入其他proto文件的定义:
import xxx.proto;
- 指定包说明符:一定程度上解决名字冲突,语法为
package xxx
。这个在cpp中等同于声明一个xxx的名称空间。 - 可以嵌套定义message:等同于c++中的内部类
- 定义服务:
- 先在
proto
文件中定义RPC服务接口 - 如果protocol buffer编译器生成cpp代码,则在
proto
文件中指定cc_generic_services
选项的值为true。
// 指明protocol buffer编译器基于RPC接口生成抽象服务代码 option cc_generic_services = true;
- 先在
Protocol Buffers的基本使用
1.步骤如下
- 参照Protocol Buffers的基础语法,在后缀名为
.proto
的文件中定义消息 - 由Protocol buffers的编译器protoc产生对应编程语言的源码(对于cpp语言,产生
.h
和.cc
文件),源码中将包含对消息操作的实用性方法
// 源目录即proto文件所在的目录
// 目标目录即用于存放生成cpp源文件的目录
protoc --proto_path=源目录 --cpp_out=目标目录 PROTO_FILES
// 示例
./protoc --proto_path=../Common --cpp_out=./ ../Common/*.proto
- 在应用中使用步骤2产生的方法。比如说获取字段的值、设置字段的值、将消息序列化到输出流、从输入流中解析消息
2.案例1
- 一个addressbook.proto定义如下
syntax = "proto3";
package tutorial;
import "google/protobuf/timestamp.proto";
// [START messages]
message Person {
string name = 1;
int32 id = 2; // Unique ID number for this person.
string email = 3;
optional uint32 age = 6;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
// 消息中嵌套消息类型
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
repeated PhoneNumber phones = 4;
google.protobuf.Timestamp last_updated = 5;
}
// Our address book file is just one of these.
message AddressBook {
repeated Person people = 1;
}
// [END messages]
- 使用protoc编译器编译proto文件,将产生addressbook.pb.h和addressbook.pb.cc文件
protoc --proto_path =./ --cpp_out=./ ./*.proto
- protoc编译器产生代码的常用方法
- getters:以字段名称作为方法名,此外对于string类型,还有一个
mutable_字段名
的getter
const std::string& name() const; std::string* mutable_name();
- 对于optional修饰的字段,判断是否设置了字段的值
bool has_age() const;
- setters:以set_字段名作为方法名
void set_age(uint32_t value);
- 清空字段的值
void clear_name();
- 标准的方法:用于检查或者操作message
bool IsInitialized() const; // 从另一个消息值改变当前的消息值 void CopyFrom(const Person& from); string DebugString() const; void Clear();
- 序列化:对于一个程序员定义的message,protoc编译器会产生对应的类,并且该类继承于Message类,Message类又继承于MessageLite类。MessageLite类中提供了很多默认方法的实现,比如说SerializeToString、ParseFromString等。
bool SerializeToString(string* output) const; bool SerializeToOstream(ostream* output) const;
- 反序列化(解析)
bool ParseFromString(const string& data); bool ParseFromIstream(istream* input);
- getters:以字段名称作为方法名,此外对于string类型,还有一个
2.案例2
- 一个
login.proto
文件定义如下:
syntax="proto3"; // 声明protobuf的版本
package network.cmd; // 声明代码所在的包
import "common.proto";
// 指明protocol buffer编译器基于RPC接口生成抽象服务代码
option cc_generic_services = true;
// 定义登录请求消息类型
message LoginRequest {
string name = 1;
string password = 2;
}
// 定义登录响应的消息类型
message LoginResponse {
ResultCode code = 1;
bool success = 2;
}
// 定义一个RPC接口
service LoginServiceRpc {
rpc login(LoginRequest) returns(LoginResponse);
}
- 使用protoc编译器编译proto文件,将产生login.pb.h和login.pb.cc文件
- 对于程序员定义的service,编译器产生相应的类A,类A继承自抽象基类Service。另外产生A_Stub类,这个类继承自A,它内部维护了一个RpcChannel,将所有对服务方法的调用转发到RpcChannel,因此程序员需要实现RpcChannel这个抽象接口。这其中包含的继承体系是A_Stub ->A->Service
class RpcChannel {
public:
inline RpcChannel() {}
virtual ~RpcChannel();
// Call the given method of the remote service.
virtual void CallMethod(const MethodDescriptor* method,
RpcController* controller,
const Message* request,
Message* response,
Closure* done) = 0;
private:
// delete copy constructor、operator=
GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(RpcChannel);
};