.proto 文件
.proto 文件 是 gRPC 和 Protocol Buffers 的接口定义文件,它描述了:
- 要传递什么数据(也就是消息体 message)。
- 要暴露什么接口(也就是服务 service 和它们的 方法)。
也就是一份规范文件,让客户端和服务端能按照相同的约定相互通信。
my_service.proto
syntax = "proto3"; // 指定使用 proto3 语法版本package demo; // 包名,方便生成代码时分模块service MyService {// 定义 RPC 服务rpc Predict(PredictRequest) returns (PredictResponse) {}
}message PredictRequest {string prompt = 1;
}message PredictResponse {string result = 1;
}
- 用 protoc 自动生成客户端/服务端代码
这里使用 python 来进行操作
python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. my_service.proto
- I. 指定 proto 搜索路径为当前目录
- -python_out=. 生成 my_service_pb2.py(定义消息类型)
- -grpc_python_out=. 生成 my_service_pb2_grpc.py(定义服务类接口)
因为 gRPC 要跨语言,所以必须有语言无关的描述文件:
-
.proto 就是统一规范
-
Protocol Buffers 定义数据传输结构(比 JSON 轻量、高速、强类型)
-
gRPC 框架用它生成客户端/服务端、自动序列化、自动网络传输
-
protoc 会以输入文件 my_service.proto 的文件名为基准自动生成对应的 Python 文件。
-
它没有单独的参数去自定义生成文件名,所以默认就是:
✅ my_service_pb2.py:
- 包含你 .proto 中定义的 message 和 enum 类型对应的 Python 类。
✅ my_service_pb2_grpc.py:
- 包含你 .proto 中定义的 service 和 RPC 方法对应的客户端和服务端桩代码。
<proto文件名>_pb2.py
<proto文件名>_pb2_grpc.py
文件 | 包含内容 | 用途 |
---|---|---|
my_service_pb2.py | 消息类型类(message、enum) | 序列化/反序列化数据 |
my_service_pb2_grpc.py | 服务类和客户端/服务端接口 | 实现服务逻辑/调用远程服务 |
- pb2.py → 负责数据模型
- pb2_grpc.py → 负责服务调用逻辑(RPC 接口)
服务端实现
from concurrent import futures
import grpc
import my_service_pb2
import my_service_pb2_grpc# 实现 RPC 服务
class MyService(my_service_pb2_grpc.MyServiceServicer):
# 是生成的服务端基类,这里我们继承它并实现 Predict 方法逻辑。def Predict(self, request, context):print(f"Received prompt: {request.prompt}")return my_service_pb2.PredictResponse(result=f"Hello, {request.prompt}!")# 启动 gRPC 服务器
def serve():server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))my_service_pb2_grpc.add_MyServiceServicer_to_server(MyService(), server)server.add_insecure_port("[::]:50051")server.start()print("gRPC Server running at 0.0.0.0:50051...")server.wait_for_termination()if __name__ == "__main__":serve()# python -m grpc_tools.protoc -I. --pytheon_out=. --grpc_python_out=. my_service.proto
✅ grpc.server(…) 创建 gRPC 服务器实例,并给它分配一个线程池(能同时处理多个请求)。
✅ add_MyServiceServicer_to_server(MyService(), server) 注册我们自己实现的服务逻辑。
✅ server.add_insecure_port(“[::]:50051”) 打开监听端口。
✅ server.start() 启动服务,wait_for_termination() 让服务持续监听,不会自动结束。
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'my_service_pb2', _globals)
- 动态注册生成消息类。
也就是:
- PredictRequest、PredictResponse 这些类在导入时动态创建,IDE 静态分析器(比如 PyCharm)无法提前知道它们存在,因此你用 IDE 查看时找不到定义。
- 但是程序运行时,这些类会存在,并能正常使用。
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x10my_service.proto\x12\x04\x64\x65mo\" \n\x0ePredictRequest\x12\x0e\n\x06prompt\x18\x01 \x01(\t\"!\n\x0fPredictResponse\x12\x0e\n\x06result\x18\x01 \x01(\t2E\n\tMyService\x12\x38\n\x07Predict\x12\x14.demo.PredictRequest\x1a\x15.demo.PredictResponse\"\x00\x62\x06proto3')
- AddSerializedFile(…) 把你 .proto 定义编译成的二进制描述数据注册进 DescriptorPool。
- 也就是把整个 my_service.proto 的结构信息(服务、消息类型、字段等)塞入一个描述池中。
客户端实现
import grpc
import my_service_pb2
import my_service_pb2_grpcdef run_client():channel = grpc.insecure_channel("localhost:50051")stub = my_service_pb2_grpc.MyServiceStub(channel)response = stub.Predict(my_service_pb2.PredictRequest(prompt="World111"))print(f"Server response: {response.result}")if __name__ == "__main__":run_client()
客户端主要做三件事:
- 建立 gRPC 连接(channel)。
- 使用自动生成的 stub 来调用服务端方法。
- 接收服务端返回的响应并打印。
代码部分 | 解释 |
---|---|
grpc.insecure_channel() | 使用未加密通道(适合本地测试,不推荐生产环境直接用这个,要用 TLS 版 secure_channel()) |
MyServiceStub(channel) | 这是 gRPC 自动生成的客户端类,内部已经帮你实现好了 RPC 序列化、反序列化逻辑 |
PredictRequest() | 使用自动生成的类构造 RPC 请求对象,这个类对应你的 proto 中定义的消息类型 |
stub.Predict(…) | 相当于远程调用服务端的 Predict(),gRPC 会自动序列化请求、网络传输、反序列化响应 |
response.result | 服务端返回的 PredictResponse 消息对象,取它的 result 字段就是你服务返回的内容 |
🚀 客户端 vs 服务端对应关系
服务端:
class MyService(my_service_pb2_grpc.MyServiceServicer):def Predict(self, request, context):return my_service_pb2.PredictResponse(result=f"Hello, {request.prompt}!")
客户端:
response = stub.Predict(my_service_pb2.PredictRequest(prompt="World111")
)
客户端调用 Predict() 时:
- 底层帮你序列化 prompt 参数并传给服务端
- 服务端执行对应逻辑并返回 result 给客户端
- 客户端拿到结果打印出来 🎉
也可以使用 go 来客户端,注意先
protoc --go_out=. --go-grpc_out=. my_service.proto
go
package mainimport ("context""log""time""google.golang.org/grpc"pb "path/to/generated/my_service" // 引入生成的包
)func main() {// 1️⃣ 建立 gRPC 连接conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure()) if err != nil {log.Fatalf("连接失败: %v", err)}defer conn.Close()// 2️⃣ 创建客户端存根client := pb.NewMyServiceClient(conn)// 3️⃣ 调用服务ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)defer cancel()resp, err := client.Predict(ctx, &pb.PredictRequest{Prompt: "Hello Go!"})if err != nil {log.Fatalf("调用错误: %v", err)}log.Printf("服务返回: %s\n", resp.Result)
}
总结
无论是 gRPC、腾讯的 tRPC,甚至国内很多 RPC 框架(例如阿里 HSF、蚂蚁 SOFA RPC),它们背后的实现思路都是一样的👇:
🔄共同点:
-
先定义接口协议(IDL)
用 proto、.thrift、.proto3 或者类似 IDL 文件描述你的服务、方法、消息。
-
代码生成器生成对应语言的客户端/服务端存根
protoc、trpcproto 或者对应语言插件 → 自动生成:
- 服务接口类(Stub / Service 接口)
- 消息类(序列化 / 反序列化逻辑)
-
服务端实现 Service 类逻辑
你只需继承生成的服务接口,然后实现方法逻辑即可。
-
客户端用生成的存根调用方法
就像调用本地函数一样,不需要手动拼接网络数据、序列化二进制流。
.proto 文件↓
[生成工具] 自动生成代码↓
服务端: 实现 Service 类
客户端: 使用 Client Stub 调用↓
底层负责网络传输、序列化、负载均衡、错误处理...
✅ 减少重复代码 — 不用你手写网络序列化部分
✅ 强类型检查 — 调用方/服务方都能知道数据结构
✅ 方便跨语言调用 — 一份协议,不同语言都能生成对应客户端、服务端
✅ 减少网络细节关注 — 把网络部分封装起来