秒懂gRPC

一,gRPC简介。

image.png



二,HTTP1.1的不足和HTTP2.0概述。

   

    1) HTTP1.1

image.png

    HTTP1.1存在如下问题:

image.png

image.png

image.png

        2) HTTP2.0

        image.png

    image.png


    socket中“流”的概念:

image.png

image.png


image.png

image.png

image.png

image.png

image.png

三,gRPC的接口类型。

image.png

image.png


image.png

image.png

image.png



四,gRPC的ProtocolBuffers.

image.png

image.png

image.png

image.png

image.png


image.png


image.png

image.png

image.png


image.png

    类型  名称  = 编号



image.png

image.png

image.png

image.png


image.png

image.png

image.png

image.png

image.png

image.png



五,案例接口定义与代码生成。


pycharm中安装插件后,如下设置--->>>>

image.png


在当前虚拟环境中,安装protobuf编译器和grpc工具

image.png


创建protos文件夹和mydemo.proto文件

syntax = 'proto3';
message Work{
    enum Operation{
        ADD = 0; // 枚举第一个值一定为0;
        SUBTRACT = 1;
        MULTIPLY = 2;
        DIVIDE = 3;
    }
    int32 num1 = 1;
    int32 num2 = 2;
    Operation op = 3;
}
message Result {
    int32 val = 1;
}
message City{
    string name = 1;
}
message Subject{
    string name = 1;
}
message Delta{
    int32 val = 1;
}
message Sum{
    int32 val = 1;
}
message Number{
    int32 val = 1;
}
message Answer{
    int32 val = 1; //结果信息
    string desc = 2; // 描述信息
}
service Demo{
    // unary rpc
    // 计算处理
    rpc Calculate(Work) returns(Result){}
    //server streaming rpc
    // 根据城市获取不同开发语言
    rpc GetSubjects(City) returns(stream Subject){}
    //client streaming rpc
    //客户端发送多个请求数据,服务器返回这些数据的累计和
    rpc Accumulate(stream Delta) returns(Sum){}
    // bidirectional streaming rpc
    // 猜数字, 客户端向服务端发送多个数据,如果是服务端认可的数据就返回响应,否则忽略
    rpc GuessNumber(stream Number) returns(stream Answer){}
}


编译生成代码:

进入到*.proto的目录下,执行如下命令。

python -m grpc_tools.protoc -I.  --python_out=..  --grpc_python_out=.. mydemo.proto

image.png

image.png

重新查看文件,就可以发现,自动生成如下的两个文件。

image.png

六,服务器与客户端的编写

分别创建server.py文件和client.py文件

image.png

1) 一元调用RPC的实现。

server.py文件的代码如下:

import mydemo_pb2_grpc
import mydemo_pb2
import grpc  # 当中定义了状态
from concurrent import futures  # 为了传递线程池对象
import time
# 第一部分,实现被调用的方法的具体代码
class DemoServicer(mydemo_pb2_grpc.DemoServicer):
    def Calculate(self, request, context):
        """
        :param request: 保存了请求的消息数据----》》》此处为Work类型
        :param context:  context ----》》》 本次响应的状态码和相关描述
        :return: 
        """
        if request.op == mydemo_pb2.Work.ADD:
            result = request.num1 + request.num2
            return mydemo_pb2.Result(val = result)
        elif request.op == mydemo_pb2.Work.SUBTRCT:
            result = request.num1 - request.num2
            return mydemo_pb2.Result(val = result)
        elif request.op == mydemo_pb2.Work.MULTIPLY:
            result = request.num1 * request.num2
            return mydemo_pb2.Result(val = result)
        elif request.op == mydemo_pb2.Work.DIVDE:
            if request.op == 0:
                # 通过设置响应状态码和描述字符串来达到抛出异常的目的
                context.setcode(grpc.StatusCode.INVALID_ARGUMENT)
                context.set_details('cannot divide by 0!')
                return mydemo_pb2.Result()
            result = request.num1 // request.num2
            return mydemo_pb2.Result(val = result)
        else:
            # 通过设置响应状态码和描述字符串来达到抛出异常的目的
            context.setcode(grpc.StatusCode.INVALID_ARGUMENT)
            context.set_details('invalid operation!')
            return mydemo_pb2.Result()
    
# 第二步骤,开启服务器,对外提供rpc调用
def serve():
    # 1) 创建服务器对象----->>> 已经被封装为多线程的服务器, 所以需要传递一个线程池对象
    server = grpc.server(futures.ThreadPoolExecutor)  
    
    # 2)注册实现的服务方法到服务器对象中--->>>生成的文件中,已经提供了
    mydemo_pb2_grpc.add_DemoServicer_to_server(DemoServicer(),server) # 参数一个为函数处理的实例对象,第二个为服务器对象
    
    # 3) 为服务器设置地址
    server.add_insecure_port('127.0.0.1:8888')
    
    # 4) 开启服务
    print('服务器已经开启')
    server.start()
    
    # 5) 关闭服务 --->>> 如下编写实现ctrl + c
    try:
        time.sleep(1000)
    except KeyboardInterrupt:
        server.stop()
if __name__ == '__main__':
    serve()

client.py 一元调用的时候,代码如下所示:

import grpc
import mydemo_pb2_grpc
import mydemo_pb2
def invoke_calculate(stub):
    # 设置参数
    work = mydemo_pb2.Work()
    work.num1 = 100
    work.num2 = 20
    
    work.op = mydemo_pb2.Work.ADD
    result = stub.Calculate(work)
    print('100 + 20 = {}'.format(result.val))
    
    
    work.op = mydemo_pb2.Work.SUBTRACT
    result = stub.Calculate(work)
    print('100 - 20 = {}'.format(result.val))
    
    
    work.op = mydemo_pb2.Work.MULTIPLY
    result = stub.Calculate(work)
    print('100 * 20 = {}'.format(result.val))
    
    
    work.op = mydemo_pb2.Work.DIVIDE
    result = stub.Calculate(work)
    print('100 // 20 = {}'.format(result.val))
    
    
    # 异常的情况
    work.num2 = 0
    try:
        result = stub.Calculate(work)
        print('100 // 20 = {}'.format(result.val))
    except grpc.RpcError as e:
        print('{}:{}'.format(e.code(),e.details()))
def run():
    # 将channel放入到上下文管理器
    with grpc.insecure_channel('127.0.0.1:8888') as channel:
        stub = mydemo_pb2_grpc.DemoStub(channel)
        invoke_calculate(stub)
if __name__ == '__main__':
    run()

运行结果如下:

image.png

image.png


2)   服务器流式RPC的实现。

server.py

import mydemo_pb2_grpc
import mydemo_pb2
import grpc  # 当中定义了状态
from concurrent import futures  # 为了传递线程池对象
import time
# 第一部分,实现被调用的方法的具体代码
class DemoServicer(mydemo_pb2_grpc.DemoServicer):
    def __init__(self):
        self.city_subject_db = {
            "beijing":['python', "c++", "go", "测试","运维", "java", "php"],
            "shanghai":['王者荣耀', "游戏开发", "go", "测试","运维", "java", "php"],
            "wuhan": ['python', "c++", "go", "测试"],
        } # 假定这个是从数据库查询的数据
    def Calculate(self, request, context):
        """
        :param request: 保存了请求的消息数据----》》》此处为Work类型
        :param context:  context ----》》》 本次响应的状态码和相关描述
        :return:
        """
        if request.op == mydemo_pb2.Work.ADD:
            result = request.num1 + request.num2
            return mydemo_pb2.Result(val = result)
        elif request.op == mydemo_pb2.Work.SUBTRACT:
            result = request.num1 - request.num2
            return mydemo_pb2.Result(val = result)
        elif request.op == mydemo_pb2.Work.MULTIPLY:
            result = request.num1 * request.num2
            return mydemo_pb2.Result(val = result)
        elif request.op == mydemo_pb2.Work.DIVIDE:
            if request.op == 0:
                # 通过设置响应状态码和描述字符串来达到抛出异常的目的
                context.setcode(grpc.StatusCode.INVALID_ARGUMENT)
                context.set_details('cannot divide by 0!')
                return mydemo_pb2.Result()
            result = request.num1 // request.num2
            return mydemo_pb2.Result(val = result)
        else:
            # 通过设置响应状态码和描述字符串来达到抛出异常的目的
            context.setcode(grpc.StatusCode.INVALID_ARGUMENT)
            context.set_details('invalid operation!')
            return mydemo_pb2.Result()
    def GetSubjects(self, request, context):
        city = request.name
        subjects = self.city_subject_db.get(city)
        for subject in subjects:
            yield mydemo_pb2.Subject(name = subject) # 注意:yield实现流式
# 第二步骤,开启服务器,对外提供rpc调用
def serve():
    # 1) 创建服务器对象----->>> 已经被封装为多线程的服务器, 所以需要传递一个线程池对象
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    # 2)注册实现的服务方法到服务器对象中--->>>生成的文件中,已经提供了
    mydemo_pb2_grpc.add_DemoServicer_to_server(DemoServicer(),server) # 参数一个为函数处理的实例对象,第二个为服务器对象
    # 3) 为服务器设置地址
    server.add_insecure_port('127.0.0.1:8888')
    # 4) 开启服务
    print('服务器已经开启')
    server.start()
    # 5) 关闭服务 --->>> 如下编写实现ctrl + c
    try:
        time.sleep(1000)
    except KeyboardInterrupt:
        server.stop()
if __name__ == '__main__':
    serve()

client.py

import grpc
import mydemo_pb2_grpc
import mydemo_pb2
def invoke_calculate(stub):
    # 设置参数
    work = mydemo_pb2.Work()
    work.num1 = 100
    work.num2 = 20
    work.op = mydemo_pb2.Work.ADD
    result = stub.Calculate(work)
    print('100 + 20 = {}'.format(result.val))
    work.op = mydemo_pb2.Work.SUBTRACT
    result = stub.Calculate(work)
    print('100 - 20 = {}'.format(result.val))
    work.op = mydemo_pb2.Work.MULTIPLY
    result = stub.Calculate(work)
    print('100 * 20 = {}'.format(result.val))
    work.op = mydemo_pb2.Work.DIVIDE
    result = stub.Calculate(work)
    print('100 // 20 = {}'.format(result.val))
    # 异常的情况
    work.num2 = 0
    try:
        result = stub.Calculate(work)
        print('100 // 20 = {}'.format(result.val))
    except grpc.RpcError as e:
        print('{}:{}'.format(e.code(),e.details()))
def invoke_get_subjects(stub):
    city = mydemo_pb2.City(name = "beijing")
    subjects = stub.GetSubjects(city)
    for subject in subjects:
        print(subject.name)
def run():
    # 将channel放入到上下文管理器
    with grpc.insecure_channel('127.0.0.1:8888') as channel:
        stub = mydemo_pb2_grpc.DemoStub(channel)
        # invoke_calculate(stub)
        invoke_get_subjects(stub)
if __name__ == '__main__':
    run()

运行结果如下:

image.png


3) 客户端流式RPC的实现。

server.py

import mydemo_pb2_grpc
import mydemo_pb2
import grpc  # 当中定义了状态
from concurrent import futures  # 为了传递线程池对象
import time
# 第一部分,实现被调用的方法的具体代码
class DemoServicer(mydemo_pb2_grpc.DemoServicer):
    def __init__(self):
        self.city_subject_db = {
            "beijing":['python', "c++", "go", "测试","运维", "java", "php"],
            "shanghai":['王者荣耀', "游戏开发", "go", "测试","运维", "java", "php"],
            "wuhan": ['python', "c++", "go", "测试"],
        } # 假定这个是从数据库查询的数据
    def Calculate(self, request, context):
        """
        :param request: 保存了请求的消息数据----》》》此处为Work类型
        :param context:  context ----》》》 本次响应的状态码和相关描述
        :return:
        """
        if request.op == mydemo_pb2.Work.ADD:
            result = request.num1 + request.num2
            return mydemo_pb2.Result(val = result)
        elif request.op == mydemo_pb2.Work.SUBTRACT:
            result = request.num1 - request.num2
            return mydemo_pb2.Result(val = result)
        elif request.op == mydemo_pb2.Work.MULTIPLY:
            result = request.num1 * request.num2
            return mydemo_pb2.Result(val = result)
        elif request.op == mydemo_pb2.Work.DIVIDE:
            if request.op == 0:
                # 通过设置响应状态码和描述字符串来达到抛出异常的目的
                context.setcode(grpc.StatusCode.INVALID_ARGUMENT)
                context.set_details('cannot divide by 0!')
                return mydemo_pb2.Result()
            result = request.num1 // request.num2
            return mydemo_pb2.Result(val = result)
        else:
            # 通过设置响应状态码和描述字符串来达到抛出异常的目的
            context.setcode(grpc.StatusCode.INVALID_ARGUMENT)
            context.set_details('invalid operation!')
            return mydemo_pb2.Result()
    def GetSubjects(self, request, context):
        city = request.name
        subjects = self.city_subject_db.get(city)
        for subject in subjects:
            yield mydemo_pb2.Subject(name = subject) # 注意:yield实现流式
    def Accumulate(self, request_iterator, context):
        # 备注:此处参数为迭代器
        sum = 0
        for request in request_iterator:
            sum += request.val
        return mydemo_pb2.Sum(val= sum)
# 第二步骤,开启服务器,对外提供rpc调用
def serve():
    # 1) 创建服务器对象----->>> 已经被封装为多线程的服务器, 所以需要传递一个线程池对象
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    # 2)注册实现的服务方法到服务器对象中--->>>生成的文件中,已经提供了
    mydemo_pb2_grpc.add_DemoServicer_to_server(DemoServicer(),server) # 参数一个为函数处理的实例对象,第二个为服务器对象
    # 3) 为服务器设置地址
    server.add_insecure_port('127.0.0.1:8888')
    # 4) 开启服务
    print('服务器已经开启')
    server.start()
    # 5) 关闭服务 --->>> 如下编写实现ctrl + c
    try:
        time.sleep(1000)
    except KeyboardInterrupt:
        server.stop()
if __name__ == '__main__':
    serve()


client.py

import grpc
import mydemo_pb2_grpc
import mydemo_pb2
import random
def invoke_calculate(stub):
    # 设置参数
    work = mydemo_pb2.Work()
    work.num1 = 100
    work.num2 = 20
    work.op = mydemo_pb2.Work.ADD
    result = stub.Calculate(work)
    print('100 + 20 = {}'.format(result.val))
    work.op = mydemo_pb2.Work.SUBTRACT
    result = stub.Calculate(work)
    print('100 - 20 = {}'.format(result.val))
    work.op = mydemo_pb2.Work.MULTIPLY
    result = stub.Calculate(work)
    print('100 * 20 = {}'.format(result.val))
    work.op = mydemo_pb2.Work.DIVIDE
    result = stub.Calculate(work)
    print('100 // 20 = {}'.format(result.val))
    # 异常的情况
    work.num2 = 0
    try:
        result = stub.Calculate(work)
        print('100 // 20 = {}'.format(result.val))
    except grpc.RpcError as e:
        print('{}:{}'.format(e.code(),e.details()))
def invoke_get_subjects(stub):
    city = mydemo_pb2.City(name = "beijing")
    subjects = stub.GetSubjects(city)
    for subject in subjects:
        print(subject.name)
def generate_delta():
    for _ in range(10):
        delta = random.randint(1,100)
        print(delta)
        yield mydemo_pb2.Delta(val=delta)
def invoke_accumulate(stub):
    delta_iterator = generate_delta()
    sum = stub.Accumulate(delta_iterator)
    print('sum= {}'.format(sum.val))
def run():
    # 将channel放入到上下文管理器
    with grpc.insecure_channel('127.0.0.1:8888') as channel:
        stub = mydemo_pb2_grpc.DemoStub(channel)
        # invoke_calculate(stub)
        # invoke_get_subjects(stub)
        invoke_accumulate(stub)
if __name__ == '__main__':
    run()

运行结果如下所示:

image.png

4) 双向流式RPC的实现。

server.py

import mydemo_pb2_grpc
import mydemo_pb2
import grpc  # 当中定义了状态
from concurrent import futures  # 为了传递线程池对象
import time
# 第一部分,实现被调用的方法的具体代码
class DemoServicer(mydemo_pb2_grpc.DemoServicer):
    def __init__(self):
        self.city_subject_db = {
            "beijing":['python', "c++", "go", "测试","运维", "java", "php"],
            "shanghai":['王者荣耀', "游戏开发", "go", "测试","运维", "java", "php"],
            "wuhan": ['python', "c++", "go", "测试"],
        } # 假定这个是从数据库查询的数据
        self.answers = list(range(10)) # 双向流式的数据准备
    def Calculate(self, request, context):
        """
        :param request: 保存了请求的消息数据----》》》此处为Work类型
        :param context:  context ----》》》 本次响应的状态码和相关描述
        :return:
        """
        if request.op == mydemo_pb2.Work.ADD:
            result = request.num1 + request.num2
            return mydemo_pb2.Result(val = result)
        elif request.op == mydemo_pb2.Work.SUBTRACT:
            result = request.num1 - request.num2
            return mydemo_pb2.Result(val = result)
        elif request.op == mydemo_pb2.Work.MULTIPLY:
            result = request.num1 * request.num2
            return mydemo_pb2.Result(val = result)
        elif request.op == mydemo_pb2.Work.DIVIDE:
            if request.op == 0:
                # 通过设置响应状态码和描述字符串来达到抛出异常的目的
                context.setcode(grpc.StatusCode.INVALID_ARGUMENT)
                context.set_details('cannot divide by 0!')
                return mydemo_pb2.Result()
            result = request.num1 // request.num2
            return mydemo_pb2.Result(val = result)
        else:
            # 通过设置响应状态码和描述字符串来达到抛出异常的目的
            context.setcode(grpc.StatusCode.INVALID_ARGUMENT)
            context.set_details('invalid operation!')
            return mydemo_pb2.Result()
    def GetSubjects(self, request, context):
        city = request.name
        subjects = self.city_subject_db.get(city)
        for subject in subjects:
            yield mydemo_pb2.Subject(name = subject) # 注意:yield实现流式
    def Accumulate(self, request_iterator, context):
        # 备注:此处参数为迭代器
        sum = 0
        for request in request_iterator:
            sum += request.val
        return mydemo_pb2.Sum(val= sum)
    def GuessNumber(self, request_iterator, context):
        for request in request_iterator:
            if request.val in self.answers:
                # 如果客户端猜的数据,在服务器中
                yield mydemo_pb2.Answer(val = request.val, desc = 'bingp')
# 第二步骤,开启服务器,对外提供rpc调用
def serve():
    # 1) 创建服务器对象----->>> 已经被封装为多线程的服务器, 所以需要传递一个线程池对象
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    # 2)注册实现的服务方法到服务器对象中--->>>生成的文件中,已经提供了
    mydemo_pb2_grpc.add_DemoServicer_to_server(DemoServicer(),server) # 参数一个为函数处理的实例对象,第二个为服务器对象
    # 3) 为服务器设置地址
    server.add_insecure_port('127.0.0.1:8888')
    # 4) 开启服务
    print('服务器已经开启')
    server.start()
    # 5) 关闭服务 --->>> 如下编写实现ctrl + c
    try:
        time.sleep(1000)
    except KeyboardInterrupt:
        server.stop()
if __name__ == '__main__':
    serve()


client.py

import grpc
import mydemo_pb2_grpc
import mydemo_pb2
import random
def invoke_calculate(stub):
    # 设置参数
    work = mydemo_pb2.Work()
    work.num1 = 100
    work.num2 = 20
    work.op = mydemo_pb2.Work.ADD
    result = stub.Calculate(work)
    print('100 + 20 = {}'.format(result.val))
    work.op = mydemo_pb2.Work.SUBTRACT
    result = stub.Calculate(work)
    print('100 - 20 = {}'.format(result.val))
    work.op = mydemo_pb2.Work.MULTIPLY
    result = stub.Calculate(work)
    print('100 * 20 = {}'.format(result.val))
    work.op = mydemo_pb2.Work.DIVIDE
    result = stub.Calculate(work)
    print('100 // 20 = {}'.format(result.val))
    # 异常的情况
    work.num2 = 0
    try:
        result = stub.Calculate(work)
        print('100 // 20 = {}'.format(result.val))
    except grpc.RpcError as e:
        print('{}:{}'.format(e.code(),e.details()))
def invoke_get_subjects(stub):
    city = mydemo_pb2.City(name = "beijing")
    subjects = stub.GetSubjects(city)
    for subject in subjects:
        print(subject.name)
def generate_delta():
    for _ in range(10):
        delta = random.randint(1,100)
        print(delta)
        yield mydemo_pb2.Delta(val=delta)
def invoke_accumulate(stub):
    delta_iterator = generate_delta()
    sum = stub.Accumulate(delta_iterator)
    print('sum= {}'.format(sum.val))
def generate_number():
    for _ in range(20):
        number = random.randint(1, 20)
        print(number)
        yield mydemo_pb2.Number(val=number)
def invoke_guess_number(stub):
    number_iterator = generate_number()
    answers = stub.GuessNumber(number_iterator)
    for answer in answers:
        print('{}:{}'.format(answer.desc, answer.val))
def run():
    # 将channel放入到上下文管理器
    with grpc.insecure_channel('127.0.0.1:8888') as channel:
        stub = mydemo_pb2_grpc.DemoStub(channel)
        # invoke_calculate(stub)
        # invoke_get_subjects(stub)
        # invoke_accumulate(stub)
        invoke_guess_number(stub)
if __name__ == '__main__':
    run()



运行结果如下:


image.png


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章