gRPC 使用(python 版本)

目录

.proto 文件

.proto 文件gRPC 和 Protocol Buffers 的接口定义文件,它描述了:

  1. 要传递什么数据(也就是消息体 message)。
  2. 要暴露什么接口(也就是服务 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;
}
  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_grpc

def 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()

客户端主要做三件事:

  1. 建立 gRPC 连接(channel)。
  2. 使用自动生成的 stub 来调用服务端方法。
  3. 接收服务端返回的响应并打印。
代码部分 解释
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 main

import (
    "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),它们背后的实现思路都是一样的👇:

🔄共同点:

  1. 先定义接口协议(IDL)

    用 proto、.thrift、.proto3 或者类似 IDL 文件描述你的服务、方法、消息。

  2. 代码生成器生成对应语言的客户端/服务端存根

    protoc、trpcproto 或者对应语言插件 → 自动生成:

    • 服务接口类(Stub / Service 接口)
    • 消息类(序列化 / 反序列化逻辑)
  3. 服务端实现 Service 类逻辑

    你只需继承生成的服务接口,然后实现方法逻辑即可。

  4. 客户端用生成的存根调用方法

    就像调用本地函数一样,不需要手动拼接网络数据、序列化二进制流。

.proto 文件
    
[生成工具] 自动生成代码
    
服务端: 实现 Service 
客户端: 使用 Client Stub 调用
    
底层负责网络传输序列化负载均衡错误处理...

减少重复代码 — 不用你手写网络序列化部分

强类型检查 — 调用方/服务方都能知道数据结构

方便跨语言调用 — 一份协议,不同语言都能生成对应客户端、服务端

减少网络细节关注 — 把网络部分封装起来

点赞一下

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦