在gRPC中,客户端应用程序可以直接在其他计算机上的服务器应用程序上调用方法,就好像它是本地对象一样,从而使您更轻松地创建分布式应用程序和服务。与许多RPC系统一样,gRPC围绕定义服务的思想,指定可通过其参数和返回类型远程调用的方法。在服务器端,服务器实现此接口并运行gRPC服务器以处理客户端调用。在客户端,客户端具有一个存根(在某些语言中仅称为客户端),提供与服务器相同的方法。
默认情况下,gRPC使用协议缓冲区,这是谷歌成熟的开源机制,用于序列化结构化数据(尽管它也可以用于其他数据格式,如JSON)。使用协议缓冲区时,第一步是为要在proto文件中序列化的数据定义结构:这是一个扩展名为.proto的普通文本文件。协议缓冲区数据结构为消息,其中每个消息都是包含一系列名为字段的名称-值对的信息的小逻辑记录。这里有一个简单的例子:
syntax = "proto3";
service Math{
rpc add(Params) returns(Response){}
rpc reduce(Params) returns(Response){}
}
message Params{
int32 num1 = 1;
int32 num2 = 2;
}
message Response{
int32 result = 1;
}
grpc使用protobuf进行数据传输,protobuf是一种数据交换格式,由三部分组成:
- proto文件:使用proto语法的文本文件,用来定义数据格式。proto语法现在有proto2与proto3两个版本,建议使用proto3
- protoc:protobuf编译器将proto文件编译成不同的语言实现,这样不同语言中的数据就可以和protobuf格式的数据进行交互
- protobuf运行时:protobuf运行时所需的库,和protoc编译生成的代码进行交互
四种服务方法
- 一个简单的RPC,客户端使用存根将请求发送到服务器,然后等待响应返回,就像正常的函数调用一样
rpc get_feature(Params) returns(Response){}
- 服务器(响应)流式RPC,客户端在其中向服务器发送请求,并获取流以读取一系列消息。客户端从返回的流中读取,直到没有更多的消息为止。
rpc get_feature(Params) returns(stream Response){}
- 客户端(请求)流式RPC,客户端在其中编写一系列消息,然后再次使用提供的流将它们发送到服务器。客户端写完消息后,它将等待服务器读取消息并返回其响应
rpc get_feature(stream Params) returns(Response){}
- 双向流式RPC,双方都是用读写流发送一系列消息。这两个流独立运行,因此客户端和服务器可以按照自己喜欢的顺序进行读写。服务器在读写响应之前等待接收所有客户端消息,或者可以先读取消息后写入消息。每个流的消息顺序都会保留
rpc get_feature(stream Params) returns(stream Response){}
安装
安装grpc与grpc工具包
pip3 install grpcio
pip3 install grpcio-tools
举个栗子
有这么一个文件math.proto
,内容如下:
syntax = "proto3";
service Math{
rpc add(Params) returns(Response){}
rpc reduce(Params) returns(Response){}
}
message Params{
int32 num1 = 1;
int32 num2 = 2;
}
message Response{
int32 result = 1;
}
编译.proto文件
/**
-I 指定工作目录
--python_out 指定python文件目录
--grpc_python_out 指定grpc python文件目录
**/
python3 -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. ./math.proto
执行上面的命令进行编译之后,会在当前目录生成math_pb2.py
和math_pb2_grpc.py
文件。服务端代码如下:
import grpc
from concurrent import futures
import math_pb2
import math_pb2_grpc
class MathServer(math_pb2_grpc.MathServicer):
def add(self, request, context):
return math_pb2.Response(result=request.num1 + request.num2)
def reduce(self, request, content):
return math_pb2.Response(result=request.num1 - request.num2)
if __name__ == '__main__':
server = grpc.server(futures.ThreadPoolExecutor(max_workers=5))
math_pb2_grpc.add_MathServicer_to_server(MathServer(), server)
server.add_insecure_port("[::]:50001")
server.start()
print('server is starting...')
server.wait_for_termination()
客户端代码如下:
import grpc
import math_pb2
import math_pb2_grpc
channel = grpc.insecure_channel("localhost:50001")
stub = math_pb2_grpc.MathStub(channel)
def add(num1, num2):
response = stub.add(math_pb2.Params(num1=num1, num2=num2))
print(f'response: {response.result}')
def reduce(num1, num2):
response = stub.reduce(math_pb2.Params(num1=num1, num2=num2))
print(f'response: {response.result}')
if __name__ == '__main__':
add(1, 2)
导入其他proto文件
我们可以使用import
指令来导入其他的proto文件。假设有这么两个文件types.proto
和math.proto
。types.proto
文件内容如下:
syntax = "proto3";
package types;
message Params{
int32 num1 = 1;
int32 num2 = 2;
}
message Response{
int32 result = 1;
}
现在我们需要在math.proto
文件中调用这个types.proto
文件的内容。math.proto
文件内容如下:
syntax = "proto3";
// protos/types.proto表示在protos文件夹下面的types.proto文件
import "protos/types.proto";
service Math{
// types为types.proto文件中的package名称
rpc add(types.Params) returns(types.Response){}
rpc reduce(types.Params) returns(types.Response){}
}
更多grpc的使用:https://www.grpc.io/docs/