分布式网络通信框架-rpc (三)Protobuf的使用

总纲

本章主要来介绍protobuf的使用,如何编写.proto文件,如何使用通过.proto文件生成的.cc和.h文件,我们都要实现哪些方法。
Protobuf 3 document
官方给出的使用protobuf实现rpc的例子

编写.proto文件

syntax = "proto3";  // 声明了protobuf的版本

package fixbug;     // 声明了代码所在的包(对于C++来说是namespace)

// 定义下面的选项,表示生成service服务类和rpc方法描述,默认不生成
option cc_generic_services = true;

message ResultCode
{
    int32 errcode = 1;
    bytes errmsg = 2;
}

// 定义登录请求消息类型
message LoginRequest
{
    bytes name = 1;
    bytes pwd = 2;
}

// 定义登录响应消息类型
message LoginResponse
{
    ResultCode result = 1;
    bool success = 2;
}

message GetFriendListsRequest
{
    uint32 userid = 1;
}

message User
{
    bytes name = 1;
    uint32 age = 2;
    enum Sex
    {
        MAN = 0;
        WOMAN = 1;
    }
    Sex sex = 3;
}

message GetFriendListsResponse
{
    ResultCode result = 1;
    repeated User friend_list = 2;    // 定义了一个列表类型
}

// 在protobuf里面怎么定义描述rpc方法的类型 - service
service UserServiceRpc
{
    rpc Login(LoginRequest) returns(LoginResponse);
    rpc GetFriendLists(GetFriendListsRequest) returns(GetFriendListsResponse);
}

如上面的代码所示,大部分内容都是常规操作。message关键字定义消息类型,service关键字定义服务类型。定义后,生成的.cc和.h文件中会生成相应的方法来获取相应的值和修改相应的值。

message关键字

message关键字定义消息类型。
如:

/*
.proto中message LoginRequest定义了name和pwd,那么对应在cc文件中就有
request->name()和request->pwd()方法来获取相应的值
设置值时就会有set_errcode()等以set_变量名的方法来对相应的变量进行设置
*/
void Login(::google::protobuf::RpcController *controller,
               const ::fixbug::LoginRequest *request,
               ::fixbug::LoginResponse *response,
               ::google::protobuf::Closure *done)
    {
        // 框架给业务上报了请求参数LoginRequest,应用获取相应数据做本地业务
        std::string name = request->name();
        std::string pwd = request->pwd();

        bool login_result = Login(name, pwd); // 做本地业务

        // 把响应写入  包括错误码、错误消息、返回值
        fixbug::ResultCode *code = response->mutable_result();
        code->set_errcode(0);
        code->set_errmsg("");
        response->set_success(login_result);

        // 执行回调函数     执行响应对象数据的序列化和网络发送(都是由框架来完成的)
        done->Run();
    }

service关键字

service关键字定义服务类型。会在生成的文件中生成对应service名称的虚基类,该类中有与我们定义在service中的方法同名的虚函数,我们需要对其进行重写。如:在.proto文件中我们有如下定义:

service UserServiceRpc
{
    // 和本地的方法名字相不相同无所谓
    rpc Login(LoginRequest) returns(LoginResponse);
    rpc Register(RegisterRequest) returns(RegisterResponse);
}

如上所示,我们定义了两个rpc(rpc也是.proto文件的关键字)服务,我们拿Login举例子(Register方法是一样的),所需要的参数是上面所定义的LoginRequest,返回值类型是LoginResponse,方法名是Login。因此,在生成的.cc和.h文件中就会有与service同名的虚基类产生,如下所示:

class UserServiceRpc_Stub;

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);
  virtual void Register(::PROTOBUF_NAMESPACE_ID::RpcController* controller,
                       const ::fixbug::RegisterRequest* request,
                       ::fixbug::RegisterResponse* 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);
};

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);
  void Register(::PROTOBUF_NAMESPACE_ID::RpcController* controller,
                       const ::fixbug::RegisterRequest* request,
                       ::fixbug::RegisterResponse* response,
                       ::google::protobuf::Closure* done);
 private:
  ::PROTOBUF_NAMESPACE_ID::RpcChannel* channel_;
  bool owns_channel_;
  GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(UserServiceRpc_Stub);
};

我们按照Login方法举例子,会生成两个虚基类,UserServiceRpc和UserServiceRpc_Stub(即,[service定义的名称]和[service定义的名称]_Stub这两个名称的类),这两个类中都含有同名的Login方法和Reigister方法需要重写。
按照我的理解,UserServiceRpc和UserServiceRpc_Stub的关系就是所有需要被调用的服务方法都实现在UserServiceRpc(重写同名虚函数)中,然后对应的UserServiceRpc_Stub起到一个代理的作用,所有对方法的调用都通过stub代理类来调用,上面的代码中可以看到UserServiceRpc_Stub类的构造函数需要传入一个::PROTOBUF_NAMESPACE_ID::RpcChannel*类型的参数,所有的通过stub类(代理类)调用的服务方法都被归到了::PROTOBUF_NAMESPACE_ID::RpcChannel的CallMethod方法中了,请看下面的代码注释:

// Abstract interface for an RPC channel.  An RpcChannel represents a
// communication line to a Service which can be used to call that Service's
// methods.  The Service may be running on another machine.  Normally, you
// should not call an RpcChannel directly, but instead construct a stub Service
// wrapping it.  Example:
//   RpcChannel* channel = new MyRpcChannel("remotehost.example.com:1234");
//   MyService* service = new MyService::Stub(channel);
//   service->MyMethod(request, &response, callback);
class PROTOBUF_EXPORT RpcChannel {
 public:
  inline RpcChannel() {}
  virtual ~RpcChannel();

  // Call the given method of the remote service.  The signature of this
  // procedure looks the same as Service::CallMethod(), but the requirements
  // are less strict in one important way:  the request and response objects
  // need not be of any specific class as long as their descriptors are
  // method->input_type() and method->output_type().
  virtual void CallMethod(const MethodDescriptor* method,
                          RpcController* controller, const Message* request,
                          Message* response, Closure* done) = 0;

  private:
   GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(RpcChannel);
};

小结

  • UserServiceRpc_Stub类中实现的方法正是我们rpc的主要逻辑,包括打包参数、寻找服务所在服务器地址、网络发送、等待响应得到打包好的返回值、解包返回值。
  • UserServiceRpc类主要定义在服务端,发送过来的参数解包、服务调用、得到结果、再把参数打包这几步。

从.proto文件生成对应的.cc和.h代码

protoc test.proto --cpp_out=./

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注