一,gRPC简介。

秒懂gRPC_案例



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

   

    1) HTTP1.1

秒懂gRPC_HTTP2.0_02

    HTTP1.1存在如下问题:

秒懂gRPC_案例_03

秒懂gRPC_gRPC_04

秒懂gRPC_protocalBuffers_05

        2) HTTP2.0

        秒懂gRPC_案例_06

    秒懂gRPC_案例_07


    socket中“流”的概念:

秒懂gRPC_案例_08

秒懂gRPC_gRPC_09


秒懂gRPC_案例_10

秒懂gRPC_protocalBuffers_11

秒懂gRPC_HTTP2.0_12

秒懂gRPC_案例_13

秒懂gRPC_gRPC_14

三,gRPC的接口类型。

秒懂gRPC_protocalBuffers_15

秒懂gRPC_protocalBuffers_16


秒懂gRPC_案例_17

秒懂gRPC_protocalBuffers_18

秒懂gRPC_HTTP2.0_19



四,gRPC的ProtocolBuffers.

秒懂gRPC_案例_20

秒懂gRPC_gRPC_21

秒懂gRPC_protocalBuffers_22

秒懂gRPC_案例_23

秒懂gRPC_HTTP2.0_24


秒懂gRPC_protocalBuffers_25


秒懂gRPC_protocalBuffers_26

秒懂gRPC_gRPC_27

秒懂gRPC_HTTP2.0_28


秒懂gRPC_protocalBuffers_29

    类型  名称  = 编号



秒懂gRPC_HTTP2.0_30

秒懂gRPC_protocalBuffers_31

秒懂gRPC_protocalBuffers_32

秒懂gRPC_gRPC_33


秒懂gRPC_HTTP2.0_34

秒懂gRPC_gRPC_35

秒懂gRPC_HTTP2.0_36

秒懂gRPC_HTTP2.0_37

秒懂gRPC_HTTP2.0_38

秒懂gRPC_protocalBuffers_39



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


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

秒懂gRPC_案例_40


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

秒懂gRPC_HTTP2.0_41


创建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

秒懂gRPC_HTTP2.0_42

秒懂gRPC_protocalBuffers_43

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

秒懂gRPC_protocalBuffers_44

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

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

秒懂gRPC_案例_45

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()

运行结果如下:

秒懂gRPC_HTTP2.0_46

秒懂gRPC_HTTP2.0_47


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()

运行结果如下:

秒懂gRPC_protocalBuffers_48


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()

运行结果如下所示:

秒懂gRPC_protocalBuffers_49

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()



运行结果如下:


秒懂gRPC_protocalBuffers_50