Protobuf是什么?
类似于json的一种数据格式,独立于语言,而且是二进制方式,所以比json更快,而且还可以直接存储一些图、树
序列化和反序列化
持久化(存到磁盘硬盘)领域中,数据存到磁盘叫序列化,从磁盘读取出来叫反序列化
网络传输领域中,数据块转字符串叫序列化,对端把字符串解析为数据块叫反序列化
客户端和服务端如何通信
比如一个结构体三个数据依次发送出去,接收端可能一下接收了三个,是无法处理的(不知道边界)
第二种情况,一次性发送出去,添加标记,这样接收端可以一次性解封装,常采用TLV (Tag Length Value)
Protobuf到底是什么
类似于刚刚说的TLV结构,是一种组织数据的格式,可以把若干数据组织成一块—> 序列化为字符串 –> 发送 –> 字符串被反序列化 –> 解析数据
Protobuf怎么安装?
只演示Linux下的安装过程
- github下载tar.gz包
https://github.com/protocolbuffers/protobuf/releases/tag/v21.12
我这里下载的21.12版本,比较老了,但是基本功能都能用
- 登录Linux服务器安装
先上传到Linux上,一般的ssh工具如Mobaxterm都有这个功能
tar -zxvf your_protobuf_package.tar.gz -C /path/to/you_want
cd /path/to/you_want
- 需要安装c++编译器,如果其他语言版本的也要安装对应编译器,c++我用的g++编译器!
./configure
make
sudo make install
protoc --version
Protobuf如何使用?
简单示例
- 确定要序列化的数据,手动改成protobuf格式
Protobuf类型 | c++类型 | 备注 |
---|---|---|
double | double | 64位浮点数 |
float | float | 32位浮点数 |
int32 | int | 32位整数 |
int64 | long | 64位整数 |
uint32 | unsigned int | 32位无符号整数 |
uint64 | unsigned long | 64位无符号整数 |
sint32 | signed int | 32位整数,处理负数效率高于int32 |
sint64 | signed long | 64位整数,处理负数效率高于int64 |
bool | bool | 布尔 |
string | string | 字符串必须是utf-8或7-bit ASCII编码 |
bytes | string | 多字节语言,比如中文字符,用这个更好 |
enum | enum | 枚举 |
message | object of class | 自定义消息类型 |
- 根据proto语法,写入后缀proto文件中
test.proto
[root@hcss-ecs-9452 test_proto]# cat test.proto /* struct Person { string name; string sex; int age; } */ syntax = "proto3"; message Person { string name = 1; bytes sex = 2; int32 age = 3; }
- protoc命令把proto文件转成c++,包含一个xxx.pb.cc和xxx.pb.h
protoc ./test.proto --cpp_out=./
[root@hcss-ecs-9452 test_proto]# [root@hcss-ecs-9452 test_proto]# ls test.proto [root@hcss-ecs-9452 test_proto]# protoc ./test.proto --cpp_out=./ [root@hcss-ecs-9452 test_proto]# ls test.pb.cc test.pb.h test.proto [root@hcss-ecs-9452 test_proto]#
- 把产生的c++文件添加到项目中,根据文件中API实现序列化和反序列化
节选test.pb.h ,这部分就是protobuf给出的对name这个数据的处理方式,比如清空 设置 查询 根据地址修改等
enum : int { kNameFieldNumber = 1, kSexFieldNumber = 2, kAgeFieldNumber = 3, }; // string name = 1; void clear_name(); const std::string& name() const; template <typename ArgT0 = const std::string&, typename... ArgT> void set_name(ArgT0&& arg0, ArgT... args); std::string* mutable_name(); PROTOBUF_NODISCARD std::string* release_name(); void set_allocated_name(std::string* name); private: const std::string& _internal_name() const; inline PROTOBUF_ALWAYS_INLINE void _internal_set_name(const std::string& value); std::string* _internal_mutable_name();
- 编写makefile
这里需要注意,protobuf大量使用c++11特性,但是centOS默认安装的是g++4.8.5编译器,可能不支持,需要在编译选项中-std=gnu++11
# 目标文件 TARGET = run # 源文件 SRC = run.cpp test.pb.cc # 编译器 CC = g++ # 编译选项 CFLAGS = -I. -lprotobuf -std=gnu++11 # 默认规则 all: $(TARGET) # 依赖关系 $(TARGET): $(SRC) $(CC) $(CFLAGS) $^ -o $@ # 编译.proto文件生成C++源文件和头文件 test.pb.cc test.pb.h: test.proto protoc --cpp_out=. $< # 清理规则 clean: rm -f $(TARGET) test.pb.cc test.pb.h # 防止make删除中间文件 .PHONY: all clean
- 编译运行查看
可能要先设置一下环境变量
echo $LD_LIBRARY_PATH # 如果已经设置过/usr/local/lib就不用往下走了 echo 'export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib' >> ~/.bashrc source ~/.bashrc echo $LD_LIBRARY_PATH
[root@hcss-ecs-9452 test_proto]# ls Makefile run.cpp test.pb.cc test.pb.h test.proto [root@hcss-ecs-9452 test_proto]# make g++ -I. -L/user/local/lib -lprotobuf -std=gnu++11 run.cpp test.pb.cc -o run [root@hcss-ecs-9452 test_proto]# ./run name:Jack sex:女 age:18
中括号写选项,逗号隔开,写在字段值后面!!
-
枚举里加选项 optional allow_alias = true , 然后枚举内就可以重复,比如enum xxx
-
oneof 只能在message里用,只起到分组的作用,oneof{} 里的值只生效一个(最后一个设置的)
复杂应用
嵌套复合数据类型
新增一个proto文件,后续步骤和上面一致
- 嵌套结构体直接加,嵌套数组前面加repeated就行,调用时需注意,数组要先add_xxx, set_xxx(0, xx)
[root@hcss-ecs-9452 test_proto]# cat testComplex.proto syntax = "proto3"; message Addr { int32 door_num = 1; bytes addr = 2; } message Person { repeated string name = 1; bytes sex = 2; int32 age = 3; Addr ad = 4; }
[root@hcss-ecs-9452 test_proto]# cat run.cpp #include "testComplex.pb.h" int main(){ Person p1; p1.add_name(); p1.set_name(0, "Jack"); p1.add_name("Tom"); p1.add_name("sb"); p1.set_sex("女"); p1.set_age(18); p1.mutable_ad()->set_addr("陕西省西安市雁塔区"); p1.mutable_ad()->set_door_num(999); std::string output; p1.SerializeToString(&output); Person p2; p2.ParseFromString(output); //std::cout << "name:" << p2.name() << std::endl; std::cout << "sex:" << p2.sex() << std::endl; std::cout << "age:" << p2.age() << std::endl; std::cout << "addr:" << p2.ad().addr() << p2.ad().door_num() << "号" << std::endl; int size = p2.name_size(); for(int i = 0; i < size; ++i) { std::cout << "第" << i << "个名字是" << p2.name(i) << std::endl; } return 0; }
使用枚举
[root@hcss-ecs-9452 test_proto]# cat testComplex.proto syntax = "proto3"; enum Color { red = 0; //第一个必须是0 green = 5; //后面的不必123 blue = 6; } message Addr { int32 door_num = 1; bytes addr = 2; } message Person { repeated string name = 1; bytes sex = 2; int32 age = 3; Addr ad = 4; Color color = 9; //这里也可以不写567 } [root@hcss-ecs-9452 test_proto]# cat run.cpp #include "testComplex.pb.h" int main(){ Person p1; p1.add_name(); p1.set_name(0, "Jack"); p1.add_name("Tom"); p1.add_name("sb"); p1.set_sex("女"); p1.set_age(18); p1.mutable_ad()->set_addr("陕西省西安市雁塔区"); p1.mutable_ad()->set_door_num(999); p1.set_color(Color::red); std::string output; p1.SerializeToString(&output); Person p2; p2.ParseFromString(output); //std::cout << "name:" << p2.name() << std::endl; std::cout << "sex:" << p2.sex() << std::endl; std::cout << "age:" << p2.age() << std::endl; std::cout << "addr:" << p2.ad().addr() << p2.ad().door_num() << "号" << std::endl; int size = p2.name_size(); for(int i = 0; i < size; ++i) { std::cout << "第" << i << "个名字是" << p2.name(i) << std::endl; } std::cout << "Color:" << p2.color() << std::endl; return 0; }
proto中导入其他proto文件
import "/path/xxx.proto"
命名空间
package xxx
-
当其他proto文件调用时:
xxx.Person p
-
上述命令会转换为cc文件的namespace
原文链接:https://www.cnblogs.com/xsl-blogs/p/18223922
原创文章,作者:优速盾-小U,如若转载,请注明出处:https://www.cdnb.net/bbs/archives/32231