能夠比較使用google protobuf RPC實現echo service可見。述。算法
google protobuf僅僅負責消息的打包和解包。並不包括RPC的實現。但其包括了RPC的定義。若是有如下的RPC定義:網絡
service MyService {
rpc Echo(EchoReqMsg) returns(EchoRespMsg)
}
那麼要實現這個RPC需要最少作哪些事?總結起來需要完畢下面幾步:函數
client
RPCclient需要實現google::protobuf::RpcChannel
。主要實現RpcChannel::CallMethod
接口。client調用不論什麼一個RPC接口,終於都是調用到CallMethod
。這個函數的典型實現就是將RPC調用參數序列化,而後投遞給網絡模塊進行發送。post
void CallMethod(const ::google::protobuf::MethodDescriptor* method,
::google::protobuf::RpcController* controller,
const ::google::protobuf::Message* request,
::google::protobuf::Message* response,
::google::protobuf::Closure* done) {
...
DataBufferOutputStream outputStream(...) // 取決於你使用的網絡實現
request->SerializeToZeroCopyStream(&outputStream);
_connection->postData(outputStream.getData(), ...
...
}
服務端
服務端首先需要實現RPC接口。直接實現MyService
中定義的接口:ui
class MyServiceImpl : public MyService {
virtual void Echo(::google::protobuf::RpcController* controller,
const EchoReqMsg* request,
EchoRespMsg* response,
::google::protobuf::Closure* done) {
...
done->Run();
}
}
標示service&method
基於以上,可以看出服務端根本不知道client想要調用哪個RPC接口。google
從server接收到網絡消息。到調用到MyServiceImpl::Echo
還有很是大一段距離。code
解決方法就是在網絡消息中帶上RPC接口標識。server
這個標識可以直接帶上service name和method name,但這樣的實現致使網絡消息太大。還有一種實現是基於service name和method name生成一個哈希值,因爲接口不會太多,因此較easy找到基本不衝突的字符串哈希算法。對象
無論哪一種方法,server是確定需要創建RPC接口標識到protobuf service對象的映射的。blog
這裏提供第三種方法:基於option的方法。
protobuf中option機制相似於這樣一種機制:service&method被視爲一個對象,其有很是多屬性,屬性包括內置的,以及用戶擴展的。用戶擴展的就是option。每一個屬性有一個值。protobuf提供訪問service&method這些屬性的接口。
首先擴展service&method的屬性。下面定義這些屬性的key:
extend google.protobuf.ServiceOptions {
required uint32 global_service_id = 1000;
}
extend google.protobuf.MethodOptions {
required uint32 local_method_id = 1000;
}
應用層定義service&method時可以指定以上key的值:
service MyService
{
option (arpc.global_service_id) = 2302;
rpc Echo(EchoReqMsg) returns(EchoRespMsg)
{
option (arpc.local_method_id) = 1;
}
rpc Echo_2(EchoReqMsg) returns(EchoRespMsg)
{
option (arpc.local_method_id) = 2;
}
...
}
以上至關於在整個應用中。每個service都被賦予了惟一的id,單個service中的method也有惟一的id。
而後可以經過protobuf取出以上屬性值:
void CallMethod(const ::google::protobuf::MethodDescriptor* method,
::google::protobuf::RpcController* controller,
const ::google::protobuf::Message* request,
::google::protobuf::Message* response,
::google::protobuf::Closure* done) {
...
google::protobuf::ServiceDescriptor *service = method->service();
uint32_t serviceId = (uint32_t)(service->options().GetExtension(global_service_id));
uint32_t methodId = (uint32_t)(method->options().GetExtension(local_method_id));
...
}
考慮到serviceId
methodId
的範圍,可以直接打包到一個32位整數裏:
uint32_t ret = (serviceId << 16) | methodId;
而後就可以把這個值做爲網絡消息頭的一部分發送。
固然server端是需要創建這個標識值到service的映射的:
bool MyRPCServer::registerService(google::protobuf::Service *rpcService) {
const google::protobuf::ServiceDescriptor = rpcService->GetDescriptor();
int methodCnt = pSerDes->method_count();
for (int i = 0; i < methodCnt; i++) {
google::protobuf::MethodDescriptor *pMethodDes = pSerDes->method(i);
uint32_t rpcCode = PacketCodeBuilder()(pMethodDes); // 計算出映射值
_rpcCallMap[rpcCode] = make_pair(rpcService, pMethodDes); // 創建映射
}
return true;
}
服務端收到RPC調用後,取出這個標識值,而後再從_rpcCallMap
中取出相應的service和method,最後進行調用:
google::protobuf::Message* response = _pService->GetResponsePrototype(_pMethodDes).New();
// 用於迴應的closure
RPCServerClosure *pClosure = new (nothrow) RPCServerClosure(
_channelId, _pConnection, _pReqMsg, pResMsg, _messageCodec, _version);
RPCController *pController = pClosure->GetRpcController();
...
// protobuf 生成的CallMethod,會本身主動調用到Echo接口
_pService->CallMethod(_pMethodDes, pController, _pReqMsg, pResMsg, pClosure);