Protobuf 小试牛刀
本文以PHP为例。
环境:
- CentOS 6.8
- proto 3.8
- PHP 7.1.12
- PHP protobuf扩展 3.8.0
- go1.12.5 linux/amd64
本文示例仓库地址: https://github.com/52fhy/protobuf-sample
是什么
Protobuf是一种平台无关、语言无关、可扩展且轻便高效的序列化数据结构的协议,可以用于网络通信和数据存储。
官方文档:https://github.com/protocolbuffers/protobuf
作为数据交换协议,常见的还有JSON、XML。相比JSON,Protobuf有更高的转化效率。一般JSON用于HTTP接口,Protobuf用于RPC比较多。以gRPC为例,默认就是使用Protobuf。
我们可以使用Protobuf:
- 作为RPC的序列化数据结构的协议。类似于JSON
- 定义proto文件,一键生成多语言代码。
安装
安装清单一览:
- protoc
- protoc-gen-go 编译出golang目标代码
- protoc-gen-doc 文档生成工具支持
- 各编程语言对应的protobuf库
安装protoc
为了将proto文件转成编程语言代码,需要安装编译工具。
地址:https://github.com/protocolbuffers/protobuf/releases/
wget https://github.com/protocolbuffers/protobuf/releases/download/v3.8.0/protoc-3.8.0-linux-x86_64.zip
unzip protoc-3.8.0-linux-x86_64.zip
cp bin/protoc /usr/bin/
cp -r include/google /usr/include/
注:最后一行是为了将proto的一些库复制到系统,例如
google/protobuf/any.proto
,如果不复制,编译如果用了里面的库例如Any,会提示:protobuf google.protobuf.Any not found 。
mac版地址:
https://github.com/protocolbuffers/protobuf/releases/download/v3.8.0/protoc-3.8.0-osx-x86_64.zip
windows版地址:
https://github.com/protocolbuffers/protobuf/releases/download/v3.8.0/protoc-3.8.0-win64.zip
然后命令行输入 protoc
可以查看帮助。
假设有一个 .proto
格式的文件,需要编译成其它语言代码:
mkdir -p sdk/php
protoc --php_out=sdk/php --java_out=sdk/java --js_out=sdk/js --objc_out=sdk/objc *.proto
其中--php_out=sdk/php
表示编译成PHP代码,放在sdk/php
目录。protof
支持的语言:
$ protoc | grep "=OUT_DIR"
--cpp_out=OUT_DIR Generate C++ header and source.
--csharp_out=OUT_DIR Generate C# source file.
--java_out=OUT_DIR Generate Java source file.
--js_out=OUT_DIR Generate JavaScript source.
--objc_out=OUT_DIR Generate Objective C header and source.
--php_out=OUT_DIR Generate PHP source file.
--python_out=OUT_DIR Generate Python source file.
--ruby_out=OUT_DIR Generate Ruby source file.
默认没有go代码支持,如果需要支持go的代码生成,则需要protoc-gen-go
工具。
golang 代码编译支持
protoc --help
并没有--go_out
参数说明, 如需编译golang目标代码,请执行以下步骤:
1、安装golang环境:yum install golang
,其它系统查看 https://studygolang.com/dl (已安装请跳过)
2、go get github.com/golang/protobuf/protoc-gen-go
;
3、复制扩展工具到/usr/bin
:
cp `go env|grep 'GOPATH'|sed -e 's/GOPATH="//' -e 's/"//'`/bin/protoc-gen-go /usr/bin/
4、编译go目标代码: protoc --go_out=./go *.proto
。
protoc-gen-doc文档生成工具支持
1、需要golang环境
2、go get -u github.com/pseudomuto/protoc-gen-doc/cmd/protoc-gen-doc
;
3、复制扩展工具到/usr/bin
:
cp `go env|grep 'GOPATH'|sed -e 's/GOPATH="//' -e 's/"//'`/bin/protoc-gen-doc /usr/bin/
4、编译proto生成HTML文档: --plugin=/usr/bin/protoc-gen-doc --doc_out=html,index.html:./doc
。
一个完整的例子:
p=$(cd `dirname $0`;pwd)
namespace="Pb_$appname"
cd $p/proto/
rm -rf $p/sdk/php
mkdir $p/sdk/php
protoc
--plugin=/usr/bin/protoc-gen-doc --doc_out=html,index.html:$p/doc \
--php_out=$p/sdk/php \
--grpc_out=$p/sdk/php --plugin=protoc-gen-grpc=/usr/local/bin/grpc_php_plugin \
--go_out=plugins=grpc:$p/$namespace/ \
--java_out=$p/sdk/java \
--js_out=$p/sdk/js \
--objc_out=$p/sdk/objc \
*.proto
--grpc_out=$p/sdk/php --plugin=protoc-gen-grpc=/usr/local/bin/grpc_php_plugin
这个用于生成grpc代码,如果没有可以去掉。
PHP扩展安装
php可以安装c扩展版本或者纯php代码版本。
C扩展版本
1、下载扩展源码:
wget https://pecl.php.net/get/protobuf-3.8.0.tgz
tar zxf protobuf-3.8.0.tgz
cd protobuf-3.8.0
phpize
./configure
make
sudo make install
或者直接使用 pecl 安装:
pecl install protobuf-3.8.0
2、 输入 php -i|grep php.ini
查看php.ini
的路,修改php.ini
, 增加:
extension=protobuf.so
3、检查是否安装成功:php --ri protobuf
,安装成功会显示版本号。
纯PHP版本
使用 composer 安装即可:
composer require google/protobuf
下面说一下区别和注意事项:
1、截止到3.8.0版本,如果安装的是纯PHP版本,protobuf 里提供的序列化方法serializeToJsonString()
不支持参数,c扩展版本支持,表示保留proto里定义的属性,不进行转大写;
2、c扩展版本无法使用var_dump等函数打印出protobuf对象里的对象的结构和内容,但是如果protobuf对象里的标量类型是可以打印出来的。
Go扩展库安装
golang如果使用protobuf,需要引入google.golang.org/grpc
库。使用 go mod管理,可以编写规则做个映射:
replace google.golang.org/grpc => github.com/grpc/grpc-go v1.21.1
应用:protobuf创建Model
有时候我们需要根据数据库表结构生成一个Model,常规办法是手写,比较麻烦。有了protobuf,我们可以先编写一个proto
文件,然后编译成目标语言的代码。
定义proto
我们先定义一个 proto
文件:
// proto/User.proto
syntax = "proto3";
package Sample.Model; //namesapce
message User {
int64 id = 1; //主键id
string name = 2; //用户名
string avatar = 3; //头像
string address = 4; //地址
string mobile = 5; //手机号
map<string, string> ext = 6; //扩展信息
}
message UserList {
repeated User list = 1; //用户列表
int32 page = 2; //分页
int32 limit = 3; //分页条数
}
以上分别创建了user
和UserList
两个Model。
编译proto
现在使用proto工具编译出来:
mkdir php
protoc --php_out=php proto/User.proto
会生成:
├── php
│ ├── GPBMetadata
│ │ └── User.php
│ └── Sample
│ └── Model
│ ├── UserList.php
│ └── User.php
├── proto
│ └── User.proto
UserList.php 代码部分示例:
测试编译生成的代码
接下来,我们写个例子看看如何使用生成的Model。在使用之前需要处理下GPBMetadata
相关的命名空间问题,这里我们定义的命名空间是Sample\Model
,但是 GPBMetadata/User.php
以及Sample/Model/User.php
的命名空间我们希望调整下,都以Sample\Model
开头,而不是GPBMetadata
。下面我们使用命令行处理:
cd protobuf-sample
#修改GPBMetadata命名空间
cd php
mv -f GPBMetadata Sample/Model/
find . -name '*.php' ! -name example.php -exec sed -i -e 's#GPBMetadata#Sample\\Model\\GPBMetadata#g' -e 's#\\Sample\\Model\\GPBMetadata\\Google#\\GPBMetadata\\Google#g' {} \;
cd -
接下来我们写个测试文件:
user.php
<?php
use Sample\Model\User;
use Sample\Model\UserList;
ini_set("display_errors", true);
error_reporting(E_ALL);
require_once "autoload.php";
$user = new User();
$user->setId(1)->setName("test");
$userList = new UserList();
$userList->setPage(1)->setLimit(5)->setList([$user]);
print_r($userList);
var_dump($userList->getPage());
print_r($userList->getList());
foreach ($userList->getList() as $key => $obj) {
print_r($obj);
echo $obj->getId() .PHP_EOL;
}
autoload.php是实现自动加载的。
我们运行:
$ php tests/user.php
Sample\Model\UserList Object
/work/git/protobuf-sample/tests/user.php:15:
int(1)
Google\Protobuf\Internal\RepeatedField Object
(
)
Sample\Model\User Object
1
{"list":[{"id":1,"name":"test"}],"page":1,"limit":5}
可以看到使用var_dump、print_r等函数是打印不出来 protobuf生成的对象的,但是里面确实是有内容的,只有标量能打印出来,或者序列化为字符串。
我们也可以将一个字符串反序列化为protobuf对象:
user_merge.php
<?php
use Sample\Model\UserList;
$json = '{"list":[{"id":1,"name":"test"}],"page":1,"limit":5}';
require_once "autoload.php";
$userList = new UserList();
$userList->mergeFromJsonString($json);
print_r($userList);
echo $userList->serializeToJsonString();
运行示例:
$ php tests/user_merge.php
Sample\Model\UserList Object
{"list":[{"id":1,"name":"test"}],"page":1,"limit":5}
proto语法
这里只将介绍简单的,如果需要细研究,请查看官方文档。
官方文档:https://developers.google.com/protocol-buffers/docs/overview
1、proto3
proto 有proto3 和 proto2。proto3 比 proto2 支持更多语言但 更简洁。去掉了一些复杂的语法和特性,更强调约定而弱化语法。如果是首次使用 Protobuf ,建议使用 proto3 。详见参考文献说明。
需要在proto头部申明:
syntax = "proto3";
如果你没有指定这个,编译器会使用proto2。
2、注释
使用 //
,示例:
message UserList {
repeated User list = 1; //用户列表
int32 page = 2; //分页
int32 limit = 3; //分页条数
}
其中写在每个属性后面的注释在生产的代码里面有保留。
3、message
message
类似于结构体的概念,最终编译为代码在PHP、JAVA里就是一个类,在golang里是结构体。每一个属性都会生成对应的getXXX
、setXXX
方法。
4、字段规则
repeated
表示这个属性重复N次,在相对应的编程语言中通常是一个空的list。PHP里对应数组。
reserved
表示标识号保留暂时不用。
message Foo {
reserved 2, 15, 9 to 11;
reserved "foo", "bar";
}
在消息定义中,每个字段都有唯一的一个数字标识符。这些标识符是用来在消息的二进制格式中识别各个字段的,一旦开始使用就不能够再改变。注:[1,15]之内的标识号在编码的时候会占用一个字节。[16,2047]之内的标识号则占用2个字节。所以应该为那些频繁出现的消息元素保留 [1,15]之内的标识号。切记:要为将来有可能添加的、频繁出现的标识号预留一些标识号。最小的标识号可以从1开始,最大到2^29 - 1, or 536,870,911。
5、支持的数据类型
详情参看官方文档:https://developers.google.com/protocol-buffers/docs/proto3#scalar
6、默认值说明
- string类型,默认值是空字符串
- bytes类型,默认值是空bytes
- bool类型,默认值是false
- 数字类型,默认值是0
- 枚举类型,默认值是第一个枚举值,即0
- repeated修饰的属性,默认值是空.
7、枚举
使用enum
关键字定义枚举,值必须从0开始:
enum Corpus {
UNIVERSAL = 0;
WEB = 1;
IMAGES = 2;
LOCAL = 3;
NEWS = 4;
PRODUCTS = 5;
VIDEO = 6;
}
8、引用类型
上面的UserList
就引用了User
类型。大家可以看一下。
9、import
如果一个proto
文件引用了另外一个proto
文件,那么可以使用import
关键字在头部申明:
import "User.proto";
10、Map类型
proto支持map属性类型的定义,语法如下:
map<key_type,value_type> map_field = N;
示例:
map<string, string> ext = 6; //扩展信息
这个map对于PHP来说就是关联数组,对于golang来说就是Map。
10、Any
Any类型允许包装任意的message类型,可以通过pack()
和unpack()
(方法名在不同的语言中可能不同)方法打包/解包:
import "google/protobuf/any.proto";
message Response {
google.protobuf.Any data = 1;
}
PHP开发的同学可能觉得Any没必要,因为数组里任何类型都可以放,但是对于强类型语言,数组里的值类型必须是一致的,使用Any类型可以解决这个问题。Any相当于把值包装了一层,这样都是Any类型。
11、服务定义
service UserService {
// 方法名 方法参数 返回值
rpc GetUser(Request) returns (Response);
}
这相当于定义了一个类,里面有一个对外的GetUser()
方法。这个通常用于定义RPC服务,与gRPC结合使用。
12、从.proto文件生成了什么?
当用protocol buffer编译器来运行.proto文件时,编译器将生成所选择语言的代码,这些代码可以操作在.proto文件中定义的消息类型,包括获取、设置字段值,将消息序列化到一个输出流中,以及从一个输入流中解析消息。
PHP
:每一个Message
或者Enum
生成一个类,另外还会生成GPBMetadata
。C++
:编译器会为每个.proto
文件生成一个.h
文件和一个.cc
文件,.proto
文件中的每一个消息有一个对应的类。Java
:编译器为每一个消息类型生成了一个.java
文件,以及一个特殊的Builder
类(该类是用来创建消息类接口的)。Python
:Python编译器为.proto
文件中的每个消息类型生成一个含有静态描述符的模块,该模块与一个元类(metaclass
)在运行时(runtime
)被用来创建所需的Python数据访问类。go
:编译器会位每个消息类型生成了一个.pd.go
文件。Ruby
:编译器会为每个消息类型生成了一个.rb
文件。Objective-C
:编译器会为每个消息类型生成了一个pbobjc.h
文件和pbobjcm
文件,.proto
文件中的每一个消息有一个对应的类。C#
:编译器会为每个消息类型生成了一个.cs
文件,.proto
文件中的每一个消息有一个对应的类。
其它
IDE插件
1、JetBrains PhpStorm 可以在插件里找到Protobuf
安装,重启IDE后就支持proto格式语法了。
2、VScode 在扩展里搜索 Protobuf
,安装即可。
3、protobuf的 php 扩展类在ide中没有提示,可将https://github.com/protocolbuffers/protobuf/tree/master/php/src 目录下载到本地,将此目录加到ide的include_path中即可。
常见问题
1、 protoc 编译输出php文件时遇到一个错误:protobuf google.protobuf.Any not found。
原因:安装proto的时候没有把include/google
复制到/usr/include/
。
解决:重新下载protoc-3.8.0-linux-x86_64.zip
并将解压后的include/google
复制到/usr/include/
。
2、Mac下执行phpize报如下错误:
grep: /usr/include/php/main/php.h: No such file or directory
grep: /usr/include/php/Zend/zend_modules.h: No such file or directory
grep: /usr/include/php/Zend/zend_extensions.h: No such file or directory
解决方法:
xcode-select --instal
参考
1、protoc2 与 protoc3 区别 - 简书
https://www.jianshu.com/p/cdedcf696e9e
2、gRPC之proto语法 - 简书
https://www.jianshu.com/p/da7ed5914088
3、Protobuf3语法详解 - 望星辰大海 - 博客园
https://www.cnblogs.com/tohxyblog/p/8974763.html
Protobuf 小试牛刀的更多相关文章
- gRPC入坑记
概要 由于gRPC主要是谷歌开发的,由于一些已知的原因,gRPC跑demo还是不那么顺利的.单独写这一篇,主要是gRPC安装过程中的坑太多了,记录下来让大家少走弯路. 主要的坑: 如果使用PHP.Py ...
- Xamarin+Prism小试牛刀:定制跨平台Outlook邮箱应用(后续)
在[Xamarin+Prism小试牛刀:定制跨平台Outlook邮箱应用]里面提到了Microsoft 身份认证,其实这也是一大块需要注意的地方,特作为后续补充这些知识点.上章是使用了Microsof ...
- python通过protobuf实现rpc
由于项目组现在用的rpc是基于google protobuf rpc协议实现的,所以花了点时间了解下protobuf rpc.rpc对于做分布式系统的人来说肯定不陌生,对于rpc不了解的童鞋可以自行g ...
- Protobuf使用规范分享
一.Protobuf 的优点 Protobuf 有如 XML,不过它更小.更快.也更简单.它以高效的二进制方式存储,比 XML 小 3 到 10 倍,快 20 到 100 倍.你可以定义自己的数据结构 ...
- java netty socket库和自定义C#socket库利用protobuf进行通信完整实例
之前的文章讲述了socket通信的一些基本知识,已经本人自定义的C#版本的socket.和java netty 库的二次封装,但是没有真正的发表测试用例. 本文只是为了讲解利用protobuf 进行C ...
- 在Wcf中应用ProtoBuf替代默认的序列化器
Google的ProtoBuf序列化器性能的牛逼已经有目共睹了,可以把它应用到Socket通讯,队列,Wcf中,身为dotnet程序员一边期待着不久后Grpc对dotnet core的支持更期待着Wc ...
- protobuf的编译安装
github地址:https://github.com/google/protobuf支持多种语言,有多个语言的版本,本文采用的是在centos7下编译源码进行安装. github上有详细的安装说明: ...
- 编译protobuf的jar文件
1.准备工作 需要到github上下载相应的文件,地址https://github.com/google/protobuf/releases protobuf有很多不同语言的版本,因为我们需要的是ja ...
- protobuf学习(2)-相关学习资料
protobuf官方git地址 protobuf官方英文文档 (你懂的需要FQ) protobuf中文翻译文档 protobuf概述 (官方翻译 推荐阅读) protobuf入门 ...
随机推荐
- 浅谈java枚举类
一.什么情况下使用枚举类? 有的时候一个类的对象是有限且固定的,这种情况下我们使用枚举类就比较方便? 二.为什么不用静态常量来替代枚举类呢? public static final int SEASO ...
- windows 路径
windows下的路径分隔符是\,而不是/ hosts文件的位置:C:\Windows\system32\drivers\etc 安卓(Android)用户:Android手机hosts文件路径:/s ...
- matlab 矢量化编程(四)—— 标量函数转化为能够处理矢量的函数
1. 组合的矢量实现 nchoosek(n, k) 的第二个参数在 matlab 下是不支持矢量化的,必须是标量形式.但 matlab 下的 gamma 函数,却可支持,矢量形式,又因为,gamma ...
- python3实现域名查询和whois查询
关键字:python3 域名查询 域名查询接口 whois查询原文:http://www.cnblogs.com/txw1958/archive/2012/08/31/python3-domain-w ...
- AWS核心服务概览
1.Amazon Web Service 应该可以说,Amazon Web Service目前是云计算领域的领头羊,其业务规模.开发水平和盈利能力在业界内都是首屈一指的.从本科毕业离开学校就一直做Ja ...
- 移花接木:借助 IViewLocationExpander 更换 ASP.NET Core View Component 视图路径
端午节在家将一个 asp.net 项目向 asp.net core 迁移时遇到了一个问题,用 view component 取代 Html.RenderAction 之后,运行时 view compo ...
- 【转载】centos7+tomcat部署JavaWeb项目超详细步骤
我们平时访问的网站大多都是发布在云服务器上的,比如阿里云.腾讯云等.对于新手,尤其是没有接触过linux系统的人而言是比较有困难的,而且至今使用云服务器也是有成本的,很多时候我们可以通过虚拟机自己搭建 ...
- npm学习(-)
了解npm请前往https://www.npmjs.cn/getting-started/what-is-npm/ npm 由三个独立的部分组成: 网站 注册表(registry) 命令行工具 (CL ...
- 用MVVM模式开发中遇到的零散问题总结(5)——将动态加载的可视元素保存为图片的控件,Binding刷新的时机
原文:用MVVM模式开发中遇到的零散问题总结(5)--将动态加载的可视元素保存为图片的控件,Binding刷新的时机 在项目开发中经常会遇到这样一种情况,就是需要将用户填写的信息排版到一张表单中,供打 ...
- WPF DataTemplateSelector的使用
<Window x:Class="CollectionBinding.MainWindow" xmlns="http://schemas.micros ...