Skip to content
← 返回RPC 与 gRPC列表
协议与完整应用

图书查询RPC协议练习 ​

前置:理解客户端/服务端、请求响应、流式返回与期限;本机示例使用 Python 3.10+,依赖版本见下文。教学范围为proto3;Python3.10+;grpcio/grpcio-tools1.76.0;protobuf6.33.0。核对日期2026-10-17。示例未执行,本轮没有安装依赖、生成协议代码、启动服务或浏览器预览。完整源码和输入文件见下方「同源完整文件」;推演结论只适用于写明的模型。

完整图书查询协议 ​

完整图书查询协议包含GetBook与StreamBooks,固定示例书目仅两本,不接数据库。proto声明请求和返回类型,服务端按ID查找,流式按书号排序;客户端显式设置两秒期限。仅用于本机读者将来自跑,默认绑定127.0.0.1且无TLS,不能直接公网部署。

图书RPC预期结果 ​

图书RPC预期结果为GetBook的b1返回书名Algorithms,流式返回b1再b2。空ID返回INVALID_ARGUMENT,不存在的b9返回NOT_FOUND;服务没启动则客户端可报UNAVAILABLE。预期结果均未执行,生成文件也未在本轮产生。

RPC失败契约练习 ​

RPC失败契约练习分别判断空ID等参数错误、未知ID与客户端期限过短的状态,并说明取消后哪些工作可以停。若将方法改成预留图书,重试必须加入请求键与持久去重,不能保留当前纯查询假设直接承诺恰好一次。

同源完整文件 ​

以下README含前置、输入、读者自跑步骤、预期结果、失败、恢复与清理;只做静态核对,没有执行。

library.proto ​

proto
syntax = "proto3";
package library.v1;

service Library {
  rpc GetBook(GetBookRequest) returns (Book);
  rpc StreamBooks(ListBooksRequest) returns (stream Book);
}
message GetBookRequest { string id = 1; }
message ListBooksRequest {}
message Book {
  string id = 1;
  string title = 2;
  optional string subtitle = 3;
  reserved 4;
  reserved "legacy_author";
}

requirements.txt ​

text
grpcio==1.76.0
grpcio-tools==1.76.0
protobuf==6.33.0

server.py ​

python
from concurrent import futures
import grpc
import library_pb2 as pb
import library_pb2_grpc as rpc

BOOKS = {'b1': 'Algorithms', 'b2': 'Operating Systems'}

class Library(rpc.LibraryServicer):
    def GetBook(self, request, context):
        if not request.id:
            context.abort(grpc.StatusCode.INVALID_ARGUMENT, 'id required')
        if request.id not in BOOKS:
            context.abort(grpc.StatusCode.NOT_FOUND, 'book not found')
        return pb.Book(id=request.id, title=BOOKS[request.id])

    def StreamBooks(self, request, context):
        for key in sorted(BOOKS):
            if not context.is_active():
                return
            yield pb.Book(id=key, title=BOOKS[key])

def main():
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=2))
    rpc.add_LibraryServicer_to_server(Library(), server)
    if server.add_insecure_port('127.0.0.1:50051') == 0:
        raise RuntimeError('cannot bind teaching port')
    server.start()
    try:
        server.wait_for_termination()
    except KeyboardInterrupt:
        server.stop(2).wait()

if __name__ == '__main__':
    main()

client.py ​

python
import sys
import grpc
import library_pb2 as pb
import library_pb2_grpc as rpc

def main():
    identifier = sys.argv[1] if len(sys.argv) > 1 else 'b1'
    with grpc.insecure_channel('127.0.0.1:50051') as channel:
        stub = rpc.LibraryStub(channel)
        try:
            book = stub.GetBook(pb.GetBookRequest(id=identifier), timeout=2)
            print(book.id, book.title)
            for item in stub.StreamBooks(pb.ListBooksRequest(), timeout=2):
                print(item.id, item.title)
        except grpc.RpcError as error:
            print(error.code().name, error.details(), file=sys.stderr)
            return 2
    return 0

if __name__ == '__main__':
    sys.exit(main())

README.md ​

md
# gRPC调用契约:完整示例

示例未执行。核对日期2026-10-17。教学版本族:proto3;Python3.10+;grpcio/grpcio-tools1.76.0;protobuf6.33.0。

## 文件与依赖

文件:library.proto, requirements.txt, server.py, client.py。requirements.txt为同源依赖声明;所有代码和配置都随本文展示。本例的 Python 库版本由 requirements.txt 固定;协议生成与启动命令见下文,本轮没有安装依赖或生成代码。

## 输入与前置

前置:理解客户端与服务端、请求响应、服务端流和截止时间。使用 Python 3.10+,依赖 requirements.txt 中固定版本的 grpcio、grpcio-tools 和 protobuf;先生成本例协议代码,再在同机两个终端运行服务端与客户端。书号参数及状态预期见下节。

读者将来自跑(本轮没有执行):先在独立虚拟环境核对并安装requirements.txt;在本目录生成协议文件:

```text
python -m pip install -r requirements.txt
python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. library.proto
python server.py
```

另一个终端在同一目录用同一虚拟环境执行`python client.py b1`,失败样例为`python client.py b9`与`python client.py ""`。PowerShell/Bash均从本例目录调用,环境激活语法按平台选择。服务仅监听本机;按Ctrl+C优雅停服,确认终端退出后删除专用虚拟环境与生成的library_pb2.py、library_pb2_grpc.py即可,勿删除原schema。生成产物未提交也未在本轮生成,路径和导入关系由完整proto定义可重建。

## 预期结果、失败与恢复

GetBook(b1)预期id=b1,title=Algorithms;空ID为INVALID_ARGUMENT;b9为NOT_FOUND;StreamBooks按b1、b2依次给出。客户端超时可能与服务端成功并存,不能推定服务端回滚。

失败要保留输入和错误,先与正文的不变量及边界核对。没有真实服务或性能验证,不以这些模型输出证明生产可用性。若将本例查询扩展为有副作用的预留操作,需重新设计持久去重与重试契约。

## 独立练习

为空ID、未知ID和已过期限各写一个处理决定;再讨论将查询变成预留后如何重试。

参考答案:空ID是INVALID_ARGUMENT,不重试先修输入;未知ID是NOT_FOUND,查资源或修ID;期限失败需增加合理预算或减少工作,不能无限重试。预留有副作用,需要同一请求键和持久结果记录,超时后查询原请求状态。

先作答,再核对 ​

为空ID、未知ID和已过期限各写一个处理决定;再讨论将查询变成预留后如何重试。

逐步参考解答

空ID是INVALID_ARGUMENT,不重试先修输入;未知ID是NOT_FOUND,查资源或修ID;期限失败需增加合理预算或减少工作,不能无限重试。预留有副作用,需要同一请求键和持久结果记录,超时后查询原请求状态。

按请求值走到明确状态码 ​

GetBook 是查询,空字符串在查表前拒绝;合法形式但不存在的b9返回NOT_FOUND。StreamBooks则返回排序后的两本书,客户端读完还应检查流最终状态,不能把收到第一本当成整次RPC成功。图只描述原示例固定数据,不含数据库、网络实测或权限系统。

查看图表代码

来源与适用边界 ​

继续阅读