首页 > 编程语言 >grpc python 实践

grpc python 实践

时间:2024-02-23 17:24:28浏览次数:25  
标签:req name dept grpc pb2 实践 helloworld python user

目录
原文链接:
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

相关文章

  • 【Python&GIS】Python线矢量等距离取点/线等分取点&点创建矢量面
    ​        不多说,这是之前项目需求的代码,已经是去年的了一直没来的及发,今天抽出来一丢丢的空挡发一下。主要就是利用线矢量等距离生成点矢量,或者直接将线矢量等分生成点矢量,这个需求其实极限一下就是线转点了(将距离设置小一点)。顺便将点生成矩形面的代码也给出来,这里的......
  • python处理栅格数据
    字节序列:ReadRaster([xoff],[yoff],[xsize],[ysize],[buf_xsize],[buf_ysize],[buf_type],[band_list],[buf_pixel_space],[buf_line_space],[buf_band_space])xoff是列读取起点,默认值为0。yoff是行读取起点,默认值为0。xsize是读取的列数,默认为全部读取。ysize是读取的......
  • python 面向对象(进阶篇)
    上一篇《Python面向对象(初级篇)》文章介绍了面向对象基本知识:面向对象是一种编程方式,此编程方式的实现是基于对类和对象的使用类是一个模板,模板中包装了多个“函数”供使用(可以讲多函数中公用的变量封装到对象中)对象,根据模板创建的实例(即:对象),实例用于调用被包装在类中的函......
  • python使用栅格计算器
    重采样是指根据一类象元的信息内插出另一类象元信息的过程。在遥感中,重采样是从高分辨率遥感影像中提取出低分辨率影像的过程。常用的重采样方法有最邻近内插法、双线性内插法和三次卷积法内插。  ReadAsArray函数可以重采样读取的数据,并且指定输出缓冲区大小或传递一个已有......
  • UtilMeta - 简洁高效的 Python 后端元框架
    最近开源了我开发多年的一个Python后端框架:UtilMeta项目介绍UtilMeta是一个用于开发API服务的后端元框架,基于Python类型注解标准高效构建声明式接口与ORM查询,能够自动解析请求参数与生成OpenAPI文档,高效开发RESTful接口,产出的代码简洁清晰,并且支持使用主流Python......
  • python调用Javascript实践
    一、背景知识1、Node.jsjavacript的运行环境有两个:浏览器、node.jsNode.js是一个开源与跨平台的JavaScript运行时环境。它是一个可用于几乎任何项目的流行工具!Node.js在浏览器外运行V8JavaScript引擎(GoogleChrome的内核)。这使Node.js表现得非常出色。运行js的命......
  • python特殊的函数
    一、文件操作1.操作googlesheetcredentials_file_path=os.path.abspath("./credentials.json")#授权:authorize():这是pygsheets库中的一个函数,用于授权对GoogleSheets的访问。为了使用GoogleSheetsAPI,你需要有一个有效的OAuth2.0凭据,这个凭据通常是一......
  • Python打开https链接报错:unable to get local issuer certificate
    Python打开https链接报错:unabletogetlocalissuercertificate或安装Python3.10以上版本,需要openssl1.1.1以上版本,会导致openssl下面缺少证书,记录下安装方法问题描述:当使用urllib.urlopen打开一个https链接时抛出如下异常:urllib.error.URLError:<urlopenerror[SSL:......
  • Python使用GDAL
     调用GDAL库:fromosgeoimportgdalimportnumpyasnpimportcv2importmatplotlib.pyplotaspltimportmatplotlibnp.set_printoptions(threshold=np.inf)#使print大量数据不用符号...代替而显示所有dataset=gdal.Open("D:/modis/jf6.tif")print(dataset.Ge......
  • Python数据结构与算法05——快速排序
    快速排序:递归defquick_sort(aimlist,first,last):#打印当前排序状态print(aimlist)#如果子列表只有一个元素或没有元素,直接返回iffirst>=last:return#初始化低位、高位和中间值low=firstheigh=lastmid=aimli......