1 proto文件详解
1.1 message介绍
message:protobuf中定义一个消息类型是通过关键字message字段指定的。消息就算需要传输的数据格式的定义。message关键字类似于C++中的class,Java中的Class,go中的struct。
例如:
在消息中承载的数据分别对应于每一个字段。
其中每个字段都有一个名字和一种类型。
1.2 字段规则
required:消息体中必填字段,不设置会导致编解码异常。一般不填就认为是必填字段了。
optional:消息体中可选字段。生成的是对应的指针。
repeated:消息体中可重复字段,重复的值的顺序会被保留,在go中重复的会被定义为切片。
这里我们来定义一下
例子:
定义一个结构
message User{
string username=1;
int32 age=2;
optional string password=3; // 生成的是指针
repeated string address=4; // 生产的是切片
}
生成一下执行protoc --go_out=./ .\user.proto
生成下面的文件
type User struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"`
Age int32 `prtobuf:"varint,2,opt,name=age,proto3" json:"age,omitempty"`
Password *string `protobuf:"bytes,3,opt,name=password,proto3,oneof" json:"password,omiyempty"`
Address []string `protobuf:"bytes,4,rep,name=address,proto3" json:"address,omitempty"`
}
可以看到Address变成了一个切片。
1.3 字段映射
1.4 标识号
标识号:在消息体的定义中,每个字段都必须要有一个唯一的标识号,标识号是[0.2^29-1]范围内的一个整数。
message User{
string username=1; // 位置1
int32 age=2;
optional string password=3;
repeated string address=4; // 位置4
}
以Person为例,name=1,id=2,email=3,phones=4中的1- 4就是标识号。
1.5 定义服务
如果想要将消息类型用在rpc系统中,可以在.proto文件中定义一个rpc服务接口,protocolbuffer编译器会根据所选择的不同语言生成服务接口代码及存根。
service UserServiceRpc
{
rpc Login(LoginRequest) returns(LoginResponse);
}
上述代表表示,定义了一个RPC服务。
因此完整的.proto文件应该是:
syntax = "proto3"; // 声明了protobuf的版本
package fixbug; // 声明了代码所在的包(对于C++来说是namespace)
//定义下面的选项,表示生成service服务类和rpc方法描述,默认不生成
option cc_generic_services = true;
message ResultCode//封装一下失败类
{
int32 errcode = 1;//表示第1字段
bytes errmsg = 2;//表示第2字段
}
// 定义登录请求消息类型 name pwd
message LoginRequest
{
bytes name = 1;//表示第1字段
bytes pwd = 2;//表示第2字段
}
// 定义登录响应消息类型
message LoginResponse
{
ResultCode result = 1;//表示第1字段
bool success = 2;//表示第2字段
}
//在protobuf里面怎么定义描述rpc方法的类型 - service
service UserServiceRpc
{
rpc Login(LoginRequest) returns(LoginResponse);
}
1.6 UserServiceRpc类 和 UserServiceRpc_stub类
UserServiceRpc类是因为在.proto文件中定义了UserServiceRpc服务而生成的,UserServiceRpc类继承于google::protobuf::Service类
class UserServiceRpc : public ::PROTOBUF_NAMESPACE_ID::Service {
protected:
// This class should be treated as an abstract interface.
inline UserServiceRpc() {};
public:
virtual ~UserServiceRpc();
typedef UserServiceRpc_Stub Stub;
static const ::PROTOBUF_NAMESPACE_ID::ServiceDescriptor* descriptor();
virtual void Login(::PROTOBUF_NAMESPACE_ID::RpcController* controller,
const ::fixbug::LoginRequest* request,
::fixbug::LoginResponse* response,
::google::protobuf::Closure* done);
// implements Service ----------------------------------------------
const ::PROTOBUF_NAMESPACE_ID::ServiceDescriptor* GetDescriptor();
void CallMethod(const ::PROTOBUF_NAMESPACE_ID::MethodDescriptor* method,
::PROTOBUF_NAMESPACE_ID::RpcController* controller,
const ::PROTOBUF_NAMESPACE_ID::Message* request,
::PROTOBUF_NAMESPACE_ID::Message* response,
::google::protobuf::Closure* done);
const ::PROTOBUF_NAMESPACE_ID::Message& GetRequestPrototype(
const ::PROTOBUF_NAMESPACE_ID::MethodDescriptor* method) const;
const ::PROTOBUF_NAMESPACE_ID::Message& GetResponsePrototype(
const ::PROTOBUF_NAMESPACE_ID::MethodDescriptor* method) const;
private:
GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(UserServiceRpc);
};
Login()虚函数,让派生类重写。我们看到,Login虚函数的名字就是我们定义的rpc服务的方法的名字,其有4个固定的参数。(这块就是重写以后处理服务端的业务逻辑)
GetDescriptor()函数,获得一个指向ServiceDescriptor类型对象的指针,ServiceDescriptor类型的对象中描述了rpc方法的名字(属于哪一个类类型的方法)和调用函数方法的参数。
UserServiceRpc_stub类继承了UserServiceRpc类,并重写了UserServiceRpc类中的虚函数Login()。
class UserServiceRpc_Stub : public UserServiceRpc {
public:
UserServiceRpc_Stub(::PROTOBUF_NAMESPACE_ID::RpcChannel* channel);
UserServiceRpc_Stub(::PROTOBUF_NAMESPACE_ID::RpcChannel* channel,
::PROTOBUF_NAMESPACE_ID::Service::ChannelOwnership ownership);
~UserServiceRpc_Stub();
inline ::PROTOBUF_NAMESPACE_ID::RpcChannel* channel() { return channel_; }
// implements UserServiceRpc ------------------------------------------
void Login(::PROTOBUF_NAMESPACE_ID::RpcController* controller,
const ::fixbug::LoginRequest* request,
::fixbug::LoginResponse* response,
::google::protobuf::Closure* done);
private:
::PROTOBUF_NAMESPACE_ID::RpcChannel* channel_;
bool owns_channel_;
GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(UserServiceRpc_Stub);
};
- 其拥有一个RpcChannel类型的指针变量,在构造时需要传入这个值(没有默认构造函数!)。
- 其Login()是继承自基类的函数,其调用了channel的CallMethod函数,Login()的实现如下:
void UserServiceRpc_Stub::Login(::PROTOBUF_NAMESPACE_ID::RpcController* controller,
const ::fixbug::LoginRequest* request,
::fixbug::LoginResponse* response,
::google::protobuf::Closure* done) {
channel_->CallMethod(descriptor()->method(0),
controller, request, response, done);
}
1.7 个人理解
1 protobuf 实际上是一个通讯协议 两边通过使用同一份.proto文件可以定义 对象、函数、服务
2 protobuf通过针对不同语言基于.proto文件生成依赖代码 达到跨平台, 语言中立的效果
3 protobuf基础库分为三大层: service、method、message、 这三个是protobuf的基类
service.h
GetDescriptor() 获得service描述符 可以获得service相关的属性
callmethod() 调用service下的method
getrequestPrototype() 根据message描述符 得到请求的message
getresponseprototype() 根据message描述符 得到响应的message
例子
定义一个价格的请求体和相应体,其中price,good代表请求的message所包含的对象。
syntax = "proto3";
option cc_generic_services = true;
message makeOrderRequest {
int32 price = 1;
string goods = 2;
}
message makeOrderResponse {
int32 ret_code = 1;
string res_info = 2;
string order_id = 3;
}
service Order {
rpc makeOrder(makeOrderRequest) returns (makeOrderResponse);
}
使用时首先生成对应的.pb.cc和.pb.h,针对service我们必须重写虚函数makeOrder,这部分主要就是完成你所需要的业务逻辑,起始就是更具请求自动生成相应。
void makeOrder(google::protobuf::RpcController* controller,
const ::makeOrderRequest* request,
::makeOrderResponse* response,
::google::protobuf::Closure* done) {
if (request->price() < 10) {
response->set_ret_code(-1);
response->set_res_info("short balance");
return;
}
response->set_order_id("21111");
}
在一个典型的RPC调用过程中,客户端首先与服务器建立连接。然后,客户端将请求编码成特定的格式,通常是使用一种序列化协议(如Protobuf等),将请求数据打包成二进制数据。接着,客户端通过网络发送请求给服务器。
服务器收到请求后,会进行解码操作,将接收到的二进制数据还原为原始的请求数据结构。然后,服务器根据请求中指定的方法名(通常是通过反射机制获取方法名)调用相应的方法(callmethod)(callmethod() 调用service下的method)。方法执行完成后,服务器将返回的结果进行编码(序列化),再次打包成二进制数据,并通过网络发送给客户端。
客户端接收到服务器返回的响应后,进行解码操作,将二进制数据还原为原始的响应数据结构。最后,客户端可以从响应中获取到所需的结果数据。