让C++也支持RMI
副标题#e#
由于没有雷同java的“反射”机制,尺度C++下实现RMI好像有些坚苦。为C++措施员所熟悉的Boost库固然有RCF实现了雷同RMI的成果,但RCF自己需依赖于Boost::serlization支持,而serlization需要编译之后方可利用,且有诸多限制。
本文试图通过C++特有的代码复用机制模仿实现具有雷同RMI成果的类库,固然不能完全实现java的RMI成果,但较之以往的C/S编程模式有了很大更改,且文中涉及许多C++代码复用技能譬喻模板,纯虚函数,要领工具等,但愿对C++初学者有必然的辅佐。
作者才疏学浅,如有不妥之处还请读者指正。
要害字
RMI,反射,长途要了解见,虚函数,函数工具,默认模板参数,宏替换。
由一个例子说起
以下实现一个简朴的客户端与处事器通讯的例子。例子回收传统的C/S模式,内容很简朴,客户端通过挪用处事器端的要领向处事器发送数据并吸收返回值。
为利便起见,下面分客户端与处事器别离先容实现。
处事器端
步调1
建设一个类“Calculate”。
class Calculate{
public:
int sum(int a,int b)
{
printf("int范例的sum要领被挪用\r\n");
t1=a;
t2=b;
return (int)(a+b);
}
double sum(double a,double b)
{
printf("double范例的sum要领被挪用\r\n");
t1=a;
t2=b;
return (a+b);
}
int GetInput()
{
int a;
printf("请输入一个整数以便传输至客户端:\r\n");
scanf("%d",&a);
return a;
}
Student GetStudent(Teacher tt)
{//Student ,Teacher 均为自界说范例
student ts;
ts.age=10;
printf("GetStuden要领被挪用\r\n teacher 的名称以及年数为: %s %d\r\n",tt.name,tt.age);
strcpy(ts.name,"StudentJim");
ts.sex=1;
return ts;
}
int t1;
int t2;
};
#p#副标题#e#
步调2
挪用MYRMI宏实现一个处事器端模板“RMIServer”。
MY_RMI_SERVER_CLASS_DECLARE(RMIServer)
MY_RMI_ SERVER _FUNCTION_ADD_P2(int,sum,int,int)
MY_RMI_ SERVER _FUNCTION_ADD_P2(double,sum,double,double)
MY_RMI_ SERVER _FUNCTION_ADD_P0(int,GetInput)
MY_RMI_ SERVER _FUNCTION_ADD_P1(Student,GetStudent,Teacher)
MY_RMI_ SERVER _CLASS_END()
函数名称以及返回值,参数列表均与Calculate类中所声明一致。
步调3
以“Calculate”为模板参数实现一个处事器端实例。
RMIServer < Calculate > ServerCalculate;
步调4
实现一个Calculate类实例 calcluateObject。
Calculate calcluateObject;
步调5
将calcluateObject工具添加进ServerCalculate中。
ServerCalculate.AddLocalObject(&calcluateObject)
步调6
ServerCalculate在指定端口监听客户端请求,启动处事。
ServerCalculate.Listen(663)
至此处事器端陈设完毕,当有客户端请求达到后,处事器便会自动启动新线程处理惩罚请求,同时不影响处事器端其它正常事情,如想遏制处事,直接挪用ServerCalculate.Stop()函数即可。
客户端
步调1
建设一个与处事器端沟通的类“Calculate”;
代码略
步调2
客户端挪用MYRMI宏实现一个客户端类模板“RMIClient”。MY_RMI_CLIENT_CLASS_DECLARE(RMIClient)
MY_RMI_CLIENT_FUNCTION_ADD_P2(int,sum,int,int)
MY_RMI_CLIENT_FUNCTION_ADD_P2(double,sum,double,double)
MY_RMI_CLIENT_FUNCTION_ADD_P0(int,GetInput)
MY_RMI_CLIENT_FUNCTION_ADD_P1(Student,GetStudent,Teacher)
MY_RMI_CLIENT_CLASS_END()
个中,RMIClient为模板名称,sum ,GetInput,GetStudent均为此模板所具有之要领,且均与Calculate中界说之要领沟通(包罗返回值,函数名,参数表)
步调3
客户端以“Calculate”为模板参数,声明一个实例“ClientCalculate”,并挪用Connect要领链接随处事器。
RMIClient<Calculate> ClientCalculate;
步调4
ClientCalculate.Connect("127.0.0.1",663);
直接挪用ClientCalculate成员要领,即可实现客户端于处事器通讯。
int nResult = ClientCalculate.sum(2,33);
以上客户端所挪用之要领均自处事器端执行,功效返回给客户端。
double dResult = ClientCalculate.sum(2.2,33.21);
nResult = ClientCalculate.GetInput();
Teacher t1;
Student s1;
s1=ClientCalculate.GetStudent(t1);
主要特点
由以上进程可见,回收MYRMI宏实现客户端与处事器之间的通讯进程有如下特点:
1. 实现简朴。客户端与处事器传输差异范例的数据无需用大量switch-case语句判定
#p#分页标题#e#
2. 可扩展性强。动态添加要领较量容易。只需在Calculate类中添加相应界说,并回收MYMRI宏向客户端以及处事器端别离添加所需之要领,即可由客户端工具ClientCalculate直接挪用。
3. 通讯进程可控。假如需要,仅需在处事器端挪用AddLocalObject()绑定至其它同范例处事器工具即可。Boost中的RCF库尚不具备此成果!
4. 多线程处理惩罚。处事器端回收多线程处理惩罚,不影响处事器的其它操纵。
5. 线程安详的。多个客户端同时毗连时,措施自动完成互斥与同步事情。
6. 回收tcp链接。安详靠得住。
7. 回收尺度C++语法实现。移植性,跨平台性强。
8. 源码宣布。只需向措施中添加相应的源文件即可,无需特另外动态链接库支持。
系统实现
本系统回收大量宏替换技能作为其实现方法,其间涉及模板,纯虚函数,要领工具,连系范例等多种C++代码复用技能,为清晰起见,首先先容系统中涉及的几种主要数据布局。
主要数据布局
class RMIClientBase:客户端数据传输基类。
认真客户端与处事器之间的数据通讯,打点和维护同处事器的毗连。详细成果函数如下:
毗连处事器:bool Connect(char* tRomateIP,int tRomatePort);
封锁毗连:void StopConnect();
判定链接有效性:bool IsAvailable();
向处事器通报数据吸收返回内容:bool CallRemoteFunction(RemoteFunctionStub* tRFStub)
class RMIServerBase:处事器端数据传输基类。
吸收客户端所传入之参数。
将当地函数返回值返回到客户端。
认真多个客户端毗连打点(启动/封锁链接,多线程间互斥同步等),详细成果函数如下:
开始监听:bool Listen(int tPort);
遏制监听:bool Stop();
监听网络端毗连请求:static DWORD WINAPI ListenThread(LPVOID pPara);
为不影响处事器正常事情,新启动一个线程认真监听客户端请求。对付每个新发生的客户 端毗连请求,再次启动一个线程挪用ProcessRequest处理惩罚新发生的客户端请求。
处理惩罚单个链接请求:static DWORD WINAPI ProcessRequest(LPVOID pPara);
对付每个客户端请求,处事器均启动一个单独线程处理惩罚其请求,线程间自动完成互斥以及同步事情。
挪用当地函数:virtual void CallLocalFunction(const char* pFuncID, void* pParaList,int tParaListLenght,SOCKET tSocket)=0;
此函数为“纯虚函数”由其基类实现之:按照pFuncID指定的当地函数,以pParaList内生存的参数列表为参数挪用当地函数。
class FunctionObject:函数工具模板。
将客户但挪用函数之参数列表以及函数ID封装为“函数存根(RemoteFunctionStub)”。
以此函数存根为参数挪用RMIServerBase的纯虚函数CallRemoteFunction,将函数ID以及参数列表传输到远端处事器。
接管处事器返回,并将功效强制转换为所挪用函数的返回值范例,返回挪用者。
struct RemoteFunctionStub:函数存根。
用于客户端与处事器间通报所挪用函数之信息。
封装了挪用函数ID,参数列表,返回值信息等内容。
class ParaListAnalyser:函数参数表理会器。
此为一模板,将参数表中的变量强制转换为指定范例。
处事器端挪用当地函数时需要操作此模板理会客户端所传入的参数列表。
各类数据布局以及彼此挪用干系如图所示
客户端类图
处事器端类图
主要宏界说
本系统回收大量的宏替换,概略可分为“类界说宏”以及“函数添加宏”,以下别离加以说明。
类界说宏
类界说宏完成客户端以及处事器端模板的界说成果。详细如下。
客户端模板界说:
#define MY_RMI_CLIENT_CLASS_DECLARE(client_class_name) \
template<typename classname> \
class client_class_name:public RMIClientBase \
{ \
private: \
说明:
本模板界说较量简朴,仅仅声明一个类模板,使之担任自“ RMIClientBase ”。
处事器端模板界说:
#define MY_RMI_SERVER_CLASS_DECLARE(server_class_name) \
template <typename TServer> \
class server_class_name :public RMIServerBase \
{ \
public: \
server_class_name() \
{ \
mServerClassName=typeid(TServer).name(); \
pMServer=NULL; \
}; \
bool AddLocalObject(TServer* pTServer) \
{ \
pMServer=pTServer;return true; \
}; \
bool IsRunning(); \
private: \
std::string mServerClassName; \
TServer* pMServer; \
void CallLocalFunction(const char* pFuncID, void* pParaList,int tParaListLenght,SOCKET tSocket)\
{ \
std::string FunTempID;
说明:
1.界说一模板类,并使之担任自“ RMIServerBase ”;
2.添加结构函数,并初始化“当地工具指针”(pMServer),以及“当地工具范例名称”(mServerClassName);
3.界说私有属性:pMServer,mServerClassName;
4.实现基类(RMIServerBase)之纯虚函数 CallLocalFunction;
接下来的函数添加宏中会逐渐完善CallLocalFunction 要领。
函数添加宏
函数添加宏末端的数字代表所要添加的函数的参数数目。最多答允添加具有9个函数参数的函数。
宏界说中各参数意义依次为:返回值,函数名,参数1,参数2,。。。
譬喻
MY_RMI_CLIENT_FUNCTION_ADD_P2(double,sum,double,double)
第一个“double” 暗示函数返回值为“double”范例。
函数名称为“sum”;
函数具有两个参数,且两者均为“double”范例。
假如所要添加的函数返回值为“void”,则挪用含有“_V_”的宏。
譬喻要添加具有一个参数,且没有返回值的函数:
MY_RMI_SERVER_FUNCTION_ADD_V_P1(MyFunction,int)
此宏添加一个返回值为“void”范例,只有一个“int”范例参数,名称为“MyFunction”的函数。
客户端与处事器端函数添加宏略有差异,以下分隔说明。
客户端函数添加宏
分为“有参数”与“无参数”两种。以下均以添加具有两个参数的函数为例来说明。
#p#分页标题#e#
有参数
#define MY_RMI_CLIENT_FUNCTION_ADD_P2(R,FunName,P1,P2)\
public:\
R FunName(P1 p1,P2 p2)\
{\
return FunctionObject<R,P1,P2>()(JOINSTRING4(R,FunName,P1,P2),this,p1,p2);\
}
1.直接在由“MY_RMI_CLIENT_CLASS_DECLARE”宏界说的类中添加函数名为“FunName”,返回值为“R”,参数别离为“P1”“P2”的函数声明。
2.以内联函数的形式完成函数界说。
3.以所传入的函数返回值范例“R”,以及两个参数范例“P1”“P2”为模板参数,实现一个姑且FunctionObject要领工具。
4.通过“JOINSTRING4”宏,将R和FunName以及P1,P2合成生成一个字符串作为所要添加的函数的“函数ID”。有关“函数ID”的具体内容见下文。
5.以生成的函数ID,工具自己的指针以及所要界说的函数两个参数变量作为参数挪用要领工具(FunctionObject)。
6.将要领工具的返回值直接返回给挪用者。
无参数
#define MY_RMI_CLIENT_FUNCTION_ADD_V_P2(FunName,P1,P2)\
public:\
void FunName(P1 p1,P2 p2)\
{\
FunctionObject<MYVOIDCLASS,P1,P2>()(JOINSTRING4(void,FunName,P1,P2),this,p1,p2);\
}
与有返回值雷同,仅仅不需要将函数工具的返回值返回罢了。
处事器端函数添加宏
差异于客户端直接向类模板中添加要领界说以及实现,处事器端的要领添加宏仅仅是完善处事器端基类RMIServerBase的纯虚函数:CallLocalFunction。
关于CallLocalFunction
CallLocalFunction函数由处事器端基类:RMIServerBase在收到客户端发来的函数挪用请求时挪用之。
其函数原型如下:
void CallLocalFunction(const char* pFuncID, void* pParaList,int tParaListLenght,SOCKET tSocket)
个中:
pFuncID 暗示客户端所要挪用的当地函数ID。
pParaList 内生存着所要挪用的函数参数列表。
tParaListLenght 暗示参数列表指针pParaList的长度。
tSocket 暗示客户端的毗连socket,以便通过此socket将函数的返回值发送至客户端。
关于函数ID
在客户端与处事器端的通讯进程中由“函数ID”独一确定客户端所要挪用的处事器端详细函数;
此为字符串,在利用函数添加宏时由JOINSTRINGi()宏按照所要添加的函数返回值,名称,参数范例列表直接拼接而成。
“JOINSTRINGi”宏末端的“i”暗示要拼接的单词数目。
譬喻一个函数的声明如下:
int sum(int a,int b);
则利用:JOINSTRING4(int,sum,int,int) 即可生成函数ID :“intsumintint”
与客户端雷同,处事器端的函数添加宏亦分为“有参数”于“无参数”两种。
以下亦以添加具有两个参数的宏为例子加以说明。
#p#分页标题#e#
有参数:
#define MY_RMI_SERVER_FUNCTION_ADD_P2(R,FunName,P1,P2) \
if(strcmp(JOINSTRING4(R,FunName,P1,P2),pFuncID)==0) \
{ \
R m##R##FunName##P1##P2=\
pMServer->FunName(ParaListAnalyser<R,P1,P2>(pParaList).GetP1(),\
ParaListAnalyser<R,P1,P2>(pParaList).GetP2());\
SendResponse(&m##R##FunName##P1##P2,\
sizeof(R),tSocket); \
return ; \
}
1. 操作“JOINSTRING4”宏生成函数ID。
2. 生成一个与返回值范例一致的姑且变量用于生存当地函数的返回值。
3. 较量宏生成的函数ID与CallLocalFunction要领中传入的pFuncID是否沟通,假如沟通则以生存的当地工具指针挪用指定函数。
4. 用函数返回值,以及参数范例列表作为模板参数实例化一个ParaListAnalyser姑且工具。
5. 挪用ParaListAnalyser工具要领理会参数表。
6. 挪用当处所法。
7. 将要函数返回值返回至客户端。
无参数:
#define MY_RMI_SERVER_FUNCTION_ADD_V_P2(FunName,P1,P2) \
if(strcmp(JOINSTRING4(void,FunName,P1,P2),pFuncID)==0) \
{ \
pMServer->FunName(ParaListAnalyser<MYVOIDCLASS,P1,P2>(pParaList).GetP1(),\
ParaListAnalyser<MYVOIDCLASS,P1,P2>(pParaList).GetP2());\
return ; \
}
无参数函数添加宏与有参数函数添加宏概略雷同,所差异者有以下几个方面。
1. 指定模板ParaListAnalyser的模板参数时,回收“MYVOIDCLASS”作为第一模板参数。
“MYVOIDCLASS”为一非凡数据范例,用于指定模板参数时,区别有返回值的环境,以便ParaListAnalyser做差异处理惩罚
2. 因无返回值,当地函数执行完毕后即直接返回,无需将功效返回至客户端,亦无须要生成一姑且变量用于生存当地函数返回值。
具体处理惩罚进程
下面以客户端以及处事器端别离加以说明
客户端
客户端回收主动请求方法与处事器通讯,当客户端有要领挪用时,即将函数所需之参数发送至处事器,并吸收返回值。
下面以客户端挪用sum要领“ ClientCalculate.sum(2.2,33.21)”为例先容处理惩罚进程。
在回收 MY_RMI_CLIENT_FUNCTION_ADD_P2(double,sum,double,double) 向RMIClient中添加sum要领后的功效代码如下
public:
double sum(double p1,double p2)
{
return FunctionObject<double ,double ,double >()(JOINSTRING4(double ,sum,double ,double ),this,p1,p2);
}
客户打量细处理惩罚进程如图所示
处事器端
回收处事器端类界说宏界说RMIServer 模板,并利用要领添加宏完善其CallLocalFunction要领。
CallLocalFunction要领经完善后内容如下(仅以添加的sum要领为例):
void CallLocalFunction(const char* pFuncID, void* pParaList,int tParaListLenght,SOCKET tSocket)
{
if(strcmp(JOINSTRING4(double,sum,double,double),pFuncID)==0)
{
Double mdoublesumdoubledouble = pMServer->sum(
ParaListAnalyser<double,double,double>(pParaList).GetP1(),
ParaListAnalyser<double,double,double>(pParaList).GetP2()
)
SendResponse(&mdoublesumdoubledouble,sizeof(double),tSocket);
}
...
...
...
}
处事器端首先以所要实现RMI的类为模板参数实例化一个工具ServerCalculate
接着挪用ServerCalculate.AddLocalObject(&calcluateObject)将所要实现长途要了解见的当地工具添加到ServerCalculate中,随后挪用ServerCalculate.Listen(663)实此刻当地663端口监听。至此处事器已经启动,当有客户端发来要领挪用请求后,处事器即可自动启动一单独线程处理惩罚请求并返回功效。
处事器打量细处理惩罚进程如图所示
不敷与改造
由于作者本领有限以实时间急遽,措施另有很多不尽如人意之处,详细表示在以下几方面。
1。安详性问题。对付客户端的毗连请求,处事器端未做授权查抄。任何与处事器端所绑定之类实例有沟通成员函数者均可挪用之。
2。不支持指针以及引用。
3。缺乏对客户端毗连的日志统计成果。诸如客户端毗连数目,请求时间,退出时间等处事器尚不具备记录统计此等信息之本领。
4。错误处理惩罚不完善。对付毗连进程中网络中呈现错误,客户端以及处事器端均未作检测,也就未能对错误做出实时有效的回响。
#p#分页标题#e#
针对以上问题,有乐趣的读者可以自行完善扩充,一来可以大大加强本措施的实用性。二来,文中涉及的许多C++代码复用技能亦不失为列位读者进修了解C++的难度时机。
跋文
本文模仿boost RCF利用方法,实现了一个雷同RMI成果的开拓包,间隔真正的应用另有很长的路要走,作者意在通过本文展示一下C++强大的代码复用技能,以期抛砖引玉,博方家一笑。
文中涉及之代码在windowsXP vc6.0 下编译通过,有意者可发送邮件至[email protected]索取。
关于作者:本文作者王树栋,北方家产大学在校研究生,平时热衷于计较机底层开拓,对Linux情有独钟。
本文配套源码