分布式网络通信框架-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=./