一,gRPC簡介。
二,HTTP1.1的不足和HTTP2.0概述。
1) HTTP1.1
HTTP1.1存在如下問題:
2) HTTP2.0
socket中“流”的概念:
三,gRPC的接口類型。
四,gRPC的ProtocolBuffers.
類型 名稱 = 編號
五,案例接口定義與代碼生成。
pycharm中安裝插件後,如下設置--->>>>
在當前虛擬環境中,安裝protobuf編譯器和grpc工具
創建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
重新查看文件,就可以發現,自動生成如下的兩個文件。
六,服務器與客戶端的編寫
分別創建server.py文件和client.py文件
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()
運行結果如下:
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()
運行結果如下:
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()
運行結果如下所示:
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()
運行結果如下: