1. 简介
在此 Codelab 中,您将使用 gRPC-Python 创建一个客户端和服务器,它们将构成以 Python 编写的路线映射应用的基础。
在本教程结束时,您将拥有一个使用 gRPC 连接到远程服务器的客户端,以获取地图上特定坐标处的位置名称或邮寄地址。一个功能完善的应用可能会使用这种客户端-服务器设计来枚举或总结路线沿途的兴趣点。
该服务在 Protocol Buffers 文件中定义,该文件将用于为客户端和服务器生成样板代码,以便它们能够相互通信,从而节省您实现该功能的时间和精力。
生成的代码不仅能处理服务器与客户端之间复杂的通信,还能处理数据序列化和反序列化。
学习内容
- 如何使用 Protocol Buffers 定义服务 API。
- 如何使用自动代码生成功能基于 Protocol Buffers 定义构建基于 gRPC 的客户端和服务器。
- 了解使用 gRPC 进行客户端-服务器通信。
此 Codelab 适合刚开始使用 gRPC 或希望复习 gRPC 的 Python 开发者,也适合任何对构建分布式系统感兴趣的人。无需具备 gRPC 经验。
2. 准备工作
所需条件
- Python 3.9 或更高版本。我们建议使用 Python 3.13。如需查看针对具体平台的安装说明,请参阅 Python 设置和使用。或者,使用 uv 或 pyenv 等工具安装非系统 Python。
- 使用 pip 安装 Python 软件包。
- venv 来创建 Python 虚拟环境。
ensurepip
和 venv
软件包是 Python 标准库的一部分,通常默认可用。
不过,一些基于 Debian 的发行版(包括 Ubuntu)在重新分发 Python 时选择排除这些文件。如需安装软件包,请运行以下命令:
sudo apt install python3-pip python3-venv
获取代码
为了简化学习过程,此 Codelab 提供了预构建的源代码框架,可帮助您快速入门。以下步骤将引导您完成应用,包括使用 grpc_tools.protoc
Protocol Buffer 编译器插件生成 gRPC 代码。
grpc-codelabs
此 Codelab 的框架源代码位于 codelabs/grpc-python-getting-started/start_here 目录中。如果您不想自己实现代码,可以在 completed
目录中找到已完成的源代码。
首先,创建 Codelab 工作目录并进入该目录:
mkdir grpc-python-getting-started && cd grpc-python-getting-started
下载并解压缩 Codelab:
curl -sL https://github.com/grpc-ecosystem/grpc-codelabs/archive/refs/heads/v1.tar.gz \
| tar xvz --strip-components=4 \
grpc-codelabs-1/codelabs/grpc-python-getting-started/start_here
或者,您也可以下载仅包含 Codelab 目录的 .zip 文件,然后手动将其解压缩。
3. 定义服务
第一步是使用 Protocol Buffers 接口定义语言定义应用的 gRPC 服务、其 RPC 方法以及其请求和响应消息类型。您的服务将提供:
- 一种名为
GetFeature
的 RPC 方法,由服务器实现并由客户端调用。 - 消息类型
Point
和Feature
是使用GetFeature
方法时在客户端和服务器之间交换的数据结构。客户端在其GetFeature
请求中向服务器提供地图坐标作为Point
,服务器则会回复相应的Feature
,其中描述了位于这些坐标处的任何内容。
此 RPC 方法及其消息类型都将在所提供源代码的 protos/route_guide.proto
文件中定义。
Protocol Buffers 通常称为 protobuf。如需详细了解 gRPC 术语,请参阅 gRPC 的核心概念、架构和生命周期。
消息类型
在源代码的 protos/route_guide.proto
文件中,首先定义 Point
消息类型。Point
表示地图上的纬度-经度坐标对。在此 Codelab 中,请使用整数作为坐标:
message Point {
int32 latitude = 1;
int32 longitude = 2;
}
数字 1
和 2
是 message
结构中每个字段的唯一 ID 编号。
接下来,定义 Feature
消息类型。Feature
使用 string
字段来表示由 Point
指定的位置处的某个事物的名称或邮政地址:
message Feature {
// The name or address of the feature.
string name = 1;
// The point where the feature is located.
Point location = 2;
}
服务方法
route_guide.proto
文件具有名为 RouteGuide
的 service
结构,用于定义应用服务提供的一个或多个方法。
在 RouteGuide
定义中添加 rpc
方法 GetFeature
。如前所述,此方法将根据给定的坐标集查找某个位置的名称或地址,因此让 GetFeature
为给定的 Point
返回 Feature
:
service RouteGuide {
// Definition of the service goes here
// Obtains the feature at a given position.
rpc GetFeature(Point) returns (Feature) {}
}
这是一元 RPC 方法:一种简单 RPC,其中客户端向服务器发送请求并等待响应返回,就像本地函数调用一样。
4. 生成客户端和服务器代码
接下来,使用协议缓冲区编译器从 .proto
文件中为客户端和服务器生成样板 gRPC 代码。
对于 gRPC Python 代码生成,我们创建了 grpcio-tools。其中包括:
- 常规的 protoc 编译器,用于根据
message
定义生成 Python 代码。 - gRPC protobuf 插件,可根据
service
定义生成 Python 代码(客户端和服务器桩)。
我们将使用 pip 安装 grpcio-tools
Python 软件包。让我们创建一个新的 Python 虚拟环境 (venv),以将项目的依赖项与系统软件包隔离开来:
python3 -m venv --upgrade-deps .venv
在 bash/zsh shell 中激活虚拟环境:
source .venv/bin/activate
对于 Windows 和非标准 shell,请参阅 https://docs.python.org/3/library/venv.html#how-venvs-work 中的表格。
接下来,安装 grpcio-tools(这也会安装 grpcio 软件包):
pip install grpcio-tools
使用以下命令生成 Python 样板代码:
python -m grpc_tools.protoc --proto_path=./protos \
--python_out=. --pyi_out=. --grpc_python_out=. \
./protos/route_guide.proto
这将为我们在 route_guide.proto
中定义的接口生成以下文件:
route_guide_pb2.py
包含根据message
定义动态创建类的代码。route_guide_pb2.pyi
是根据message
定义生成的 “桩文件”或“类型提示文件”。它仅包含签名,不包含实现。IDE 可以使用桩文件来提供更好的自动补全和错误检测功能。route_guide_pb2_grpc.py
是根据service
定义生成的,包含特定于 gRPC 的类和函数。
gRPC 专用代码包含:
5. 创建服务
首先,我们来看看如何创建 RouteGuide
服务器。创建和运行 RouteGuide
服务器分为两个工作项:
- 实现从服务定义生成的服务接口,其中包含执行服务实际“工作”的函数。
- 在特定端口运行 gRPC 服务器,以监听来自客户端的请求并传输响应。
您可以在 start_here/route_guide_server.py
中找到初始 RouteGuide
服务器。
实现 RouteGuide
route_guide_server.py
具有一个 RouteGuideServicer
类,该类是生成的类 route_guide_pb2_grpc.RouteGuideServicer
的子类:
# RouteGuideServicer provides an implementation
# of the methods of the RouteGuide service.
class RouteGuideServicer(route_guide_pb2_grpc.RouteGuideServicer):
RouteGuideServicer
实现所有 RouteGuide
服务方法。
下面我们来详细了解一下简单的 RPC 实现。方法 GetFeature
从客户端获取 Point
,并从其数据库中返回 Feature
中的相应功能信息。
def GetFeature(self, request, context):
feature = get_feature(self.db, request)
if feature is None:
return route_guide_pb2.Feature(name="", location=request)
else:
return feature
该方法会传递一个针对 RPC 的 route_guide_pb2.Point
请求,以及一个提供 RPC 特定信息(例如超时限制)的 grpc.ServicerContext
对象。它会返回 route_guide_pb2.Feature
响应。
启动服务器
实现所有 RouteGuide
方法后,下一步是启动 gRPC 服务器,以便客户端能够实际使用您的服务:
def serve():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
route_guide_pb2_grpc.add_RouteGuideServicer_to_server(
RouteGuideServicer(),
server,
)
listen_addr = "localhost:50051"
server.add_insecure_port(listen_addr)
print(f"Starting server on {listen_addr}")
server.start()
server.wait_for_termination()
服务器 start()
方法是非阻塞的。系统将实例化一个新线程来处理请求。调用 server.start()
的线程通常在此期间不会有任何其他工作要做。在这种情况下,您可以调用 server.wait_for_termination()
来干净地阻塞调用线程,直到服务器终止。
6. 创建客户端
在本部分中,我们将了解如何为 RouteGuide
服务创建客户端。您可以在 start_here/route_guide_client.py
中查看初始客户端代码。
创建桩
如需调用服务方法,我们首先需要创建 stub。
我们实例化了 route_guide_client.py
文件中由 .proto
生成的 route_guide_pb2_grpc
模块的 RouteGuideStub
类。
channel = grpc.insecure_channel("localhost:50051")
stub = route_guide_pb2_grpc.RouteGuideStub(channel)
调用服务方法
对于返回单个响应的 RPC 方法(称为“response-unary”方法),gRPC Python 同时支持同步(阻塞)和异步(非阻塞)控制流语义。
简单 RPC
首先,我们来定义一个 Point
以调用该服务。这应该很简单,只需使用一些属性从 route_guide_pb2
软件包中实例化一个对象即可:
point = route_guide_pb2.Point(latitude=412346009, longitude=-744026814)
对简单 RPC GetFeature
的同步调用几乎与调用本地方法一样简单。RPC 调用会等待服务器响应,并返回响应或引发异常。我们可以调用该方法并查看如下所示的响应:
feature = stub.GetFeature(point)
print(feature)
您可以检查 Feature 对象的字段并输出请求的结果:
if feature.name:
print(f"Feature called '{feature.name}' at {format_point(feature.location)}")
else:
print(f"Found no feature at at {format_point(feature.location)}")
7. 试试看
运行服务器:
python route_guide_server.py
在另一个终端中,再次激活虚拟环境,然后运行客户端:
python route_guide_client.py
您将看到如下输出(为清晰起见,省略了时间戳):
name: "16 Old Brook Lane, Warwick, NY 10990, USA" location { latitude: 412346009 longitude: -744026814 } Feature called '16 Old Brook Lane, Warwick, NY 10990, USA' at latitude: 412346009, longitude: -744026814
8. 后续步骤
- 如需了解 gRPC 的运作方式,请参阅 gRPC 简介和核心概念
- 完成基础知识教程
- 探索 Python API 参考文档