首页 > 代码库 > RCF—用于C++的进程间通讯(1)
RCF—用于C++的进程间通讯(1)
基础
我们从一个标准的echo服务器和客户端的例子来开始,这样的例子可以在几乎所有的网络和IPC示例中见到。我们暴露(expose)然后调用一个函数,这个函数接受一个字符串,并且返回一个相同的字符串,使用RCF,服务器端的代码是这样的:
#include RCFIdl.hpp #include RCFRcfServer.hpp #include RCFTcpEndpoint.hpp RCF_BEGIN(I_Echo, I_Echo) RCF_METHOD_R1(std::string, echo, const std::string &) RCF_END(I_Echo) class Echo { public: std::string echo(const std::string &s) { return s; } }; int main() { Echo echo; RCF::RcfServer server(RCF::TcpEndpoint(50001)); server.bind<I_Echo>(echo); server.startInThisThread(); return 0; }
... 客户端代码如下:
#include <RCF/Idl.hpp> #include <RCF/TcpEndpoint.hpp> RCF_BEGIN(I_Echo, "I_Echo") RCF_METHOD_R1(std::string, echo, const std::string &) RCF_END(I_Echo) int main() { RcfClient<I_Echo> echoClient(RCF::TcpEndpoint("localhost", 50001)); std::string s = echoClient.echo(RCF::Twoway, "what‘s up"); return 0; }
I_Echo是用RCF_BEGIN/RCF_METHOD/RCF_END宏进行定义的一个接口。这些接口对应于CORBA里的IDL定义,但是在这里,接口定义是被放在C++的源代码里,所以并不需要另外的编译步骤。服务器端和客户端代码简单地包含这些接口,然后和其他的代码一起编译。
客户端桩(stub)中的参数RCF::Twoway用来告诉RCF采用一个two-way方式的客户端调用,这种方式下,客户端发送一个请求并且等待响应,如果在一定时间内没有收到响应(这个时间是可配的),将会抛出一个异常。另一个选项是RCF::Oneway,这种方式下,如果服务端没有发送响应,客户端调用桩会立即把控制返回给用户。
在客户端调用中,把这个双向实参(directional argument)作为第一个参数,可以在代码里清楚地知道远程调用是采用哪种方式。一般说来,远程调用都会被规划为看起来像本地调用一样(正统的RPC观点),但在我看来,透明更重要一些。然而,如果你愿意,通过不传递那个双向实参(directional argument)从而以RPC风格来进行一个远程调用(这时调用是以two-way方式进行的)。
客户端桩时没有进行任何同步操作,所以应该在同一个线程里进行访问。服务端尽管是支持多线程,但在上面的例子中,还是以一个线程来运行的。RcfServer::startInThisThread()劫持了调用线程,并把它变为一个工作线程。
可以通过调用RcfServer::start(
false)
然后重复调用RcfServer::cycle()
来达到相同的效果。在多线程版本中,也可以调用RcfServer::start()
,然后驱动服务器的现场将会被自动创建。多线程版本需要Boost.Threads
库,并且要定义RCF_USE_BOOST_THREADS
预处理符号。
我们也可以用UDP协议来重写上面的客户端和服务器端。这次我们让服务器端和客户端程序运行在一个进程的不同线程里。
#include <RCF/Idl.hpp> #include <RCF/RcfServer.hpp> #include <RCF/UdpEndpoint.hpp> #ifndef RCF_USE_BOOST_THREADS #error Need to build with RCF_USE_BOOST_THREADS #endif RCF_BEGIN(I_Echo, "I_Echo") RCF_METHOD_R1(std::string, echo, const std::string &) RCF_END(I_Echo) class Echo { public: std::string echo(const std::string &s) { return s; } }; int main() { Echo echo; RCF::RcfServer server(RCF::UdpEndpoint(50001)); server.bind<I_Echo>(echo); server.start(); RcfClient<I_Echo> echoClient(RCF::UdpEndpoint("127.0.0.1", 50001)); std::string s = echoClient.echo(RCF::Twoway, "what‘s up"); server.stop(); // would happen anyway as server object goes out of scope return 0; }
和TCP不同的是UDP是无状态的。数据包不能保证是发送时的顺序被接收到,也不能保证所有的发送的数据包都会被接收到。在像上面的例子的本地连接中,使用two-way方式一般来说是能够正常工作的,因为这些数据包并没有从变幻莫测的真实网络中传输。通常,这种情况应该用one-way方式。
接口
接口定义宏的功能和之前一个版本的RCF是完全一致的。RCF_BEGIN()
宏用给定的名字和运行时描述来开始一个接口的定义,RCF_END()
宏来结束接口定义。在两者中间,用RCF_METHOD_xx()
宏可以定义一共25个接口的成员方法。
RCF_METHOD_xx()
宏的后两个字母表示参数的个数和返回类型是否是一个void。比如说,RCF_METHOD_V3
用来定义一个三个参数返回值类型为void的方法。
使用这些宏定义了RcfClient
<type
>类,这里的type
是这个接口的名字。这个类直接用作客户端的客户端桩,间接地用作服务器端的服务桩。RCF的接口也可以定义在任意的名字空间内:
namespace A { namespace B { RCF_BEGIN(I_X, "I_X") RCF_METHOD_V0(void, func1) RCF_METHOD_R5(int, func2, int, int, int, int, int) RCF_METHOD_R0(std::auto_ptr<std::string>, func3) RCF_METHOD_V2(void, func4, const boost::shared_ptr<std::string> &, boost::shared_ptr<std::string> &) // .. RCF_END(I_X) } } int main() { A::B::RcfClient<A::B::I_X> client; // or A::B::I_X::RcfClient client; // ... }
服务器绑定
在服务器端,接口需要绑定到一个具体的实现上。通过RcfServer::bind()
方法来实现这个绑定。根据内存管理方式的不同,绑定有几种变化。下面的几种调用都可以实现一模一样的事情:把一个Echo
对象绑定到I_Echo
接口上。
{ // bind to an object... Echo echo; server.bind<I_Echo>(echo); // or to a std::auto_ptr<>... std::auto_ptr<Echo> echoAutoPtr(new Echo()); server.bind<I_Echo>(echoAutoPtr); // or to a boost::shared_ptr<>... boost::shared_ptr<Echo> echoPtr(new Echo()); server.bind<I_Echo>(echoPtr); // or to a boost::weak_ptr<>... boost::weak_ptr<Echo> echoWeakPtr(echoPtr); server.bind<I_Echo>(echoWeakPtr); }
默认情况下,客户端可以通过接口的名字来使用绑定。服务器端可以通过同一个接口暴露(expose)多个服务器端对象,但是在这种情况下,需要明确指定每个对象的名字:
{ RcfServer server(endpoint); // bind first object Echo echo1; server.bind<I_Echo>(echo1, "Echo1"); // bind second object Echo echo2; server.bind<I_Echo>(echo2, "Echo2"); server.start(); RcfClient<I_Echo> echoClient(endpoint); echoClient.getClientStub().setServerBindingName("Echo1"); std::cout << echoClient.echo("this was echoed by the echo1 object"); echoClient.getClientStub().setServerBindingName("Echo2"); std::cout << echoClient.echo("this was echoed by the echo2 object"); }