使用C++模板实现不需要IDL的RPC
IDL的数据定义由几个宏定义实现:
RPC_DECLARE_MF(FunName, ArgList)
声明函数,ArgList必须带括号
BEGIN_RPC_ADD_MF_EX(ThisClass,ClassName)
BEGIN_RPC_ADD_MF(ThisClass)
开始注册函数
EX后缀可以使用指定的名字作为类名称
RPC_ADD_MF(FunName)
注册一个函数
END_RPC_ADD_MF()
结束注册
用起来很类似于微软MFC中的消息映射声明。
实现上有过几次改动:
初始完成
l 每个ClientStub都有一个Stub的引用和一个真实的Stub实例
l Server端的Servant对象没有名字,用到哪个类,自动创建一个该类对象
l 参数序列化时,除非明确指定rpc_in/rpc_out/rpc_inout,否则都是双向传送
l 第一次调用远程函数时使用名字调用,以后都使用ID调用
l 只有同步调用(Client等待Server返回)
第二次改动
l 使用模板偏特化,自动推导参数的传送方式
l 实现了异步调用
目前状态
l Server端可以有GlobaleScope和SessionScope对象
l Global对象在Server整个运行期间都存在【除非显式删除】
l Session对象仅在一个会话中有效,会话结束就被删除
l Client可以创建、查询GlobaleScope和SessionScope的Servant
l Client/Server本身也使用这种rpc声明方式(rpc_interface.h)
l Server启动时可以注册一些驻留的GlobaleScope对象,这些对象由用户创建后调用rpc_server.add_servant(obj,name)注册
通信对象也被看做一个SessionScope对象,在客户端,这个对象由rpc_client表示,在服务端,由RpcSession表示。这个对象的ID是1,连接建立起来之后,两端就都把自己放入SessionScope对象池。这样,就可以方便地使用rpc_interface.h中定义的函数。这个过程相当于自己的bootstrap。但是,在Server上销毁RpcSession对象时,需要先把自己从SessionScope对象池中删除,然后再销毁SessionScope对象池中的所有对象。
client.h[.cpp]
rpc客户端实现
server.h[.cpp]
rpc服务端实现
rpc_basic.h
rpc基本类型定义
client_io.h
客户端io
server_io.h
服务端io
rpc_interface.h
客户端/服务端接口定义
arg_traits.h
推导rpc参数,函数原型推导为rpc io参数
pp_arglist_type.h
arglist
偏特化,用来配合boost.pp生成模板代码
pp_client_stub.h
客户端桩函数
pp_server_stub.h
服务端桩函数
函数参数的推导(以T为未修饰类型):
T
rpc_in<T>
输入参数,只被传入,不被传出
const T
rpc_in<T>
const T&
rpc_in<T>
const T*
rpc_in<T>
T&
rpc_inout<T>
输入/输出参数,既被传入,又被传出
T*
rpc_out<T>
输出参数,不被传入,只被传出
remote_object
的派生类另外处理
只传把对象ID作为输入参数传递
要做这个推导的原因是:C++在传递参数时,T会隐式转换成T&或者const T&,如果T是一个临时对象(std::string getstr() 的返回值),可能会转化成const T&,如果不是临时变量(如void fun(int x)中的x),会推导为T&,从而,在将它传给输入输出函数时会引发不确定性。而通过推导,可以区分T的所有不同修饰,从而在输入输出时做到正确识别。
实现上,能放在.cpp中实现的,都尽量放在.cpp中,不能在.cpp中实现的,才放在.h中。rpc_client和rpc_server都可以通过模板参数来修改Input/Output的方式,目前可用的是二进制,也许将来可以使用文本(如XML rpc,JsonRPC)。
项目地址:http://code.google.com/p/febird
相关资源:febird C++ 库(附带所有源码)