從零開始講解,PHP(客戶端)與 Golang(服務端)使用grpc+protobuf 通信。因為我本地環境都是配置好的,避免我落下步驟操作,所以我在docker環境下開發,拉取一個基於Alpine的鏡像。
Alpine操作系統是一個面向安全的輕型 Linux
發行版。
搭建環境
1.項目中,我會用到composer以及PHP相關的擴展,於是我拉取一個基於PHP7.2的docker鏡像。命令中的參數,可以看我另一篇文章,Docker 使用筆記-常用基礎命令。
docker container run -it --name grpc-ubuntu -v /Users/wuerzhilv-lq/examples:/home/examples lorisleiva/laravel-docker:7.2 /bin/bash
2.安裝相關的包
apk add autoconf automake libtool linux-headers
3.這個docker沒有golang語言環境,我們這里安裝下。這里采用編譯安裝的方式,為什么不用apk包管理器安裝,因為有坑,詳情鏈接
。
Go工具鏈是用Go編寫的,要構建它,需要安裝Go編譯器;由於我們沒有GO編譯器,同時1.4以后的GO語言版本沒有直接支持GCC,支持GO編譯器和GCCGO,因此我們需要先現在1.4版本的GO,
利用GCC編譯好GO編譯器后,
再利用1.4版本的GO編譯器編譯最新版本的GO(比如以下的1.14)。
①下載go1.4版本,源碼從官方下載 wget https://dl.google.com/go/go1.4-bootstrap-20171003.tar.gz tar -zxf go1.4-bootstrap-20171003.tar.gz mv go /usr/local/go1.4
②下載go1.14版本 wget https://dl.google.com/go/go1.14.2.src.tar.gz tar -zxf go1.14.2.src.tar.gz mv go /usr/local/go
③環境變量設置(GOROOT_BOOTSTRAP是編譯器環境設定需要) echo 'export GOROOT_BOOTSTRAP=/usr/local/go1.4' >> ~/.bash_profile echo 'export GOROOT=/usr/local/go' >> ~/.bash_profile echo 'export PATH=${GOROOT}/bin:${PATH}' >> ~/.bash_profile //重載環境配置變量(主要是重載GOROOT_BOOTSTRAP)
source ~/.bash_profile
④ 編譯go1.4的版本 cd /usr/local/go1.4/src && ./make.bash
⑤利用go1.4的go編譯器,編譯go1.14 cd /usr/local/go/src && ./make.bash
⑥查看golang版本 go version
4.新下載的php本來就沒有現成的php.ini文件。只是給了php.ini-development (開發環境用)與 php.ini-production(生產環境用)兩個建議。這里我們基於生產環境,復制出一個php.ini文件。
cp /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini
5.系統層面的,我們環境就安裝好了。Grpc 是使用protobuf來傳輸數據,protobuf是一個文件,那怎么讓php和golang語言識別它,這里就需要一個編譯工具了。
apk add protobuf
6. PHP安裝Grpc,Protobuf擴展。
pecl install grpc-1.25.0 pecl install protobuf-3.11.4
打開我們新建的php.ini文件,將擴展加進去。可以使用php -m 查看配置是否生效。
extension=grpc.so
extension=protobuf.so
7.安裝PHP Protoc Plugin
$ git clone -b v1.28.1 https://github.com/grpc/grpc $ cd grpc $ git submodule update --init $ make grpc_php_plugin
make編譯后,輸出目錄為bins/opt,輸出文件grpc_php_plugin,將文件復制到/home/examples
cp bins/opt/grpc_php_plugin /home/examples/
8.安裝Golang Protoc Plugin
go get -u github.com/golang/protobuf/protoc-gen-go
export PATH=$PATH:$GOPATH/bin
9.安裝grpc
go get -u google.golang.org/grpc
10.環境都配好了,我們來寫項目代碼,我在創建docker容器的時候,就把容器目錄掛載到了本地,可以使用ide工具開發。
我這里配置的本地目錄是,/Users/wuerzhilv-lq/examples。examples就相當於項目目錄了,在這個項目中新建protos目錄,用於存放protobuf文件;新建php目錄用於存放客戶端代碼;新建go目錄,用於存放服務端代碼。
我們新建一個protobuf文件(helloworld.proto),來定義服務方法,請求參數,請求返回值。 內容如下
// 定義語法,一共是有兩種proto2,proto3。我這里使用proto3。 syntax = "proto3"; // 定義服務(我感覺像類) service Greeter { // 定義服務方法為SayHello,可以有多個方法 rpc SayHello (HelloRequest) returns (HelloReply) {} } //定義請求參數 message HelloRequest { string name = 1; } //定義返回參數 message HelloReply { string message = 1; }
11. 服務端代碼,進入容器中的項目目錄,根據protobuf文件,使用proto工具生成服務端(golang)代碼到go目錄。執行命令之后,會在go文件夾中出現一個proto文件夾,里面包含一個helloworld.pb.go文件。
protoc protos/helloworld.proto --go_out=plugins=grpc:go
在examples/go 文件夾中,新建一個main.go文件,注冊helloworld服務。
package main import ( "context" "log" "net" "google.golang.org/grpc" pb "./protos" ) const ( port = ":50051" ) // server is used to implement helloworld.GreeterServer. type server struct { pb.UnimplementedGreeterServer } // SayHello implements helloworld.GreeterServer func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { log.Printf("Received: %v", in.GetName()) return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil } func main() { lis, err := net.Listen("tcp", port) if err != nil { log.Fatalf("failed to listen: %v", err) } s := grpc.NewServer() pb.RegisterGreeterServer(s, &server{}) log.Printf("服務啟動"+port) if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } }
運行go run main.go啟動服務,看看服務是否能啟動。
12.客戶端代碼,進入容器中的項目目錄,根據protobuf文件,使用proto工具生成客戶端(PHP)代碼到php目錄。執行命令之后,會在php文件夾中出現一些文件。
protoc --proto_path=protos --php_out=php --grpc_out=php --plugin=protoc-gen-grpc=/home/examples/grpc_php_plugin protos/helloworld.proto
更換composer源
composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/
通過composer工具,我們初始化一個composer.json文件。
composer.json內容如下:
{
"name": "grpc/grpc-demo",
"description": "gRPC example for PHP",
"require": {
"grpc/grpc": "^1.27",
"google/protobuf": "^3.11"
},
"authors": [
{
"name": "Li",
"email": "calmliqi@gmail.com"
}
]
}
composer install 安裝擴展包。
在php目錄下,新建客戶端文件,greeter_client.php
<?php
require dirname(__FILE__).'/vendor/autoload.php'; @include_once dirname(__FILE__).'/Helloworld/GreeterClient.php'; @include_once dirname(__FILE__).'/Helloworld/HelloReply.php'; @include_once dirname(__FILE__).'/Helloworld/HelloRequest.php'; @include_once dirname(__FILE__).'/GPBMetadata/Helloworld.php'; function greet($name) { $client = new Helloworld\GreeterClient('localhost:50051', [ 'credentials' => Grpc\ChannelCredentials::createInsecure(), ]); $request = new Helloworld\HelloRequest(); $request->setName($name); list($reply, $status) = $client->SayHello($request)->wait(); $message = $reply->getMessage(); return $message; } $name = !empty($argv[1]) ? $argv[1] : 'world'; echo greet($name)."\n";
13.進入examples項目目錄,
啟動服務端
go run go/main.go
執行客戶端
php php/greeter_client.php
如果輸出helloworld,那就是成功了。
總結:
這篇文章,只是簡單粗略說了下,Grpc+protobuf的使用,比如針對protobuf文件,具體的內容沒有說。以后,我會在寫一篇針對protobuf文件詳細講解的文章。我懂得也不是太多,就是當自己做了一次總結。如有不對的地方,大家多提意見。我本機掛了代理,所以沒有遇到牆的問題,如果被牆了,自行百度吧。
參考了如下資料:
https://developers.google.com/protocol-buffers
https://grpc.io/docs/quickstart
https://golang.org/doc/install/source
https://tkstorm.com/posts-list/programming/go/build-golang-from-source-on-alpine/