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 参考文档