python3中使用gRPC

python3中使用grpc

官方demo:https://grpc.io/docs/languages/python/basics/

该案例包含对美国一些地理位置进行检索查询等操作。通过gRPC的方式,客户端通过传递不同类型的参数调用服务端方法并获取返回结果。

gRPC

gRPC中,客户端应用程序可以直接调用服务器(另一台机器上运行)应用程序中的方法,就像调用本地对象一样,这更容易创建分布式应用程序和服务。

在许多RPC系统中,gRPC是基于**定义服务(接口)**的思想,通过参数和返回类型指定可以被远程调用的方法

在服务器端,服务器实现了这个接口,并运行一个gRPC服务器来处理客户端调用。在客户端,客户端有一个存根stub(在某些语言中仅称为客户端),它提供与服务器相同的方法。

具体调用过程可以看这张图:https://grpc.io/docs/what-is-grpc/introduction/

gRPC客户端和服务器可以在各种环境中相互运行和通信,并且可以用任何gRPC支持的语言编写。例如,你可以用Java轻松创建一个gRPC服务器,客户端使用GoPythonRuby

Protocol Buffers

谷歌成熟的开源机制,用于序列化结构化数据

Protocol Buffers的数据结构定义在.proto后缀的文件中,通过message关键字定义,每条message都是一个逻辑信息记录,其中包含一系列键值对,

message Person{
	string name = zhangsn;
	int32 id = 2;
	bool has_ponycopter = 3;
}

定义完成之后,就可以使用protocol buffer 编译器编译.proto文件,然后生成所使用的语言可以访问的类。它们为每个字段提供了简单的访问函数,如name()set_name(),以及将整个结构序列化/解析为原始字节的方法。

此外,可以在普通的.proto文件中定义gRPC服务,使用RPC方法参数并返回指定为protocol buffer消息的类型:

// rpc方法和message定义
service Greeter {
  // 基本语法
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

python中使用grpc

安装grpc

使用pip进行安装

python -m pip install grpcio

安装python的grpc-tools,它包含协议编译器protoc和用于从.proto定义的服务生成服务器和客户端代码的特殊插件

python -m pip install grpcio-tools

官方route_guide案例

编写proto文件

按照需求和上述语法编写proto文件,在service中的定义RPC方法和message。注意需要在文件开始标明语法版本:syntax = "proto3";

定义service方法

​ 通过关键字serviceproto文件中定义服务,然后在服务中定义方法,包含请求和返回的类型。grpc允许定义四种服务方法:

  • 一个简单的RPC,客户端使用存根(stub)向服务器发送请求,并等待响应返回,就像普通的函数调用一样。

    rpc GetFeature(Point) returns (Feature) {}

  • 响应流RPC,客户端向服务器发送请求,并获取一个来读取返回的消息序列。客户端从流中读取数据,直到没有消息。定义该方法需要在返回值前面使用stream关键字。该结果返回是流,而不是一次返回,可以在返回大量数据时使用(返回大量特征)。
    rpc ListFeatures(Rectangle) returns (stream Feature) {}

  • 请求流RPC,客户端写入一系列消息并将它们发送到服务器,同样使用提供的流。一旦客户端写完消息,它就会等待服务器读取所有消息并返回响应。你可以在请求类型前面加上stream关键字来指定请求流方法。

    rpc RecordRoute(stream Point) returns (RouteSummary) {}

  • 双向流RPC,其中双方使用读写流发送一系列消息。这两个流是独立操作的,所以客户端和服务器可以按照自己喜欢的顺序读取和写入数据:例如,服务器可以等待接收所有客户端消息后再写入响应,或者可以交替读取一个消息,然后写入一个消息,或者其他读写的组合。每个流中的消息顺序被保留。你可以通过在请求和响应之前放置stream关键字来指定这种类型的方法。
    rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}

定义message

.proto文件还包含service中的方法使用的所有请求和响应类型的protocol buffer消息类型定义

// 地理坐标
message Point {
  int32 latitude = 1;
  int32 longitude = 2;
}
// 方形区域,通过两个点确定一个矩形
message Rectangle{
    Point lo = 1;
    Point hi = 2;
}
// 特征:名字和地理位置
message Feature{
    string name = 1;
    Point location = 2;
}

message RouteNote{
    Point location = 1;
    string message = 2;
}
// 路径统计
message RouteSummary{
    int32 point_conut = 1;
    int32 Feature_count = 2;
    int32 distance = 3;
    int32 elapsed_time = 4
}

编译proto文件,生成对应编程语言的代码

python -m grpc_tools.protoc -I protos --python_out=. --pyi_out=. --grpc_python_out=. ./protos/route_guide.proto
  • -I:proto文件夹

  • --xxx_out:生成文件的对应文件夹

  • 末尾为要编译的具体proto文件

编译之后会生成了名为route_guide_pb2.pyroute_guide_pb2_grpc.py的代码文件,其包含以下内容:

  • route_guide_pb2包含route_guide.proto定义的message的类

  • route_guide_pb2_grpc.py文件中包含了两个类和一个方法:

    • RouteGuideStub

      该类用于生成一个gRPC客户端,构造函数接收一个channel对象,proto定义的每个方法在该类中会有对应的一个属性(属性名字和方法名字相同)根据gRPC接收和返回的数据类型(单一类型或者stream),给属性赋不同类型的值。
      在这里插入图片描述

    • RouteGuideServicer类:

      对于proto中定义的每个service,都会生成一个相应的类,包含proto定义的rpc方法。这个类一般作为父类被子类继承,并根据业务需求重新定义的方法。

    • add_RouteGuideServicer_to_server方法:

      对于每一个service,这个方法用于将该service作为grpc.server对象登记在RPC服务器中。帮助服务器可以将不同的请求路由到对应的service中。该方法接收一个继承了RouteGuideServicer的子类。
      在这里插入图片描述

pb2中的2表示生成的代码遵循Protocol Buffers Python API version 2。版本1已经过时了。

创建gRPC 服务端

创建grpc server,该项工作分为两步:

  1. 根据具体功能,实现proto文件中的service中定义的接口
  2. 启动grpc服务,等待客户端请求和传递消息

首先,创建一个类RouteGuideServicer,并继承route_guide_pb2_grpc中的RouteGuideServicer类,然后实现父类中定义的方法:

from concurrent import futures
import time
import json
import math
import sys
import logging
sys.path.append("/home/sysj/GQ/OpenSourceProject/flwithflower/grpc/")

import route_guide_pb2_grpc
import route_guide_pb2
import grpc
# 读取本地坐标数据
def read_route_guide_database():
    feature_list = []
    with open(r"route_guide_server/route_guide_db.json") as route_guide_db_file:
        for item in json.load(route_guide_db_file):
            feature = route_guide_pb2.Feature(name=item['name'],
                                            location=route_guide_pb2.Point(
                                            latitude=item["location"]["latitude"],
                                            longitude=item["location"]["longitude"]
                                              ))
            feature_list.append(feature)
    return feature_list
# 根据位置查询本地数据是否包含该地点
def get_feature(feature_list,point):
    for feature in feature_list:
        print(feature)
        if feature.location == point:
            return feature
    return None
# 计算路径
def get_distance(start, end):
    """Distance between two points."""
    coord_factor = 10000000.0
    lat_1 = start.latitude / coord_factor
    lat_2 = end.latitude / coord_factor
    lon_1 = start.longitude / coord_factor
    lon_2 = end.longitude / coord_factor
    lat_rad_1 = math.radians(lat_1)
    lat_rad_2 = math.radians(lat_2)
    delta_lat_rad = math.radians(lat_2 - lat_1)
    delta_lon_rad = math.radians(lon_2 - lon_1)
    # Formula is based on http://mathforum.org/library/drmath/view/51879.html
    a = (pow(math.sin(delta_lat_rad / 2), 2) +
         (math.cos(lat_rad_1) * math.cos(lat_rad_2) *
          pow(math.sin(delta_lon_rad / 2), 2)))
    c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
    R = 6371000
    # metres
    return R * c
# 继承生成的RouteGuideServicer类,并重写方法
class RouteGuideService(route_guide_pb2_grpc.RouteGuideServicer):
    def __init__(self) -> None:
        self.db = read_route_guide_database()
    def GetFeature(self, request, context):
        feature = get_feature(self.db, request)
        if feature is None:
            return route_guide_pb2.Feature(name="",location=request)
        else:
            return feature
	## 获取一定区域内所有的feature
    # 响应为流的方法
    def ListFeatures(self, request, context):
        left = min(request.lo.longtitude,request.hi.longitude)
        right = max(request.lo.longitude,request.hi.longitude)
        top = max(request.lo.latitude,request.lo.latitude)
        bottom = min(request.lo.latitude,request.lo.latitude)
        for feature in self.db:
            if (feature.location.longitude >= left and
                feature.location.longitude <= right and
                feature.location.latitude >= bottom and
                feature.location.latitude <= top):
                yield feature
	## 路径记录
    # 请求为流的方法
    def RecordRoute(self, request_iterator, context):
        point_count = 0
        feature_count = 0
        distance = 0.0
        prev_point = None
        start_time = time.time()
        for point in request_iterator:
            point_count+=1
            if get_feature(self.db,point):
                feature_count+=1
            if prev_point:
                distance+=get_distance(prev_point,point)
            prev_point = point
        elapsed_time = time.time()-start_time
        return route_guide_pb2.RouteSummary(
            point_conut=point_count,
            feature_count=feature_count,
            distance=int(distance),
            elapsed_time=int(elapsed_time))
    # 请求和响应都为流的方法,它被传递一个请求值的迭代器,它本身也是一个响应值的迭代器。
    def RouteChat(self, request_iterator, context):
        prev_notes = []
        # 请求值的迭代器
        for new_note in request_iterator:
            for pre_note in prev_notes:
                if pre_note.location == new_note.location:
                    # 响应值的迭代器
                    yield prev_notes
            prev_notes.append(new_note)
def server():
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    route_guide_pb2_grpc.add_RouteGuideServicer_to_server(RouteGuideService(),server)
    server.add_insecure_port('[::]:8080')
    server.start()
    # print(" 50051 server start......")
    server.wait_for_termination()

if __name__=="__main__":
    # db = read_route_guide_database()
    # print(db)
    logging.basicConfig()
    server()

当实现了所有RouteGuide的方法后,就可以启动一个gPRC服务,然后,客户端可以请求该服务器:

def server():
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    route_guide_pb2_grpc.add_RouteGuideServicer_to_server(RouteGuideService(),server)
    server.add_insecure_port('[::]:50051')
    server.start()
    server.wait_for_termination()

serverstart()方法是非阻塞的。一个新的线程将被实例化来处理请求。调用server.start()的线程通常在此期间没有其他工作要做。在这种情况下,可以调用server.wait_for_termination()来阻塞调用线程,直到服务器终止。

创建gRPC客户端

通过实例化route_guide_pb2_grpc模块中RouteGuideStub类的对象来创建一个stub,然后通过该stub来调用定义的方法。

  • 对于返回单个响应的RPC方法(“response-unary”方法),gRPC Python同时支持同步(阻塞)和异步(非阻塞)控制流语义。
  • 对于响应流RPC方法,调用会立即返回响应值的迭代器。对迭代器的next()方法的调用会阻塞,直到从迭代器得到的响应可用为止

创建gRPC客户端

    logging.basicConfig()
    # 创建非安全通道,创建一个存根
    channel = grpc.insecure_channel('localhost:50051')
    stub = route_guide_pb2_grpc.RouteGuideStub(channel)
方法调用

调用返回单个响应的PRC方法

    ## 调用返回单个响应的方法
    point = route_guide_pb2.Point(latitude=409146138, longitude=-746188906)
    feature = stub.GetFeature(point)
    print(feature)

调用返回流的PRC方法,返回类型为生成器类型

	## 调用返回流的方法
    # 创建一个范围,返回经纬度在该范围内的所有地点
    rectangle = route_guide_pb2.Rectangle(
        lo=route_guide_pb2.Point(latitude=400000000, longitude=-750000000),
        hi=route_guide_pb2.Point(latitude=420000000, longitude=-730000000))
    print("Looking for features between 40, -75 and 42, -73")    
    features = stub.ListFeatures(rectangle)
    # print(features.next())  通过next()获取值
    # print(features.next())
    for feature in features:  # 直接通过for循环取值
        print("Feature called %s at %s" % (feature.name, feature.location))

[(img-WYdcp6jW-1684462359476)(]
调用请求为流的RecordRoute方法,类似于将迭代器传递给本地方法。它可以被同步或异步调用:

    ## 调用请求参数为流的方法,需要将一个迭代器参数传递给该方法
    # 获取feature_list
    feature_list = read_route_guide_database()
    # 通过yield语法,获取一个iterable对象。
    feature_generator = generate_generator(feature_list=feature_list)
    route_summary = stub.RecordRoute(feature_generator)
    print("Finished trip with %s points " % route_summary.point_conut)
    print("Passed %s features " % route_summary.Feature_count)
    print("Travelled %s meters " % route_summary.distance)
    print("It took %s seconds " % route_summary.elapsed_time)

调用双向流的RouteChat方法(与服务端一样),结合请求流和响应流的语义:

# 通过yield语法,获取一个iterable对象。
message_generate = generate_messages()
# 传入iterable对象
responses = stub.RouteChat(message_generate)
# 接收
for response in responses:
    print("Received message %s at %s" % (response.message, response.location))

参考:
https://grpc.io/docs/languages/python/basics/
https://grpc.io/docs/what-is-grpc/introduction/
https://github.com/grpc/grpc/blob/v1.54.0/examples/python/route_guide/route_guide_server.py

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值