一,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()
运行结果如下:
转载于:https://blog.51cto.com/13914991/2309916