项目地址:http://code.google.com/p/febird
IDL的数据定义由几个宏定义实现:
RPC_DECLARE_MF(FunName, ArgList) | 声明函数,ArgList必须带括号 |
BEGIN_RPC_REGISTER_MF_EX(ThisClass,ClassName) BEGIN_RPC_REGISTER_MF(ThisClass) | 开始注册函数 EX后缀可以使用指定的名字作为类名称 |
RPC_REGISTER_MF(FunName) | 注册一个函数 |
END_RPC_REGISTER_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