本文以PHP为例。
环境:php
本文示例仓库地址: https://github.com/52fhy/prot...html
Protobuf是一种平台无关、语言无关、可扩展且轻便高效的序列化数据结构的协议,能够用于网络通讯和数据存储。java
官方文档:https://github.com/protocolbu...python
做为数据交换协议,常见的还有JSON、XML。相比JSON,Protobuf有更高的转化效率。通常JSON用于HTTP接口,Protobuf用于RPC比较多。以gRPC为例,默认就是使用Protobuf。linux
咱们可使用Protobuf:git
安装清单一览:github
为了将proto文件转成编程语言代码,须要安装编译工具。golang
地址:https://github.com/protocolbu...数据库
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/protocolbu...编程
windows版地址:
https://github.com/protocolbu...
而后命令行输入 protoc
能够查看帮助。
假设有一个 .proto
格式的文件,须要编译成其它语言代码,以PHP为例则是:
mkdir php protoc --php_out=php *.proto
其中--php_out=php
表示编译成PHP代码,放在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.
后面有示例说明。
golang 代码编译支持 protoc --help
并无--go_out
参数说明, 如需编译golang目标代码,请执行如下步骤:
一、安装golang环境:yum install golang
,其它系统查看 https://studygolang.com/dl (已安装请跳过)
二、go get github.com/golang/protobuf/protoc-gen-go
;
三、复制扩展工具到/usr/bin
:
cp `go env|grep 'GOPATH'|sed -e 's/GOPATH="//' -e 's/"//'`/bin/protoc-gen-go /usr/bin/
四、编译go目标代码: protoc --go_out=./go *.proto
。
php能够安装c扩展版本或者纯php代码版本。
C扩展版本
一、下载扩展源码:
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
二、 输入 php -i|grep php.ini
查看php.ini
的路,修改php.ini
, 增长:
extension=protobuf.so
三、检查是否安装成功:php --ri protobuf
,安装成功会显示版本号。
纯PHP版本
使用 composer 安装便可:
composer require google/protobuf
下面说一下区别和注意事项:
一、截止到3.8.0版本,若是安装的是纯PHP版本,protobuf 里提供的序列化方法serializeToJsonString()
不支持参数,c扩展版本支持,表示保留proto里定义的属性,不进行转大写;
二、c扩展版本没法使用var_dump等函数打印出protobuf对象里的对象的结构和内容,可是若是protobuf对象里的标量类型是能够打印出来的。
golang若是使用protobuf,须要引入google.golang.org/grpc
库。使用 go mod管理,能够编写规则作个映射:
replace google.golang.org/grpc => github.com/grpc/grpc-go v1.21.1
有时候咱们须要根据数据库表结构生成一个Model,常规办法是手写,比较麻烦。有了protobuf,咱们能够先编写一个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工具编译出来:
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}
这里只将介绍简单的,若是须要细研究,请查看官方文档。
官方文档:https://developers.google.com...
一、proto3
proto 有proto3 和 proto2。proto3 比 proto2 支持更多语言但 更简洁。去掉了一些复杂的语法和特性,更强调约定而弱化语法。若是是首次使用 Protobuf ,建议使用 proto3 。详见参考文献说明。
须要在proto头部申明:
syntax = "proto3";
若是你没有指定这个,编译器会使用proto2。
二、注释
使用 //
,示例:
message UserList { repeated User list = 1; //用户列表 int32 page = 2; //分页 int32 limit = 3; //分页条数 }
其中写在每一个属性后面的注释在生产的代码里面有保留。
三、message message
相似于结构体的概念,最终编译为代码在PHP、JAVA里就是一个类,在golang里是结构体。每个属性都会生成对应的getXXX
、setXXX
方法。
四、字段规则 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。
五、支持的数据类型
详情参看官方文档:https://developers.google.com...
六、默认值说明
七、枚举
使用enum
关键字定义枚举,值必须从0开始:
enum Corpus { UNIVERSAL = 0; WEB = 1; IMAGES = 2; LOCAL = 3; NEWS = 4; PRODUCTS = 5; VIDEO = 6; }
八、引用类型
上面的UserList
就引用了User
类型。你们能够看一下。
九、import
若是一个proto
文件引用了另一个proto
文件,那么可使用import
关键字在头部申明:
import "User.proto";
十、Map类型
proto支持map属性类型的定义,语法以下:
map<key_type,value_type> map_field = N;
示例:
map<string, string> ext = 6; //扩展信息
这个map对于PHP来讲就是关联数组,对于golang来讲就是Map。
十、Any
Any类型容许包装任意的message类型,能够经过pack()
和unpack()
(方法名在不一样的语言中可能不一样)方法打包/解包:
import "google/protobuf/any.proto"; message Response { google.protobuf.Any data = 1; }
PHP开发的同窗可能以为Any不必,由于数组里任何类型均可以放,可是对于强类型语言,数组里的值类型必须是一致的,使用Any类型能够解决这个问题。Any至关于把值包装了一层,这样都是Any类型。
十一、服务定义
service UserService { // 方法名 方法参数 返回值 rpc GetUser(Request) returns (Response); }
这至关于定义了一个类,里面有一个对外的GetUser()
方法。这个一般用于定义RPC服务,与gRPC结合使用。
十二、从.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
文件中的每个消息有一个对应的类。一、JetBrains PhpStorm 能够在插件里找到Protobuf
安装,重启IDE后就支持proto格式语法了。
二、VScode 在扩展里搜索 Protobuf
,安装便可。
三、protobuf的 php 扩展类在ide中没有提示,可将https://github.com/protocolbu... 目录下载到本地,将此目录加到ide的include_path中便可。
一、 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/
。
二、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
一、protoc2 与 protoc3 区别 - 简书
https://www.jianshu.com/p/cde...
二、gRPC之proto语法 - 简书
https://www.jianshu.com/p/da7...
三、Protobuf3语法详解 - 望星辰大海 - 博客园
https://www.cnblogs.com/tohxy...