gRPC - 使用 Python 的 Hello World 应用
现在让我们创建一个基本的“Hello World”类型的应用,它将使用 gRPC 以及 Python。
.proto 文件
首先让我们在 **common_proto_files** 中定义 **greeting.proto** 文件 -
syntax = "proto3"; service Greeter { rpc greet (ClientInput) returns (ServerOutput) {} } message ClientInput { string greeting = 1; string name = 2; } message ServerOutput { string message = 1; }
现在让我们仔细看看上面代码块中的每一行 -
syntax = "proto3";
这里的 **"syntax"** 表示我们使用的 Protobuf 版本。因此,我们使用最新的版本 3,并且该模式因此可以使用对版本 3 有效的所有语法。
package tutorial;
这里的 **package** 用于冲突解决,例如,如果我们有多个具有相同名称的类/成员。
service Greeter { rpc greet(ClientInput) returns (ServerOutput) {} }
此块表示服务“**Greeter**”的名称和可以调用的函数名称“**greet**”。**"greet"** 函数接收类型为 **"ClientInput"** 的输入并返回类型为 **"ServerOutput"** 的输出。现在让我们看看这些类型。
message ClientInput { string greeting = 1; string name = 2; }
在上面的代码块中,我们定义了 **ClientInput**,它包含两个属性“**greeting**”和“**name**”,它们都是字符串。客户端应该将类型为“**ClientInput**”的对象发送到服务器。
message ServerOutput { string message = 1; }
在这里,我们还定义了,给定一个“**ClientInput**”,服务器将返回一个带有单个属性“**message**”的“**ServerOutput**”。服务器应该将类型为“**ServerOutput**”的对象发送到客户端。
现在,让我们为 Protobuf 类和 gRPC 类生成底层代码。为此,我们需要执行以下命令 -
python -m grpc_tools.protoc -I ..\common_proto_files\ -- python_out=../python --grpc_python_out=. greeting.proto
但是,请注意,要执行该命令,我们需要安装教程的 **设置** 部分中提到的正确依赖项。
这应该会自动生成我们使用 gRPC 所需的源代码。源代码将放置在 -
Protobuf class code: python/greeting_pb2.py Protobuf gRPC code: python/greeting_pb2_grpcpb2.py
设置 gRPC 服务器
现在我们已经定义了包含函数定义的 proto 文件,让我们设置一个可以调用这些函数的服务器。
让我们编写服务器代码来服务上述函数并将其保存在 **server.py** 中 -
示例
from concurrent import futures import grpc import greeting_pb2 import greeting_pb2_grpc class Greeter(greeting_pb2_grpc.GreeterServicer): def greet(self, request, context): print("Got request " + str(request)) return greeting_pb2.ServerOutput(message='{0} {1}!'.format(request.greeting, request.name)) def server(): server = grpc.server(futures.ThreadPoolExecutor(max_workers=2)) greeting_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server) server.add_insecure_port('[::]:50051') print("gRPC starting") server.start() server.wait_for_termination() server()
上面的代码在指定的端口启动一个 gRPC 服务器,并为我们在 proto 文件中编写的函数和服务提供服务。让我们遍历上面的代码 -
从 **main** 方法开始,我们在指定的端口创建一个 gRPC 服务器。
但在启动服务器之前,我们将要运行的服务分配给服务器,即在我们的例子中,是 **Greeter** 服务。
为此,我们需要将服务实例传递给服务器,因此我们继续创建服务实例,即在我们的例子中,是 **Greeter**。
服务实例需要提供 **.proto** 文件中存在的 method/function 的实现,即在我们的例子中,是 **greet** 方法。
该方法期望一个与 .proto 文件中定义的类型相同的对象,即对我们来说,是 **request**。
该方法处理上述输入,进行计算,然后应该在 **.proto** 文件中返回提到的输出,即在我们的例子中,是 **ServerOutput**。
设置 gRPC 客户端
现在我们已经编写了服务器的代码,让我们设置一个可以调用这些函数的客户端。
让我们编写客户端代码来调用上述函数并将其保存在 **client.py** 中 -
示例
import grpc import greeting_pb2 import greeting_pb2_grpc def run(): with grpc.insecure_channel('localhost:50051') as channel: stub = greeting_pb2_grpc.GreeterStub(channel) response = stub.greet(greeting_pb2.ClientInput(name='John', greeting = "Yo")) print("Greeter client received following from server: " + response.message) run()
上面的代码在指定的端口启动一个 gRPC 服务器,并为我们在 proto 文件中编写的函数和服务提供服务。让我们遍历上面的代码 -
从 **main** 方法开始,我们为与服务器的 gRPC 通信设置了一个 Channel。
然后,我们使用 channel 创建一个 **stub**。在这里,我们使用计划调用的函数的服务“**Greeter**”。stub 只不过是一个包装器,它隐藏了远程调用对调用者的复杂性。
然后,我们简单地创建 proto 文件中定义的预期输入,即在我们的例子中,是 **ClientInput**。我们硬编码了两个参数,即 **name** 和 **greeting**。
我们最终进行调用并等待服务器的结果。
所以,这就是我们的客户端代码。
客户端服务器调用
现在,我们已经定义了我们的 **proto** 文件,编写了我们的服务器和客户端代码,让我们继续执行此代码并查看实际情况。
要运行代码,请启动两个 shell。通过执行以下命令在第一个 shell 中启动服务器 -
python .\server.py
输出
我们将得到以下输出 -
gRPC starting
上述输出表示服务器已启动。
现在,让我们启动客户端。
python .\client.py
我们将看到以下输出 -
输出
Greeter client received following from server: Yo John!
现在,如果我们打开服务器日志,我们将看到以下数据 -
gRPC starting Got request greeting: "Yo" name: "John"
因此,正如我们所看到的,客户端能够按预期调用服务器,并且服务器通过向客户端回送问候来响应。