此博客用于记录学习gRPC(python)的过程。
gRPC
gRPC默认使用协议缓冲池(Protocol Buffers)进行服务器与客户端之间的交互,它是用于序列化结构化数据的成熟的谷歌开源机制。
大致流程是三步:
- Define a service in a
.proto
file. - Generate server and client code using the
protocol buffer compiler
. - Use the Python gRPC API to write a simple client and server for your service.
安装 gRPC
1 | python -m pip install --upgrade pip |
Protocal Buffer 3
官方推荐使用proto3语法,因为它更简洁、强大,也支持更多语言。
一个简单的.proto
文件示例:
1 | syntax = "proto3"; |
- 第一行的
syntax
指定portobuf的语法版本,必须在第一行,不过不指定,默认为proto2
message
用来定义客户端与服务器交互的数据结构,在上边例子中,string query=1;
称为field
,string/query/1分别为type/name/value- 每一个
field
有一个独特的值,用于在二进制消息格式中标识field
。这个值的范围是[1, 2^29-1(536870911)],其中,[19000,19999]是禁用的,是为协议缓冲区实现保留的。1-15使用1个字节编码,16-2047使用2个字节,所以常出现的filed
应该尽量给予小的值 - 注释是C/C++样式,使用
//
和/*...*/
repeated
字段用于传输列表数据,比如上边的int_list
,我就可以从客户端feed [1,2,3],或者从客户端返回[1,2,3]
proto与python数据类型对应表:
proto | Python |
---|---|
float/double | float |
int32/sint32/sfixed32 | int |
int64/uint32/uint64/sint64/fixed32/fixed64/sfixed64 | int/long |
bool | bool |
string | str/unicode |
Bytes | str |
默认值:
- strings, empty string
- bytes, empty bytes
- bools, false
- numeric types, 0
- enums, 默认值为第一个定义的枚举值,必须为0
在message
中使用枚举,可以这么定义:
1 | message Cooking { |
在python端可以用以下三种方式定义枚举字段:
1 | name_pb2.Cooking.CUCUMBER |
对于客户端可RPC调用服务器的方式主要有四种,可以这样写:
1 | syntax = "proto3"; |
重点在stream
修饰符。
就拿rpc GetFeature(Point) returns (Feature) {}
来说,它生成的服务器端函数接口应该是这样的:
def GetFeature(self, request, context):
其中,request即为客户端发送的Point
消息类,context提供特定于rpc的信息,如超时限制,该方法返回给客户端Feature
类。
下载官方示例
1 | Clone the repository to get the example code: |
1 | server |
生成服务器与客户端代码
1 | python -m grpc_tools.protoc -I ../../protos --python_out=. --grpc_python_out=. ../../protos/[name].proto |
--python_out
:指定编译生成处理 protobuf 相关的代码的路径--grpc_python_out
:指定编译生成处理 grpc 相关的代码的路径-I
:指定proto 文件的目录,然后再写文件路径
这条命令会生成两个文件:
name_pb2.py
包含我们定义的请求(request)、响应(response)类。name_pb2_grpc.py
包含服务器(server)、客户端(client)类。&Stub
类被客户端调用服务端RPC&Servicer
类定义实现服务端代码的接口- 方法
add_&Servicer_to_server
,把我们定义的服务器类添加到grpc.Server
编写服务器
主要步骤:
- 实现服务端接口函数。Implementing the servicer interface generated from our service definition with functions that perform the actual “work” of the service.
- 开启服务器并监听客户端请求,产生响应。Running a gRPC server to listen for requests from clients and transmit responses.
简而言之,就是重载name_pb2_grpc.py
中&Servicer
类的所有RPC方法。比如:
1 | class RouteGuideServicer(route_guide_pb2_grpc.RouteGuideServicer): |
接下来就是开启服务器,监听客户端请求了。
1 | def serve(): |
编写客户端
创建存根(stub),从name_pb2_grpc.py
中实例化&Stub
类。
1 | channel = grpc.insecure_channel('localhost:50051') |
或者使用with
上下文管理器:
1 | with grpc.insecure_channel('localhost:50051') as channel: |
使用存根调用服务器端的方法,可以有同步与异步两种。
同步:feature = stub.GetFeature(point)
异步:
1 | feature_future = stub.GetFeature.future(point) |