文章导读
本文主要介绍gRPC中重要的部分Protocol Buffers语法,预计需要花费15分钟左右的时间,阅读后你将收获以下知识,
- Protocol Buffers语法规范。
- 通过Protocol Buffers进行数据结构定义。
- 通过Protocol Buffers进行服务定义
- 多种数据类型的gRPC服务搭建。
这是一篇进阶版本的文章,读者应具备基本的gRPC基础,在此之前已有文章讲解Python3搭建gRPC(入门版),如不了解gRPC建议先阅读
同时提供以下进阶材料
- Python3搭建gRPC(入门版)
- 同步视频教程(后续会更新)
- 其他相关联文章,包括pytest进行gRPC测试,python3客户端测试java/golang grpc服务端(后续会更新)
如果大家觉得有收获,记得一键三连,后续继续为大家输出更多有价值的文章!!!
Protocal Buffers语法
Protocal Buffers是描述型语言,语法简单,本章将详细讲解
数据类型和各语言之间的关系
Protocal Buffers最终都是需要转换成各种语言的源码,需要了解在pb的数据类型和各语言之间的对应关系
.proto Type | Notes | C++ Type | Java/Kotlin Type[1] | Python Type[3] | Go Type | Ruby Type | C# Type | PHP Type | Dart Type |
---|---|---|---|---|---|---|---|---|---|
double | double | double | float | float64 | Float | double | float | double | |
float | float | float | float | float32 | Float | float | float | double | |
int32 | int32 | int | int | int32 | Fixnum or Bignum (as required) | int | integer | int | |
int64 | int64 | long | int/long[4] | int64 | Bignum | long | integer/string[6] | Int64 | |
uint32 | uint32 | int[2] | int/long[4] | uint32 | Fixnum or Bignum (as required) | uint | integer | int | |
uint64 | uint64 | long[2] | int/long[4] | uint64 | Bignum | ulong | integer/string[6] | Int64 | |
sint32 | int32 | int | int | int32 | Fixnum or Bignum (as required) | int | integer | int | |
sint64 | int64 | long | int/long[4] | int64 | Bignum | long | integer/string[6] | Int64 | |
fixed32 | uint32 | int[2] | int/long[4] | uint32 | Fixnum or Bignum (as required) | uint | integer | int | |
fixed64 | uint64 | long[2] | int/long[4] | uint64 | Bignum | ulong | integer/string[6] | Int64 | |
sfixed32 | int32 | int | int | int32 | Fixnum or Bignum (as required) | int | integer | int | |
sfixed64 | int64 | long | int/long[4] | int64 | Bignum | long | integer/string[6] | Int64 | |
bool | bool | boolean | bool | bool | TrueClass/FalseClass | bool | boolean | bool | |
string | string | String | str/unicode[5] | string | String (UTF-8) | string | string | String | |
bytes | string | ByteString | str (Python 2) bytes (Python 3) | []byte | String (ASCII-8BIT) | ByteString | string |
常见类型如何定义
pb包含有多种类型,包括int,float,string,bool,map等
pb没有list类型,该如何定义,在list类型章节介绍
基础类型
syntax = "proto3";
message IntRequest{
int32 id=1;
}
message FloatRequest{
float num=1;
}
message StringRequest{
string name=1;
}
message BoolRequest{
bool exist=1;
}
map类型
map类型需要定义内部的泛型,如下表示search为key和value都是string类型的map,在Python中叫字典
message MapRequest{
map<string, string> search=1;
}
list类型
list在pb中没有对应的数据类型,需要使用repeated关键词的意思是可重复0到N个相同的值,和列表的定义是一致的,列表中有0到N元素
如下定义表示UsersRequest数据结构中,usernames是一个string类型的列表
message UsersRequest{
repeated string usernames=1;
}
外部依赖的类型怎么处理
已有一个map_demo.proto
# map_demo.proto
syntax = "proto3";
message MapDemo{
map<string, string> keyValue=1;
}
map_use_demo.proto需要引用map_demo.proto中定义的类型
# map_use_demo.proto
syntax = "proto3";
import "map_demo.proto";
service MapService{
rpc MapHello(Request) returns (Response){}
}
message Request{
MapDemo mapDemo=1;
}
message Response{
string name=1;
}
需要在map_use_demo.proto中import "map_demo.proto"
,这样map_demo中定义的MapDemo数据类型就可以使用了
外部引用有2个注意点:
1、在第四章中讲到grpc_tools.protoc -I参数就是在这种情况下使用,-I需要指定为map_demo.proto所在的目录,如不指定,生成map_use_demo时会找不到map_demo.proto
2、map_demo.proto也需要编译成Python代码
具体demo实例请参考第6章
message定义
message定义一个数据结构,类似于Python的类
message User{
string name=1; # 从1开始依次往后
string password=2; # 2
}
message 类名{
类型 变量名=1; # 这里1是固定值,从1开始依次往后
类型 变量名=2; # 这里1是固定值,从1开始依次往后
}
service定义
定义服务相对简单,通过关键字service配合关键字rpc可以进行服务定义
returns定义接口返回的数据类型
service MapService{
rpc MapHello(Request) returns (Response){}
}
service 服务名{
rpc 接口名(请求类型) returns (返回类型){}
}
pb转成源码作用解释
常见gRPC实战
将通过2个实际案例讲解复杂的pb定义
实战一:多种数据类型的复杂接口
- 第一步proto定义,
# all_demo.proto
syntax = "proto3";
service UserService{
rpc GetUserByKeyWord(Request) returns(Response){}
}
message User{
string name=1;
int32 id=2;
float age=3;
bool isBoy=4;
}
message UserList{
repeated User user=1;
}
message Response{
UserList userList=1;
}
message Request{
map<string,string> keyWord=1;
bool fuzzy=2;
}
2.转成Python源码
python3 -m grpc_tools.protoc -I ./ --python_out=. --pyi_out=. --grpc_python_out=. all_demo.proto
3.开发服务端
import grpc
from concurrent import futures
from grpc_demo.pb.all_demo_pb2_grpc import UserServiceServicer, add_UserServiceServicer_to_server
from grpc_demo.pb.all_demo_pb2 import Request, Response, User, UserList
class UserService(UserServiceServicer):
def GetUserByKeyWord(self, request, context):
user1 = User(name="zhangsan", id=1, age=24.3, isBoy=False)
user2 = User(name="zhangsan1", id=1, age=24.3, isBoy=False)
user_list = UserList(user=[user1, user2])
response = Response(userList=user_list)
return response
def serve():
port = "50000"
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
add_UserServiceServicer_to_server(UserService(), server)
server.add_insecure_port('[::]:' + port)
server.start()
print("Server started, listening on " + port)
server.wait_for_termination()
if __name__ == '__main__':
serve()
4.开发客户端
import grpc
from grpc_demo.pb.all_demo_pb2_grpc import UserServiceStub
from grpc_demo.pb.all_demo_pb2 import Request
def run():
with grpc.insecure_channel("localhost:50000") as channel:
stub = UserServiceStub(channel)
key_word = {"name": "zhang"}
request = Request(keyWord=key_word, fuzzy=False)
resp = stub.GetUserByKeyWord(request)
print(resp)
if __name__ == '__main__':
run()
5.通过客户端调试
实战二:存在外部依赖的场景
1、定义第一个proto,map_demo.proto
这个proto定义了MapDemo的类
#map_demo.proto
syntax = "proto3";
message MapDemo{
map<string, string> keyValue=1;
}
2、定义第二个proto,这个proto会引用第一个proto中定义的类
这个proto定义了接口,以及Request类,Request类引用了第一个proto中的MapDemo
syntax = "proto3";
import "map_demo.proto";
service MapService{
rpc MapHello(Request) returns (Response){}
}
message Request{
MapDemo mapDemo=1;
}
message Response{
string name=1;
}
3、将map_demo.proto转换成python代码
这一步很重要,虽然map_demo是被use_map_demo引用,但也需要生成python代码,否则会报错
python3 -m grpc_tools.protoc -I ./ --python_out=. --pyi_out=. --grpc_python_out=. map_demo.proto
4、将use_map_demo.proto转换成python代码
python3 -m grpc_tools.protoc -I ./ --python_out=. --pyi_out=. --grpc_python_out=. map_use_demo.proto
注意:这行完成上述操作后会生成如下python代码
5、gRPC服务代码
from concurrent import futures
import grpc
from grpc_demo.pb.map_use_demo_pb2_grpc import MapServiceServicer, add_MapServiceServicer_to_server
from grpc_demo.pb.map_use_demo_pb2 import Response
class MapUseDemoServer(MapServiceServicer):
def MapHello(self, request, context):
print(request)
return Response(name="hello map")
def serve():
port = "40001"
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
add_MapServiceServicer_to_server(MapUseDemoServer(), server)
server.add_insecure_port('[::]:' + port)
server.start()
print("Server started, listening on " + port)
server.wait_for_termination()
if __name__ == '__main__':
serve()
6、客户端代码
import grpc
from grpc_demo.pb.map_use_demo_pb2_grpc import MapServiceStub
from grpc_demo.pb.map_use_demo_pb2 import Request
from grpc_demo.pb.map_demo_pb2 import MapDemo
def run():
with grpc.insecure_channel("localhost:40001") as channel:
stub = MapServiceStub(channel)
map_demo = MapDemo(keyValue={"hello": "a"})
response = stub.MapHello(Request(mapDemo=map_demo))
print(response.name)
if __name__ == '__main__':
run()
7、启动服务端,通过客户端调用查看结果
客户端打印hello,表示调用成功
服务端打印request信息
常见错误
外部依赖没有转换
错误信息:ModuleNotFoundError: No module named 'grpc_demo.pb.map_demo_pb2'
这个错误原因是use_map_demo.proto引用了map_demo.proto,但map_demo.proto没有转换成pyton代码导致引入失败
解决方案:将map_demo.proto转换成python代码
python3 -m grpc_tools.protoc -I ./ --python_out=. --pyi_out=. --grpc_python_out=. map_demo.proto
客户端入参类型错误
ValueError: Protocol message MapDemo has no "xxxx" field.
错误演示:Request的参数应该是mapDemo类型,不是一个字典
正确做法:定义map_demo的对象,入参给Request