python之gRPC初试
前言
本文章作为初学gRPC的一个记录,主要参考文档:Quick start gRPC
准备
-
python 3.5 +
-
pip 9.0.1 +
安装
- 安装gRPC
$pip install grpcio
- 安装gRPC tools
该工具包括了协议缓冲区编译器protoc和用于从.proto文件定义生成特定的服务器和客户端代码的插件。
$pip install grpcio-tools
示例
可以参考文档中的方式,通过github获取示例代码
# Clone the repository to get the example code:
$ git clone -b v1.50.0 --depth 1 --shallow-submodules https://github.com/grpc/grpc
# Navigate to the "hello, world" Python example:
$ cd grpc/examples/python/helloworld
编写proto文件
demo.proto
//需要使用的proto版本
syntax = "proto3";
//对服务的定义
service MsgService {
//服务提供的方法定义
rpc GetMsg (MsgRequest) returns (MsgResponse){}
}
//服务请求消息的定义
message MsgRequest {
string name = 1;
}
//服务响应消息的定义
message MsgResponse {
string msg = 1;
}
syntax指定了需要使用的proto协议的版本,具体的去吧大家可以自行搜索,我作为一个初学者目前还没有涉及到太深,后续会根据学习的深入对相关内容进行补充。(文件第一行非空白非注释行必须写syntax="proto3", 使用proto2则更换为proto2)
service MsgService 是对服务的定义,类似于一个class,或者interface。语法为service ServiceName {}。
在大括号里面的需要定义提供的服务方法,即rpc服务。rpc GetMsg (MsgRequest) returns (MsgResponse) {}表明定义了一个GetMsg的方法,客户端传递一个MsgRequest类型的请求体,该方法最后会返回一个MsgResponse类型的响应体。姑且可以看作是http协议中的request和response所荷载的信息。
message MsgRequest{}和message MsgResponse{}是对提供的服务方法中消息的定义。上述文件中的string name = 1;表明该消息体中包含一个string类型的,名称为name的变量, 等于1表示在该消息体中的索引位置(如果说错了多谢评论指正)。同理在响应体定义中,定义了一个string类型的名称为msg的变量。
通过proto文件生成_pb2.py和_pb2_grpc.py文件
_pb2.py文件会对每一个message进行对应的信息存储,在处理service和client文件的时候会使用
_pb2_grpc.py用来存储每个服务的server与client以及注册server的工具
执行命令,生成相应的文件
$python -m grpc_tools.protoc -I . --python_out=. --grpc_python_out=. demo.proto
命令表示将pb2 和 pb2_grpc文件都生成到当前目录,指定proto文件为执行命令目录下的demo.proto文件
这里不对两个文件内容进行张贴了,大家可以自己按照上面的步骤生成,然后去参考生成的文件内容即可。
编写server文件
demo_server.py
import grpc
import demo_pb2
import demo_pb2_grpc
from concurrent import futures
import time
_ONE_DAY_IN_SECONDS = 60 * 60 * 24
class MsgServicer(demo_pb2_grpc.MsgServiceServicer):
def GetMsg(self, request, context):
print("Received name: %s" % request.name)
return demo_pb2.MsgResponse(msg='Hello, %s!' % request.name)
def serve():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
demo_pb2_grpc.add_MsgServiceServicer_to_server(MsgServicer(), server)
server.add_insecure_port('[::]:5001')
server.start()
try:
while True:
time.sleep(_ONE_DAY_IN_SECONDS)
except KeyboardInterrupt:
server.stop(0)
if __name__ == '__main__':
serve()
首先定义服务和服务方法。
MsgServicer继承自通过工具生成的MsgServiceServicer。
需要实现一个定义的GetMsg方法。方法需要接收一个request(如果是流传递,则是request_iterator,本文先只针对单项情况下进行说明,后续补充关于流传递的内容)对象和一个context上下文对象
由于我们定义了request(即MsgResquest)包含一个name,所以这里可以通过request.name获取到客户端传递过来的值。
GetMsg定义返回一个MsgResponse的对象类型的结果,所以可以通过pb2获取到该类型,并对其赋值
通过对MsgServicer的实现,我们完成了服务端服务的定义
接下来是对服务的注册
首先通过grpc.server()来获取一个server的对象,grpc服务是支持多线程的,但是需要通过工具来配合使用,于是有了代码中的futures,并且定义了10个线程。
通过pb2_grpc文件中提供的注册方法,将该服务类进行注册
设置一个监听端口,本文是一个本地5001的端口
由于服务开启后不会一直保留(python的锅),所以通过一个while True进行阻塞
编写client文件
demo_client.py
import grpc
import demo_pb2
import demo_pb2_grpc
def run():
with grpc.insecure_channel('localhost:50051') as channel:
stub = demo_pb2_grpc.MsgServiceStub(channel)
response = stub.GetMsg(demo_pb2.MsgRequest(name='world'))
print("Client received: " + response.msg)
if __name__ == '__main__':
run()
(只需要关注run方法即可)
通过grpc申请一个channel,通过pb2_grpc中提供的客户端,进行客户端渠道对接stub = demo_pb2_grpc.MsgServiceStub(channel)
。请求rpc服务并获取响应结果,response=stub.GetMsg(demo_pb2.MsgRequest(name="world"))
运行
- 先开启服务端
python demo_server.py
- 再开启客户端
python demo_client.py
可以看到服务端处于一直运行状态,客户端启动后获取到响应结果,服务端也打印相关内容(如果存在打印处理)
- 服务端显示内容
- 客户端显示内容
结语
对于一个编程人来说,一个新的东西并不可怕,只要愿意动手,愿意尝试,也能对能力进行提升。保持学习,保持进步。