前言
最近写项目的时候碰到一些api接口需要使用gRPC调用,本着php是世界上最好的语言的前提,简单研究了一下使用方法。
扩展安装
直接使用pecl命令编译,简单省事,如需手动编译建议自行谷歌。
将此行添加到 php.ini 文件中的任意位置,例如 /etc/php/php.ini。
protoc编译器安装
1
2
| sudo apt-get update
sudo apt-get install -y cmake git make g++
|
上述依赖命令适用于 Debian/Ubuntu。其他发行版的包名可能不同,不能仅替换包管理器名称。
1
2
3
4
5
6
7
| git clone --recurse-submodules https://github.com/grpc/grpc
cd grpc
git submodule update --init --recursive
mkdir -p cmake/build
cd cmake/build
cmake ../.. -DgRPC_BUILD_TESTS=OFF
make protoc grpc_php_plugin
|
protoc:Protocol Buffers 编译器,用于生成消息类;服务客户端由下面的插件生成。
grpc_php_plugin:protoc的插件,用于生成服务存根类。
1
2
| sudo install -m 0755 third_party/protobuf/protoc /usr/local/bin/protoc
sudo install -m 0755 grpc_php_plugin /usr/local/bin/grpc_php_plugin
|
将二进制文件移动到/usr/local/bin目录。
项目依赖与代码生成
以下步骤在自己的 PHP 项目根目录执行,不是在 gRPC 的编译目录中执行:
1
2
| composer require grpc/grpc google/protobuf
mkdir -p proto src
|
将自己的 .proto 文件放到 proto 目录及其子目录。使用本文示例时,先创建 proto/helloworld.proto,内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| syntax = "proto3";
package Services.Base.HelloWorld;
service HelloWorldService {
rpc Demo (HelloWorldRequest) returns (HelloWorldResponse);
}
message HelloWorldRequest {
string name = 1;
}
message HelloWorldResponse {
string message = 1;
}
|
准备好协议文件后,进入协议目录,自动查找全部 .proto,批量生成消息类和服务客户端到上一级 src:
1
2
3
4
| cd proto \
&& mkdir -p ../src \
&& protoc --proto_path=./ --php_out=../src --grpc_out=../src \
--plugin=protoc-gen-grpc=/usr/local/bin/grpc_php_plugin $(find . -type f -name "*.proto")
|
完成后回到 PHP 项目根目录:
本文示例的协议使用 Services.Base.HelloWorld 包名,生成类位于 src/Services;将以下内容合并到现有 composer.json 的自动加载配置中。使用自己的协议时,命名空间与目录按生成结果调整:
1
2
3
4
5
6
7
8
9
| {
"autoload": {
"psr-4": {
"App\\": "src/App/",
"Services\\": "src/Services/",
"GPBMetadata\\": "src/GPBMetadata/"
}
}
}
|
1
2
| mkdir -p src/App
composer dump-autoload
|
插件会生成服务客户端;下方手工 Client 用于解释调用路径,不需要替换生成文件。将其保存为 src/App/Client.php,调用类保存为 src/App/App.php。
客户端代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| <?php
namespace App;
use Grpc\BaseStub;
use Services\Base\HelloWorld\HelloWorldRequest;
use Services\Base\HelloWorld\HelloWorldResponse;
class Client extends BaseStub
{
public function __construct($hostname, $opts, $channel = null)
{
parent::__construct($hostname, $opts, $channel);
}
/**
* 用于请求和响应该服务
* @param HelloWorldRequest $argument
* @param array $metadata
* @param array $options
* @return
*/
public function Demo(HelloWorldRequest $argument, $metadata = [], $options = [])
{
return $this->_simpleRequest('/Services.Base.HelloWorld.HelloWorldService/Demo',
$argument,
[HelloWorldResponse::class, 'decode'],
$metadata, $options);
}
}
|
调用gRPC代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
| <?php
namespace App;
use Grpc\ChannelCredentials;
use Services\Base\HelloWorld\HelloWorldRequest;
/**
* grpc请求案例
*/
class App
{
public $client;
public static function run()
{
(new self())->grpc()->Main();
}
public function grpc()
{
$this->client = $this->client();
return $this;
}
public function client()
{
return new Client('127.0.0.1:8000', [
'credentials' => ChannelCredentials::createInsecure(),
]);
}
public function Main()
{
$request = new HelloWorldRequest();
$request->setName("林逸博客");
$get = $this->client->Demo($request)->wait();
list($reply, $status) = $get;
if ($status->code === 0 && $reply !== null) {
print_r($reply->getMessage());
} else {
echo '获取返回内容失败:' . $status->details;
}
}
}
|
完整Demo
https://github.com/zihelyu/php-grpc-client
运行入口
在项目根目录创建 run.php:
1
2
3
| <?php
require __DIR__ . '/vendor/autoload.php';
\App\App::run();
|
运行前需要在 127.0.0.1:8000 启动实现相同协议的 gRPC 服务端。createInsecure() 仅适用于本文本地示例;远程服务按其要求配置 TLS。编译工具链和联调步骤参考 PHP gRPC 官方入门。
服务端就绪后,在项目根目录执行 php run.php 发起调用。
延伸阅读