PHP 调用 gRPC:依赖、代码生成与客户端示例

配置 PHP gRPC 依赖,生成消息与服务客户端代码,并处理调用结果。

前言

最近写项目的时候碰到一些api接口需要使用gRPC调用,本着php是世界上最好的语言的前提,简单研究了一下使用方法。

扩展安装

1
pecl install grpc

直接使用pecl命令编译,简单省事,如需手动编译建议自行谷歌。

1
extension=grpc.so

将此行添加到 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 项目根目录:

1
cd ..

本文示例的协议使用 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 发起调用。

延伸阅读

使用 Hugo 构建
主题 Stack 由 Jimmy 设计