PHP(客户端)与 Golang(服务端)使用grpc+protobuf 通信
从零开始讲解,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/