目录
原文链接:
1.使用Python实现gRPC通信
前言
本文主要讲述如何使用Python实现gRPC通信,并通过三个例子,由浅入深地进行说明。
注:本文基于Python3
项目结构
本文的代码结构如下,源代码均存放在本人的Github上,需要的话点击这里进行查阅。
├── README.md
├── hello_client.py
├── hello_server.py
├── helloworld_pb2.py
├── helloworld_pb2_grpc.py
├── http_client.py
├── http_server.py
└── proto
├── __init__.py
└── helloworld.proto
依赖安装
1.安装gRPC
pip install grpcio
2.安装gRPC tools
pip install grpcio-tools
注:gRPC tools包含了protobuf的编译器protoc,以及编译插件grpc_python_out(后面编译会用到)。
简单Demo
简单Demo使用的是官方例子,比较简单。
接口定义
// 文件位置:helloworld.proto
syntax = "proto3";
package helloworld;
service Greeter {
// 基础Demo
rpc SayHello (HelloRequest) returns (HelloResponse) {}
}
// 简单请求
message HelloRequest {
string name = 1;
}
// 简单响应
message HelloResponse {
string message = 1;
}
大体意思就是我们有一个Greeter服务,该服务提供一个SayHello接口,请求体是HelloRequest,包含一个name参数;响应体是一个HelloResponse,包含一个参数message。
编译proto
$ cd python_grpc
$ python -m grpc_tools.protoc -I./proto --python_out=. --grpc_python_out=. proto/helloworld.proto
编译后会生成helloworld_pb2.py和helloworld_pb2_grpc.py两份存根文件(stub)。
服务端的实现
# hello_server.py
import grpc
import random
from concurrent import futures
import helloworld_pb2
import helloworld_pb2_grpc
# 实现定义的方法
class Greeter(helloworld_pb2_grpc.GreeterServicer):
def SayHello(self, request, context):
return helloworld_pb2.HelloResponse(message='Hello {msg}'.format(msg=request.name))
def serve():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
# 绑定处理器
helloworld_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
server.add_insecure_port('[::]:50054')
server.start()
print('gRPC 服务端已开启,端口为50054...')
server.wait_for_termination()
if __name__ == '__main__':
serve()
服务端的实现主要分为两步,第一步是实现我们定义的接口,即接收请求,返回响应;第二步就是创建一个gRPC服务器实例,绑定处理函数并启动,监听特定端口,等待请求到来。
客户端的实现
# hello_client.py
import grpc
import helloworld_pb2, helloworld_pb2_grpc
def run():
# 本次不使用SSL,所以channel是不安全的
channel = grpc.insecure_channel('localhost:50054')
# 客户端实例
stub = helloworld_pb2_grpc.GreeterStub(channel)
# 调用服务端方法
response = stub.SayHello(helloworld_pb2.HelloRequest(name='World'))
print("Greeter client received: " + response.message)
if __name__ == '__main__':
run()
进阶Demo
上述简单Demo只是方便讲述gRPC的通信流程,实际参考价值不太大,因为字段的定义过于简单,所以这里提供了一个比较复杂的例子。
接口定义
// 文件位置:helloworld.proto
syntax = "proto3";
package helloworld;
service Greeter {
// 进阶Demo
rpc GetDeptUser (GetDeptUserRequest) returns (GetDeptUserResponse) {}
}
// 复杂请求
message GetDeptUserRequest {
uint32 dept_id = 1; // 部门
string dept_name = 2; // 部门名称
repeated uint32 uid_list = 3; // 用户id列表
map<string, string> filter = 4; // 筛选条件
}
// 复杂响应
message GetDeptUserResponse {
repeated BasicUser user_list = 1; // 用户列表
map<uint32, BasicUser> user_map = 2; // 用户哈希表
}
// 用户基本信息
message BasicUser {
uint32 id = 1;
string name = 2;
}
注:接口编译命令与上述一致,因为定义在同一份文件中
服务端的实现
关于服务端的实现,只列举接口实现部分,因为创建gRPC服务器实例的过程与上述一致。
写法一:
class Greeter(helloworld_pb2_grpc.GreeterServicer):
def GetDeptUser(self, request, context):
# 字段使用点号获取
dept_id = request.dept_id
dept_name = request.dept_name
uid_list = request.uid_list
if dept_id <= 0 or dept_name == '' or len(uid_list) <= 0:
return helloworld_pb2.GetDeptUserResponse()
print('dept_id is {0}, dept_name is {1}'.format(dept_id, dept_name))
user_list = []
user_map = {}
for id_ in uid_list:
uid = id_ + random.randint(0, 1000)
letters = 'qwertyuiopasdfghjklzxcvbnm'
name = "".join(random.sample(letters, 10))
user = helloworld_pb2.BasicUser()
user.id = uid
user.name = name
user_list.append(user) # 与正常的添加操作差不多
user_map[uid] = user
return helloworld_pb2.GetDeptUserResponse(user_list=user_list, user_map=user_map)
写法二:先定义对象,再赋值
class Greeter(helloworld_pb2_grpc.GreeterServicer):
def GetDeptUser(self, request, context):
rsp = helloworld_pb2.GetDeptUserResponse()
dept_id = request.dept_id
dept_name = request.dept_name
uid_list = request.uid_list
if dept_id <= 0 or dept_name == '' or len(uid_list) <= 0:
return rsp
print('dept_id is {0}, dept_name is {1}'.format(dept_id, dept_name))
user_list = []
for id_ in uid_list:
uid = id_ + random.randint(0, 1000)
letters = 'qwertyuiopasdfghjklzxcvbnm'
name = "".join(random.sample(letters, 10))
user = helloworld_pb2.BasicUser()
user.id = uid
user.name = name
user_list.append(user)
# 注意map的写法:rsp.user_map[uid] = user 的写法会报错
rsp.user_map[uid].id = uid
rsp.user_map[uid].name = name
# 注意map的写法:rsp.user_map = user_map,或者 rsp.user_map.update(user_map) 都会报错
rsp.user_list.extend(user_list)
return rsp
客户端的实现
客户端也有两种写法,与服务端类似。
写法一:
response = stub.GetDeptUser(helloworld_pb2.GetDeptUserRequest(dept_id=1, dept_name='dd', uid_list=[1, 2, 3]))
print(response.user_list)
print(response.user_map)
写法二:
user_req = helloworld_pb2.GetDeptUserRequest()
user_req.dept_id = 110
user_req.dept_name = 'police'
user_req.uid_list.append(1)
user_req.uid_list.append(2)
user_req.uid_list.append(3)
# 可用extend函数替换
# user_req.uid_list.extend([1, 2, 3])
response = stub.GetDeptUser(user_req)
print(response.user_list)
print(response.user_map)
使用HTTP作为中介
有时候设计上我们并不会让client和server直接进行RPC通信,而是通过HTTP做了一层转换,常见的做法是有一个HTTP网关,client的数据序列化后传输到server,server解析后发送响应数据,client收到后也进行解析。
这里也有一个例子加以说明(接口定义与上述一致):
服务端的实现
import random
import flask
from flask import request
import helloworld_pb2, helloworld_pb2_grpc
app = flask.Flask(__name__)
@app.route('/get_dept_user', methods=['POST'])
def get_dept_user():
# 接收client数据并进行反序列化
req_data = request.data
user_req = helloworld_pb2.GetDeptUserRequest()
user_req.ParseFromString(req_data)
# 拼接响应信息
dept_id = user_req.dept_id
dept_name = user_req.dept_name
print('dept_id is {0}, dept_name is {1}'.format(dept_id, dept_name))
uid_list = user_req.uid_list
user_list = []
for id_ in uid_list:
uid = id_ + random.randint(0, 1000)
letters = 'qwertyuiopasdfghjklzxcvbnm'
name = "".join(random.sample(letters, 10))
user = helloworld_pb2.BasicUser()
user.id = uid
user.name = name
user_list.append(user)
# 将响应信息序列化
rsp = helloworld_pb2.GetDeptUserResponse()
rsp.user_list.extend(user_list)
rsp_data = rsp.SerializeToString()
return rsp_data
if __name__ == '__main__':
app.run('0.0.0.0', port=5001)
服务端这里使用Flask构建了一个简单的HTTP服务器,client和server序列化数据使用的是SerializeToString函数,反序列化使用的是ParseFromString函数
客户端的实现
import requests
import helloworld_pb2, helloworld_pb2_grpc
if __name__ == '__main__':
user_req = helloworld_pb2.GetDeptUserRequest()
user_req.dept_id = 110
user_req.dept_name = 'police'
user_req.uid_list.append(1)
user_req.uid_list.append(2)
user_req.uid_list.append(3)
# 如果不是直接用gRPC,而是先经过HTTP,就得进行序列化
data = user_req.SerializeToString()
req_url = 'http://127.0.0.1:5001/get_dept_user'
try:
response = requests.post(req_url, data=data)
if response.status_code != 200:
print('request failed, code: ', response.status_code)
# 反序列化数据
rsp = helloworld_pb2.GetDeptUserResponse()
rsp.ParseFromString(response.content)
print('rsp: ', rsp.user_list)
except Exception as e:
print('request failed, err: ', e)
客户端这里使用request框架发送请求,同样也有序列化和反序列化的过程。
标签:req,name,dept,grpc,pb2,实践,helloworld,python,user From: https://www.cnblogs.com/davis12/p/18030002