使用C++模板实现不需要IDL的RPC【二】
严格讲,是不需要专用的 IDL 语言,传统 RPC 的 IDL 语言 相应的部分 在这里全部是 C++ 语言本身,也可以把它称作 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)。