一,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()
运行结果如下: