本文以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...