基础教程

PHP gRPC 基础教程简介。

基础教程

PHP gRPC 基础教程简介。

本教程为 PHP 程序员提供了 gRPC 基础使用入门。

通过此示例,您将学习如何

  • 在 .proto 文件中定义服务。
  • 使用协议缓冲区编译器生成客户端代码。
  • 使用 PHP gRPC API 为您的服务编写一个简单的客户端。

本教程假定您对 Protocol Buffers 有一定了解。请注意,本教程中的示例使用的是 Protocol Buffers 语言的 proto2 版本。

另请注意,目前您只能在 PHP 中为 gRPC 服务创建客户端。请使用其他语言来创建 gRPC 服务器。

为什么使用 gRPC?

我们的示例是一个简单的路线映射应用程序,它允许客户端获取其路线上特征的信息,创建其路线摘要,并与服务器及其他客户端交换路线信息(例如交通更新)。

使用 gRPC,我们可以在一个 .proto 文件中定义一次服务,然后生成支持 gRPC 的任何语言的客户端和服务器,这些客户端和服务器又可以在从大型数据中心内的服务器到您自己的平板电脑等各种环境中运行——不同语言和环境之间通信的所有复杂性都由 gRPC 为您处理。我们还可以获得使用 Protocol Buffers 的所有优点,包括高效序列化、简单的 IDL 和易于接口更新。

示例代码和设置

本教程的示例代码位于 grpc/grpc/examples/php/route_guide。要下载示例,请运行以下命令克隆 grpc 仓库及其子模块:

git clone --recurse-submodules -b v1.74.0 --depth 1 --shallow-submodules https://github.com/grpc/grpc

您需要 grpc-php-plugin 来帮助您编译 .proto 文件。从源代码构建它,如下所示:

cd grpc
mkdir -p cmake/build
pushd cmake/build
cmake ../..
make protoc grpc_php_plugin
popd

然后切换到 route guide 目录并编译示例的 .proto 文件:

cd examples/php/route_guide
./route_guide_proto_gen.sh

我们的示例是一个简单的路线映射应用程序,它允许客户端获取其路线上特征的信息,创建其路线摘要,并与服务器及其他客户端交换路线信息(例如交通更新)。

您还应该安装相关工具来生成客户端接口代码(以及用其他语言编写的服务器,用于测试)。例如,您可以按照 这些设置说明 来获取后者。

尝试一下!

要尝试示例应用程序,我们需要一个 gRPC 服务器在本地运行。例如,让我们编译并运行此仓库中的 Node.js 服务器:

cd ../../node
npm install
cd dynamic_codegen/route_guide
nodejs ./route_guide_server.js --db_path=route_guide_db.json

运行 PHP 客户端(在另一个终端中)

./run_route_guide_client.sh

接下来的章节将逐步指导您如何定义此 proto 服务,如何从它生成客户端库,以及如何创建使用该库的客户端 stub。

定义服务

首先,让我们看看我们正在使用的服务是如何定义的。gRPC 服务及其方法请求响应类型使用 Protocol Buffers 定义。您可以在 examples/protos/route_guide.proto 中查看我们示例的完整 .proto 文件。

要定义服务,请在您的 .proto 文件中指定一个命名的service

service RouteGuide {
   ...
}

然后,在您的服务定义中定义 rpc 方法,并指定它们的请求和响应类型。Protocol Buffers 允许您定义四种服务方法,所有这些方法都用于 RouteGuide 服务:

  • 一种简单 RPC,客户端向服务器发送请求,然后稍后接收响应,就像普通的远程过程调用一样。

    // Obtains the feature at a given position.
    rpc GetFeature(Point) returns (Feature) {}
    
  • 一种响应流 RPC,客户端向服务器发送请求,并接收回一个响应消息流。您可以通过在响应类型前放置 stream 关键字来指定响应流方法。

    // Obtains the Features available within the given Rectangle.  Results are
    // streamed rather than returned at once (e.g. in a response message with a
    // repeated field), as the rectangle may cover a large area and contain a
    // huge number of features.
    rpc ListFeatures(Rectangle) returns (stream Feature) {}
    
  • 一种请求流 RPC,客户端向服务器发送一系列消息。一旦客户端完成消息写入,它会等待服务器读取所有消息并返回其响应。您可以通过在请求类型前放置 stream 关键字来指定请求流方法。

    // Accepts a stream of Points on a route being traversed, returning a
    // RouteSummary when traversal is completed.
    rpc RecordRoute(stream Point) returns (RouteSummary) {}
    
  • 一种双向流 RPC,两端都向对方发送一系列消息。这两个流独立运行,因此客户端和服务器可以按他们喜欢的任何顺序读写:例如,服务器可以在写入响应之前等待接收所有客户端消息,或者它可以交替地读取一条消息然后写入一条消息,或者读写以其他组合进行。每个流中的消息顺序都得到保留。您可以通过在请求和响应类型前都放置 stream 关键字来指定这种方法。

    // Accepts a stream of RouteNotes sent while a route is being traversed,
    // while receiving other RouteNotes (e.g. from other users).
    rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
    

我们的 .proto 文件还包含我们服务方法中使用的所有请求和响应类型的协议缓冲区消息类型定义——例如,这是 Point 消息类型

// Points are represented as latitude-longitude pairs in the E7 representation
// (degrees multiplied by 10**7 and rounded to the nearest integer).
// Latitudes should be in the range +/- 90 degrees and longitude should be in
// the range +/- 180 degrees (inclusive).
message Point {
  int32 latitude = 1;
  int32 longitude = 2;
}

生成客户端代码

proto 文件的 PHP 客户端 stub 实现可以通过 gRPC PHP Protoc 插件生成。要编译该插件:

make grpc_php_plugin

要生成客户端 stub 实现的 .php 文件:

cd grpc
protoc --proto_path=examples/protos \
  --php_out=examples/php/route_guide \
  --grpc_out=examples/php/route_guide \
  --plugin=protoc-gen-grpc=bins/opt/grpc_php_plugin \
  ./examples/protos/route_guide.proto

或者,如果您从源代码构建了 grpc-php-plugin,则在 grpc/example/php/route_guide 目录下运行辅助脚本:

./route_guide_proto_gen.sh

examples/php/route_guide 目录中将生成许多文件。您不需要修改这些文件。

要加载这些生成的文件,请将此部分添加到 examples/php 目录下的 composer.json 文件中:

  "autoload": {
    "psr-4": {
      "": "route_guide/"
    }
  }

该文件包含:

  • 所有用于填充、序列化和检索我们的请求和响应消息类型的 Protocol Buffers 代码。
  • 一个名为 Routeguide\RouteGuideClient 的类,允许客户端调用在 RouteGuide 服务中定义的方法。

创建客户端

在本节中,我们将学习如何为我们的 RouteGuide 服务创建 PHP 客户端。您可以在 examples/php/route_guide/route_guide_client.php 中查看完整的示例客户端代码。

构建客户端对象

要调用服务方法,我们首先需要创建一个客户端对象,它是生成的 RouteGuideClient 类的一个实例。该类的构造函数需要我们想要连接的服务器地址和端口:

$client = new Routeguide\RouteGuideClient('localhost:50051', [
    'credentials' => Grpc\ChannelCredentials::createInsecure(),
]);

调用服务方法

现在让我们看看如何调用我们的服务方法。

简单 RPC

调用简单的 RPC GetFeature 几乎与调用本地异步方法一样简单。

$point = new Routeguide\Point();
$point->setLatitude(409146138);
$point->setLongitude(-746188906);
list($feature, $status) = $client->GetFeature($point)->wait();

如您所见,我们创建并填充一个请求对象,即一个 Routeguide\Point 对象。然后,我们调用 stub 上的方法,并将请求对象传递给它。如果没有错误,我们就可以从响应对象(即 Routeguide\Feature 对象)中读取来自服务器的响应信息。

print sprintf("Found %s \n  at %f, %f\n", $feature->getName(),
              $feature->getLocation()->getLatitude() / COORD_FACTOR,
              $feature->getLocation()->getLongitude() / COORD_FACTOR);
流式 RPC

现在我们来看看流方法。这里是我们调用服务器端流方法 ListFeatures 的地方,它返回一个地理 Feature 流:

$lo_point = new Routeguide\Point();
$hi_point = new Routeguide\Point();

$lo_point->setLatitude(400000000);
$lo_point->setLongitude(-750000000);
$hi_point->setLatitude(420000000);
$hi_point->setLongitude(-730000000);

$rectangle = new Routeguide\Rectangle();
$rectangle->setLo($lo_point);
$rectangle->setHi($hi_point);

$call = $client->ListFeatures($rectangle);
// an iterator over the server streaming responses
$features = $call->responses();
foreach ($features as $feature) {
  // process each feature
} // the loop will end when the server indicates there is no more responses to be sent.

$call->responses() 方法调用返回一个迭代器。当服务器发送响应时,foreach 循环中将返回一个 $feature 对象,直到服务器指示没有更多响应要发送。

客户端流方法 RecordRoute 类似,不同之处在于我们为每个要从客户端写入的点调用 $call->write($point),并获得一个 Routeguide\RouteSummary

$call = $client->RecordRoute();

for ($i = 0; $i < $num_points; $i++) {
  $point = new Routeguide\Point();
  $point->setLatitude($lat);
  $point->setLongitude($long);
  $call->write($point);
}

list($route_summary, $status) = $call->wait();

最后,让我们看看我们的双向流 RPC routeChat()。在这种情况下,我们只需向方法传递一个上下文,然后获取一个 BidiStreamingCall 流对象,我们可以使用它来写入和读取消息。

$call = $client->RouteChat();

要从客户端写入消息:

foreach ($notes as $n) {
  $point = new Routeguide\Point();
  $point->setLatitude($lat = $n[0]);
  $point->setLongitude($long = $n[1]);

  $route_note = new Routeguide\RouteNote();
  $route_note->setLocation($point);
  $route_note->setMessage($message = $n[2]);
  $call->write($route_note);
}
$call->writesDone();

要从服务器读取消息:

while ($route_note_reply = $call->read()) {
  // process $route_note_reply
}

双方总是会按写入顺序接收对方的消息,客户端和服务器都可以按任何顺序读写——流完全独立运行。