秒懂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


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