Protobuf:基本概念与使用流程
- 基本概念
- Linux 安装
- 使用流程
- .proto文件
- 编译
- 使用
- 运行机制
基本概念
在进行网络编程时,经常需要进行数据传输,只有双方主机都保证数据格式的一致性,才能保证数据被正常解析。这个过程称为序列化
与反序列化
,当前主流的标准有json
、xml
等,而protobuf
就是其中一个数据格式的标准。
json
和xml
都是人类可视化的序列化形式,存储的都是字符串,哪怕没有程序解析都可以直接读取。
比如一个Person
类,分别用json
和xml
序列化的结果:
json
:
{
"name" : "John Doe",
"age" : 30,
"email" : "john.doe@example.com"
}
xml
:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Person>
<name>John Doe</name>
<age>30</age>
<email>john.doe@example.com</email>
</Person>
哪怕没有学习过相关语法,也可以很容易读取出序列化后的内容所包含的信息。
而protobuf
存储的方式则是二进制形式,序列化后无法直接读取,只能看到乱码,必须由程序完成解析。相应的,protobuf
的效率会比上面两者高很多,因为protobuf
对数据的压缩效率极高,牺牲可视化换取高效,在一些需要高效传输数据的场景很有用。
一个序列化相关的协议,想要在计算机语言中使用,自然要配备相关的库,比如JavaScript
原生对json
的支持,C++
通过jsoncpp
库对json
的支持。
protobuf
由谷歌开发,谷歌也编写了相关的库,其可以支持C++
、Java
、Python
、Go
、C#
、JavaScript
、PHP
等主流语言,本博客以C++
讲解protobuf
。
Linux 安装
以ubuntu
为例,接下来讲解如何在Linux
上安装protobuf
。
- 安装依赖库
protobuf
有很多依赖的库文件,执行以下指令下载所有所需的库:
apt-get install autoconf automake libtool curl make g++ unzip -y
如果是centOS
,则把apt-get
换成yum
,另外的需要root
权限。
- 下载安装包
protobuf
的下载连接:
https://github.com/protocolbuffers/protobuf/releases
在页面中,会有很多的版本,每个版本下面的assert
内有如下的安装包:
此时根据自己的系统架构,以及想要的版本选择对应的安装包,比如Linux x86_64
架构,安装28.2
版本,执行:
wget https://github.com/protocolbuffers/protobuf/releases/protoc-28.2-linux-x86_64.zip
当然也有很多其他方式,只要获取到压缩包就行。
- 解压安装包
最常见的安装位置是/usr/local/bin
,里面存储了很多可执行文件,创建一个protobuf
的目录,用于存放对应的可执行文件:
mkdir -p /usr/local/bin/protoc
随后把压缩包的内容解压到对应目录下:
unzip protoc-28.2-linux-x86_64.zip -d /usr/local/bin/protoc
- 添加环境变量
为了protobuf
可以直接执行,要把protobuf
的bin
的路径添加到PATH
环境变量中,打开~/.bashrc
文件,追加以下语句到末尾:
export PATH=$PATH:/usr/local/bin/protoc/bin
最后重新加载配置文件:
source ~/.bashrc
- 检查安装
protoc --version
如果出现对应的版本号,那么安装成功了。
使用流程
.proto文件
就像C语言
基于.c
文件,C++
基于.cpp
文件,protobuf
是基于.proto
文件使用的。在.proto
文件内部基于proto3
语法编写文档,就可以自动生成其他语言的代码。
创建一个test.proto
文件,在内部写以下内容:
syntax = "proto3";
package test_pack;
message Person {
string name = 1;
int32 age = 2;
}
// 一条注释
.proto
文件基本格式如下:
- 语法指定行
首行固定为语法指定行
,用于指定protobuf
的语法版本,格式:
syntax = "版本号";
目前最新的版本为proto3
,填入:
syntax = "proto3";
注意一定要在首行,哪怕上面有空行也不行,否则无法编译。
package
命名空间
package
是一个命名空间,可以避免命名冲突,类似于C++
中的namespace
或者Java
中的package
。这是一个可选项,如果不怕命名冲突,也可以不指定。
语法:
package 命名空间;
message
消息
message
用于定义一个结构化的对象,其实就是一个class
,内部可以定义成员。
成员字段的格式如下:
类型 成员名 = 标签;
具体类型比较多,会有专门的博客讲解protobuf
的类型。标签
是protobuf
压缩数据的重要方式,就是给每个变量指定一个编号,后续会专门讲解该内容。
标签范围: [ 1 , ( 2 29 − 1 ) ] [1, (2^{29} - 1)] [1,(229−1)]
其中[19000, 19999]
不可用,是保留的编号。
解析:
message Person {
string name = 1;
int32 age = 2;
}
name
:字符串类型,标签为1
age
:32位整型,标签为2
学过任何一门面向对象语言,这些内容都很好理解。
- 注释
.proto
文件中注释格式有两种:
// 行注释
/* 块注释 */
另外的,注释不占行数,也就是说首行可以是注释,不会影响syntax
指定的语法标准:
// 注释...
// 注释...
// 注释...
syntax = "proto3";
以上写法是合法的,syntax
前面可以有注释,但不能有空行或其他内容。
编译
编写好一个基本的.proto
文件后,就可以对其进行编译,需要通过protoc
指令:
protoc [--proto_path=improt路径] --cpp_out=目标路径 源路径.proto
--proto_path
:指定.proto
文件的搜索路径
在.proto
文件中,可以通过import
导入其它的.proto
文件,此时就需要通过--proto_path
来指定其他文件的查找路径,否则无法找到文件。
--cpp_out
=目标路径
--cpp_out
用于指定输出C++
语言的代码,目标路径
是生成的代码的位置。
其他语言的选项:
选项 | 描述 |
---|---|
--cpp_out | 指定生成 C++ 代码的目录 |
--java_out | 指定生成 Java 代码的目录 |
--python_out | 指定生成 Python 代码的目录 |
--csharp_out | 指定生成 C# 代码的目录 |
--go_out | 指定生成 Go 代码的目录 |
--js_out | 指定生成 JavaScript 代码的目录 |
--objc_out | 指定生成 Objective-C 代码的目录 |
--php_out | 指定生成 PHP 代码的目录 |
--ruby_out | 指定生成 Ruby 代码的目录 |
--grpc_out | 指定生成 gRPC 服务端和客户端代码的目录 |
--grpc_java_out | 指定生成 Java gRPC 代码的目录 |
--swift_out | 指定生成 Swift 代码的目录 |
- 源文件路径
最后再指定xxx.proto
源文件的路径。
如果是C++
,编译后会产生如下文件:
至少产生了xxx.pb.cc
和xxx.pb.h
,这些就是生成的C++
代码,后续可以直接使用内部的接口。
使用
在xxx.pb.h
文件中至少可以找到以下内容:
namespace test_pack {
class Person final : public ::google::protobuf::Message
/* @@protoc_insertion_point(class_definition:test_pack.Person) */ {
public:
inline Person() : Person(nullptr) {}
~Person() PROTOBUF_FINAL;
// ...
}
}
可以看到一个命名空间域namespace test_pack
,这就是之前在.proto
文件中写的package test_pack
,最后转化为了C++
的命名空间域。
message Person
也转化为了class Person
,其内部实现了大量接口,包含get
,set
等方法,以及序列化和反序列化接口。
运行机制
至此也可以看出protobuf
具体是如何运行的了,如下图:
使用protobuf
需要编写.proto
文件,随后通过protoc
编译器编译.proto
文件,就可以得到对应语言的文件。在对应语言的文件中,会包含各类接口,最重要的就是序列化和反序列化。
在.proto
文件中,会有很多和计算机语言具体对应的概念,比如message
对应类,package
对应命名空间域,或者其他语言的包。他们都会在编译器的作用下,自动转化成对应的语言。
最后只要往自己的业务代码中引入文件,比如#include "xxx.pb.h"
,或者其他语言的import
等,就可以直接在业务代码中使用protobuf
了。