基础教程
gRPC 在 PHP 中的基本教程介绍。
基础教程
本教程为 PHP 程序员提供了使用 gRPC 的基本介绍。
通过学习此示例,您将学习如何
- 在 .proto 文件中定义服务。
- 使用协议缓冲区编译器生成客户端代码。
- 使用 PHP gRPC API 为您的服务编写一个简单的客户端。
它假设您对 协议缓冲区 有一定的了解。请注意,本教程中的示例使用了协议缓冲区语言的 proto2 版本。
另请注意,目前,您只能在 PHP 中为 gRPC 服务创建客户端。使用另一种语言来创建 gRPC 服务器。
为什么要使用 gRPC?
我们的示例是一个简单的路线映射应用程序,它允许客户端获取有关其路线上要素的信息,创建其路线的摘要,并与服务器和其他客户端交换交通更新等路线信息。
借助 gRPC,我们可以在 .proto
文件中定义一次服务,并在 gRPC 支持的任何语言中生成客户端和服务器,这些客户端和服务器又可以在从大型数据中心内的服务器到您自己的平板电脑等各种环境中运行 — gRPC 为您处理不同语言和环境之间通信的所有复杂性。我们还获得了使用协议缓冲区的所有优点,包括高效的序列化、简单的 IDL 和轻松的接口更新。
示例代码和设置
本教程的示例代码位于grpc/grpc/examples/php/route_guide中。要下载该示例,请运行以下命令克隆 grpc
存储库及其子模块
git clone --recurse-submodules -b v1.66.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
然后更改到路线指南目录并编译示例的 .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 服务,如何从中生成客户端库,以及如何创建使用该库的客户端存根。
定义服务
首先,让我们看看我们正在使用的服务是如何定义的。使用协议缓冲区定义 gRPC 服务及其方法请求和响应类型。您可以在examples/protos/route_guide.proto
中查看示例的完整 .proto 文件。
要定义服务,您需要在 .proto 文件中指定一个名为 service
的名称
service RouteGuide {
...
}
然后在您的服务定义中定义 rpc
方法,指定它们的请求和响应类型。协议缓冲区允许您定义四种服务方法,所有这些方法都在 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;
}
生成客户端代码
可以通过 gRPC PHP Protoc 插件生成 proto 文件的 PHP 客户端存根实现。要编译该插件
make grpc_php_plugin
要生成客户端存根实现 .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/"
}
}
该文件包含
- 用于填充、序列化和检索请求和响应消息类型的所有协议缓冲区代码。
- 一个名为
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
对象。然后,我们在存根上调用该方法,并将请求对象传递给它。如果没有错误,那么我们可以从响应对象(即一个 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
}
每一方都会按照消息写入的顺序获取对方的消息,客户端和服务器都可以按照任何顺序读取和写入——流完全独立运行。