led液晶屏参数查询网址:使用google protobuf RPC实现echo servic
来源:百度文库 编辑:偶看新闻 时间:2024/06/11 18:29:41
使用google protobuf RPC实现echo service
2011年1月23日 那谁 发表评论 阅读评论这篇文章将讲述如何使用google的protobuf库实现一个RPC service,就实现一个最简单的service吧:echo.
文章对应的代码都可以在eventrpc中找到,写下这篇文章时的svn revision是138.
1) 定义协议
首先需要为这个service定义proto文件, 如下:
01
package
echo;
02
03
message EchoRequest
04
{
05
required string message =
1
;
06
};
07
08
message EchoResponse
09
{
10
required string response =
1
;
11
};
12
13
service EchoService
14
{
15
rpc Echo(EchoRequest) returns (EchoResponse);
16
};
解释一下这个proto文件中做的事情,它定义了一个package: echo, 这个package中有service:EchoService,而这个service下只有一个服务:Echo, 它的请求由EchoRequest结构体定义,回复由EchoResponse定义.
package相当于是C++中namespace的概念,有些package中可能会提供相同名字的service,为了解决命名冲突,就引入了package这个概念.
2) 对应的C++文件
使用protobuf自带的编译proto文件编译器,可以生成对应的pb.h和pb.cc文件.具体细节可以参考protobuf关于这部分的参考文档.
所生成的C++文件,都会在namespace echo中,就是前面提到的package概念.对于service EchoService而言,会对应的生成两个类:EchoService类和EchoService_Stub类:
01
class
EchoService :
public
::google::protobuf::Service {
02
// ....
03
EchoService_Stub(::google::protobuf::RpcChannel* channel);
04
virtual
void
Echo(::google::protobuf::RpcController* controller,
05
const
::echo::EchoRequest* request,
06
::echo::EchoResponse* response,
07
::google::protobuf::Closure* done);
08
void
CallMethod(
const
::google::protobuf::MethodDescriptor* method,
09
::google::protobuf::RpcController* controller,
10
const
::google::protobuf::Message* request,
11
::google::protobuf::Message* response,
12
::google::protobuf::Closure* done);
13
};
14
15
class
EchoService_Stub :
public
EchoService {
16
//...
17
void
Echo(::google::protobuf::RpcController* controller,
18
const
::echo::EchoRequest* request,
19
::echo::EchoResponse* response,
20
::google::protobuf::Closure* done);
21
};
上面省略了一些细节,只把最关键的部分提取出来了.
这两部分如何使用,后面会继续讲解
3) 实现客户端
首先来看如何实现客户端.
客户端都通过上面提到的对应service的stub类来发送请求,以sample/echo_client.cpp中的代码来解释:
01
Dispatcher dispatcher;
02
RpcChannel *channel =
new
RpcChannel(
"127.0.0.1"
,
21118
, &dispatcher);
03
if
(!channel->Connect()) {
04
printf(
"connect to server failed, abort\n"
);
05
exit(-
1
);
06
}
07
echo::EchoService::Stub stub(channel);
08
echo::EchoRequest request;
09
echo::EchoResponse response;
10
request.set_message(
"hello"
);
11
stub.Echo(NULL, &request, &response,
12
gpb::NewCallback(::echo_done, &response, channel));
可以看到,stub类的构造函数需要一个::google::protobuf::RpcChannel指针,这个类需要我们来实现,后面继续说.然后就是根据协议填充请求字段,注册回调函数,这之后就可以调用stub类提供的Echo函数发送请求了.
4) 实现RpcChannel类
现在可以讲解RpcChannel类和stub类的关系了,看看在调用stub::Echo函数,也就是发送请求时发生了什么事情:
1
void
EchoService_Stub::Echo(::google::protobuf::RpcController* controller,
2
const
::echo::EchoRequest* request,
3
::echo::EchoResponse* response,
4
::google::protobuf::Closure* done) {
5
channel_->CallMethod(descriptor()->method(
0
),
6
controller, request, response, done);
7
}
可以看到,发送请求的背后,最后调用的其实是RpcChannel的CallMethod函数.所以,要实现RpcChannel类,最关键的就是要实现这个函数,在这个函数中完成发送请求的事务.具体可以看rpc_channel.cpp中的做法,不再阐述,因为这里面做的事情,和一般的网络客户端做的事情差不多.
5) 如何识别service
前面提到过,每个service的请求包和回复包都是protobuf中的message结构体,在这个例子中是EchoRequest和EchoResponse message.可是,它们仅仅是包体,也就是说,即使你发送了这些消息,在服务器端还需要一个包头来识别到底是哪个请求的包体.
于是在代码中,引入了一个类Meta,其中有两个关键的变量:包体长度和method id.
包体长度自不必说,就是紧跟着包头的包体数据的长度.
method id是用来标识哪一个service的,如果不用id数字,也可以使用字符串,每个service,都有一个full name的概念,以这里的例子而言,Echo服务的full name是echo::EchoService::Echo(再次的,又是C++中namespace的概念来表示”全路径”以避免命名冲突).但是,如果使用full name来区分,一来发送包头就会过大,而来查找service时是一个字符串比较操作的过程,耗时间.
所以引入了method id的概念,选择hash full name为一个id值,一般而言,一个服务器对外提供的service,撑死有几百个吧,而选用的id是整型数据,另外再选择足够好的hash算法,绝大多数情况下是不会出现冲突的.
以上就是Meta类做的事情,封装了包体和识别service的method id,一并作为包头和包体拼接发送给服务器端.
5) 实现服务器端
接收到客户端的请求之后,首先要做一些安全性的检查,比如method id对应的service是否有注册.
其次就是真正的处理过程了:
01
int
RpcMethodManager::HandleService(string *message,
02
Meta *meta, Callback *callback) {
03
RpcMethod *rpc_method = rpc_methods_[meta->method_id()];
04
const
gpb::MethodDescriptor *method = rpc_method->method_;
05
gpb::Message *request = rpc_method->request_->New();
06
gpb::Message *response = rpc_method->response_->New();
07
request->ParseFromString(*message);
08
HandleServiceEntry *entry =
new
HandleServiceEntry(method,
09
request,
10
response,
11
message,
12
meta,
13
callback);
14
gpb::Closure *done = gpb::NewCallback(
15
&HandleServiceDone, entry);
16
rpc_method->service_->CallMethod(method,
17
NULL,
18
request, response, done);
19
return
0
;
20
}
上面注册了一个名为HandleServiceDone的回调函数,当service的Echo处理完毕之后,自动就会调用这个回调函数
来看 EchoService::CallMethod的定义
01
void
EchoService::CallMethod(
const
::google::protobuf::MethodDescriptor* method,
02
::google::protobuf::RpcController* controller,
03
const
::google::protobuf::Message* request,
04
::google::protobuf::Message* response,
05
::google::protobuf::Closure* done) {
06
GOOGLE_DCHECK_EQ(method->service(), EchoService_descriptor_);
07
switch
(method->index()) {
08
case
0
:
09
Echo(controller,
10
::google::protobuf::down_cast<
const
::echo::echorequest*=
""
>(request),
11
::google::protobuf::down_cast< ::echo::EchoResponse*>(response),
12
done);
13
break
;
14
default
:
15
GOOGLE_LOG(FATAL) <<
"Bad method index; this should never happen."
;
16
break
;
17
}
18
}
19
const
>
可以看到, 这个Echo服务是需要注册的服务器端首先实现的,以echo_server.cpp中的代码为例,它是这样做的:
01
class
EchoServiceImpl :
public
echo::EchoService {
02
public
:
03
EchoServiceImpl() {
04
};
05
06
virtual
void
Echo(::google::protobuf::RpcController* controller,
07
const
::echo::EchoRequest* request,
08
::echo::EchoResponse* response,
09
::google::protobuf::Closure* done) {
10
printf (
"request: %s\n"
, request->message().c_str());
11
response->set_response(request->message());
12
if
(done) {
13
done->Run();
14
}
15
}
16
};
它做的事情就是把收到的请求打印出来,然后将请求消息作为回复消息传送回去.调用done->Run()函数,其实就是调用前面注册的回调函数HandleServiceDone函数,这时候表示服务器端已经准备好了给客户端响应的消息,后面就是网络传输层的事情了.
以上是使用google protobuf RPC实现一个service的全过程.protobuf官方并没有给出这样一个demo的例子,所以我在eventrpc项目中试图封装protobuf来做RPC service.
但是,当前的实现还不够完善,存在以下的问题:
1) 效率不高
2) 没有实现客户端可以选择异步或者同步方式来响应服务器端的消息
3) 安全性检查不够完善,目前仅适用method id来检查
4) 没有把dispatcher抽出来独立到一个线程中,只有这样才能实现2)
5) 没有为每个函数写测试用例.
....
N) 其他还没有想到的....等着您给建议
不过,就以上而言,如果想了解如何使用protobuf来实现RPC,已经足够说明原理了,可以对应着代码和官方文档看看每个类的含义.
要编译成功,需要protobuf库和phread库.之前曾经使用libevent,但是不喜欢这个东东,于是就自己做了,但是目前仅支持epoll而已,所以还只能在linux上面编译.
- egmkang 2011年1月24日22:52 | #1 回复 | 引用
为啥不用Thrift呢?完整的RPC,会帮你把服务器,客户端代码都gen好的.而且支持的数据类型比protobuf多.
gen出来的代码也清爽很多.[回复]
- 那谁 2011年1月24日23:05 | #2 回复 | 引用
@egmkang
因为目前还没有一个像样的开源项目使用protobuf来做RPC,想把这个功能做起来,而且是尝试着自己去做[回复]
- seahouse 2011年1月25日14:06 | #3 回复 | 引用
您好!我有个protobuf问题请教:对repeated描述符的变量相当于一个list,那么生成的.pb.h代码里却没有删除list里某个index的函数操作。请问该如何解决?
如:
// repeated .LPersonalMainCategory personalMainCategorylist = 1;
inline int personalmaincategorylist_size() const;
inline void clear_personalmaincategorylist();
static const int kPersonalMainCategorylistFieldNumber = 1;
inline const ::LPersonalMainCategory& personalmaincategorylist(int index) const;
inline ::LPersonalMainCategory* mutable_personalmaincategorylist(int index);
inline ::LPersonalMainCategory* add_personalmaincategorylist();
inline const ::google::protobuf::RepeatedPtrField&
personalmaincategorylist() const;
inline ::google::protobuf::RepeatedPtrField*
mutable_personalmaincategorylist();
里面没有删除某个LPersonalMainCategory元素的函数。感谢。
[回复]
- 那谁 2011年1月28日09:50 | #4 回复 | 引用
@seahouse
没有删除操作是正常的吧,protobuf生成的代码中任何字段好像都没有删除操作,因为它只是把数据序列化/反序列化,也就是只负责解释数据.[回复]
- oscar 2011年2月23日11:32 | #5 回复 | 引用
protobuf2.4已经默认关闭service的生成,官方推荐采用新的plugin机制做rpc.
个人protobuf相对于thrift的明显优点:
1. pb序列化可以支持向后兼容
2. java版本thrift client只支持短连接[回复]
- 那谁 2011年2月23日13:07 | #6 回复 | 引用
谢谢,回头我看看你说的这个plugin机制…
[回复]