proto文件详解

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)。方法执行完成后,服务器将返回的结果进行编码(序列化),再次打包成二进制数据,并通过网络发送给客户端。

客户端接收到服务器返回的响应后,进行解码操作,将二进制数据还原为原始的响应数据结构。最后,客户端可以从响应中获取到所需的结果数据。

  • 16
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Protobuf是一种由Google开发的数据描述语言,它是一种轻便高效的结构化数据存储格式,用于结构化数据的序列化和反序列化。它可以用于通讯协议、数据存储等领域,具有语言无关、平台无关和可扩展的特点。Protobuf使用.proto文件来定义数据结构和消息格式,并通过protoc编译器将.proto文件编译成不同编程语言的代码。 在.proto文件中,可以使用以下语法进行定义和描述: - 语法规则:.proto文件由多个消息和服务组成,每个消息由字段组成,每个字段包括名称、类型和标签等。 - 消息定义:使用message关键字定义消息,可以嵌套定义其他消息。 - 字段定义:使用字段类型和字段名称定义消息的字段,可以指定字段的标签和默认值。 - 枚举定义:使用enum关键字定义枚举类型,可以指定枚举值和默认值。 - 服务定义:使用service关键字定义服务,服务由多个方法组成,每个方法包括名称、输入消息类型和输出消息类型等。 除了上述基本语法外,还有一些高级用法: - 字段规则:可以使用required、optional或repeated关键字定义字段的规则。 - 扩展字段:可以使用extensions关键字定义扩展字段,允许在不修改.proto文件的情况下添加新的字段。 - 自定义选项:可以使用option关键字定义自定义选项,用于指定一些特定的配置信息。 以上是Protobuf的基本语法,你可以根据需要在.proto文件中进行定义和描述。如果你有更具体的问题,请告诉我。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值