IT点滴

我不去想是否能够成功 既然选择了远方 便只顾风雨兼程
  博客园  :: 首页  :: 联系 :: 订阅 订阅  :: 管理

RCF—用于C++的进程间通讯

Posted on 2009-07-07 14:44  Ady Lee  阅读(2950)  评论(0编辑  收藏  举报

导言
RCF(Remote Call Framework,远程调用框架)是一个C++的框架。这个框架用来为实现C++程序进程间调用提供一个简单和一致(consistent)的方法。这个框架基于强类型的C/S接口概念,类似于用户所熟悉的CORBA,DCOM这样的中间件。然而,因为RCF只应用于C++,所以可以充分利用C++特性,从而提供一个以相对简单和整洁的方式实现进程间调用。
这是RCF库的第二个版本,第一个版本也可以在CodeProject上找到。
中国有句老话,一例胜千言(我咋没听过?)。所以将以一系列的例子来组织这篇文章,这些例子会覆盖RCF的特性。
基础
我们从一个标准的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");
}

 

 

列集(Marshaling)
RCF决定哪个方向的参数被列集时,是遵循C++惯例的。特别地,一个接口的所有的参数都是入参(in parameter),所有的non-const引用参数都是in-out参数,所有的返回值都是出参(out parameter)。但是也没有强制规定一定要遵循这些in/out/inout惯例。

并不是C++里的所有东西都可以被安全地列集,这也限制了接口方法的参数类型。也就是说,指针和引用可以作为参数;指针的引用不允许作为参数;指针和引用不能作为返回值。

这也意味着如果一个接口的方法想要返回一个指针,比如一个多态指针,返回值就需要时一个像std::auto_ptr<> 或者 boost::shared_ptr<>的智能指针。或者其中一个参数可以是一个智能指针的non-const引用。

序列化
Echo那个例子只序列化了std::string对象,但它(RCF)在使用序列化的情况下几乎可以发送所有的C++类和结构。RCF拥有自己的序列化框架,名字就叫序列化框架(SF),同时它也支持Boost.Serialization框架,这个框架是Boost库的一部分。

一般说来,需要在将被序列化的类里面包含序列化代码。如果你在接口有一个std::vector<>参数,你需要包含<SF/vector.hpp>或者<boost/serialization/vector.hpp>(或者同时包含两者),对于其他的STL和Boost库也是一样。

在RCF接口中使用你自己的类,你需要一个自定义的序列化方法,在大多数情况下,这非常简单。
#include <boost/serialization/string.hpp>

#include <boost/serialization/map.hpp>
#include <boost/serialization/vector.hpp>

#include <SF/string.hpp>
#include <SF/map.hpp>
#include <SF/vector.hpp>

struct X
...{
    int myInteger;
    std::string myString;
    std::map<
        std::string,
        std::map<int,std::vector<int> > > myMap;
};

// this serialization function
// will work as is with both SF and B.S.

template<typename Archive>
void serialize(Archive &ar, X &x)
...{
    ar & x.myInteger & x.myString & x.myMap;
}

// if you need to distinguish between SF and B.S. serialization,
// specialize the SF serialization function:
//void serialize(SF::Archive &ar, X &x)
//{
//    ar & myInteger & myString & myMap;
//}
当处理指向多态对象指针和循环指针的时候,序列化会变成一个很复杂的事情。SF和Boost.Serialization都能处理这些情况,但是用的是不同的方法,所以为了适应两种序列化系统需要写不同的序列化代码。下面的例子使用了SF和Boost.Serialization来发送多态对象。

class Base
...{
    // some members here
    // ...
};

typedef boost::shared_ptr<Base> BasePtr;

class Derived1 : public Base
...{
    // some members here
    // ...
};

class Derived2 : public Base
...{
    // some members here
    // ...
};

template<typename Archive>
void serialize(Archive &ar, Base &base, const unsigned int)
...{
    // ...
}

template<typename Archive>
void serialize(Archive &ar, Derived1 &derived1, const unsigned int)
...{
    // valid for both SF and B.S.
    serializeParent<Base>(derived1);

    // ...
}

template<typename Archive>
void serialize(Archive &ar, Derived2 &derived2, const unsigned int)
...{
    // valid for both SF and B.S.
    serializeParent<Base>(derived1);

    // ...
}

// Boost type registration, needed on both server and client
BOOST_CLASS_EXPORT_GUID(Derived1, "Derived1")
BOOST_CLASS_EXPORT_GUID(Derived2, "Derived2")

RCF_BEGIN(I_PolymorphicArgTest, "")
    RCF_METHOD_R1(std::string, typeOf, BasePtr)
RCF_END(I_PolymorphicArgTest)

class PolymorphicArgTest
...{
public:
    std::string typeOf(BasePtr basePtr)
    ...{
        return typeid(*basePtr).name();
    }
};

...{
    // SF type registration, needed on both server and client
    SF::registerType<Derived1>("Derived1");
    SF::registerType<Derived2>("Derived2");

    RcfClient<I_PolymorphicArgTest> client(endpoint);

    // SF serialization (default)
    client.getClientStub().setSerializationProtocol(RCF::SfBinary);
    std::string typeBase = client.typeOf( BasePtr(new Base()) );
    std::string typeDerived1 = client.typeOf( BasePtr(new Derived1()) );
    std::string typeDerived2 = client.typeOf( BasePtr(new Derived2()) );

    // Boost serialization
    client.getClientStub().setSerializationProtocol(RCF::BsBinary);
    typeDerived2 = client.typeOf( BasePtr(new Derived2()) );
}
继承(Inheritance)
RCF接口现在支持多继承。不仅可以从别的接口继承,还可以继承自标准C++类。接口内的方法可以用它们的分派ID和它们所属的接口名来标识。这样的信息对于服务器端用来映射来自客户端的调用到正确的服务器端绑定来说已经足够了。下面的例子示范了接口继承:

RCF_BEGIN(I_A, "I_A")
    RCF_METHOD_V0(void, func1)
RCF_END(I_Base)

RCF_BEGIN(I_B, "I_B")
    RCF_METHOD_V0(void, func2)
RCF_END(I_Base)

// derives from I_A

RCF_BEGIN_INHERITED(I_C, "I_C", I_A)
    RCF_METHOD_V0(void, func3)
RCF_END(I_Base)

// derives from I_A and I_B

RCF_BEGIN_INHERITED_2(I_D, "I_D", I_A, I_B)
    RCF_METHOD_V0(void, func4)
RCF_END(I_Base)

class I_E
...{
public:
    virtual void func5() = 0;
};

// derives from abstract base class I_E

RCF_BEGIN_INHERITED(I_F, "I_F", I_E)
    RCF_METHOD_V0(void, func5)
RCF_END(I_Base)

...{
    RcfClient<I_C> clientC(endpoint);
    clientC.func3();
    clientC.func1();

    RcfClient<I_D> clientD(endpoint);
    clientD.func4();
    clientD.func2();
    clientD.func1();

    RcfClient<I_F> clientF(endpoint);
    I_E &e = clientF;
    e.func5();
}

 

 

过滤器(Filters)
RCF通过过滤器的概念来支持对消息的压缩和加密。过滤器需要同时应用于服务器端和客户端。也可以被应用于传输层,例如应用SSL过滤器到向TCP这样基于流的传输;或者应用于独立的消息载荷,例如压缩像UDP这样基于包传递的消息。前者称为传输过滤器,后者称为载荷过滤器。

传输过滤器
在一个服务器-客户的会话中安装一个传输过滤器是由客户端发起的。客户端查询服务器端是否支持给定的过滤器,如果服务器端支持,过滤器就会同时在两端安装,然后继续通讯。

询问服务器端和安装过滤器的过程是由客户端桩自动处理的。用户只要在远程调用之前调用ClientStub::setTransportFilters(),客户端桩会自动询问服务器端并且安装过滤器。

传输过滤器可以用two-way方式的,那样的话一个读操作或者写操作都会导致下行方向的多个读写请求。 一个典型的例子就是一个SSL加密过滤器在任何时刻都需要发起一个握手操作,这个握手操作又会包含多个下行的读写请求。

载荷过滤器
使用载荷过滤器不需要服务器端或者客户端任何特殊的步骤。如果客户端桩通过ClientStub::setPayloadFilters()函数调用设置了过滤器,消息的载荷将会用设置的过滤器来传输。载荷的前面会被加入足够的数据,以便让服务器端能够解码传输用的过滤器,从而让服务器端解码传输的载荷。如果服务器端不能解码这个过滤器,服务器端将返回一个异常到客户端。

载荷过滤器必须使用one-way方式,也就是说一个读操作只会导致在下行方向的一个或者多个读请求(译注:传输过滤器则会导致多个读写请求),写操作也是类型的。RCF本身已经提供了几个过滤器:基于Zlib的两个压缩过滤器,还有一个基于OpenSSL的SSL加密过滤器。这些过滤器也可以被连接起来使用,从而提高过滤器序列。

OpenSSL的加密过滤器只能用于传输过滤器,因为SSL的加密和解密过程需要一个two-way的会话,这个前面也有提到过。另一方面,有状态和无状态的压缩过滤器可以被用于传输和载荷过滤器。在像UDP这样非面向流的传输之上,只有用无状态的压缩过滤器才是有意义的。但是,在想TCP这样的传输之上,有状态好无状态压缩过滤器都是可以使用的。

一个例子:

...{
    // compression of payload

    RcfClient<I_X> client(endpoint);
    client.setPayloadFilters( RCF::FilterPtr(
        new RCF::ZlibStatelessCompressionFilter() ) );

    // encryption of transport

    std::string certFile = "client.pem";
    std::string certFilePwd = "client_password";
    client.setTransportFilters( RCF::FilterPtr(
    new RCF::OpenSslEncryptionFilter(
            RCF::SslClient, certFile, certFilePwd)) ) );

    // multiple chained transport filters

    //       - compression followed by encryption

    std::vector<RCF::FilterPtr> transportFilters;
    transportFilters.push_back( RCF::FilterPtr(
        new RCF::ZlibStatefulCompressionFilter()));
    transportFilters.push_back( RCF::FilterPtr(
        new RCF::OpenSslEncryptionFilter(
            RCF::SslClient, certFile, certFilePwd)) ) );
    client.setTransportFilters(transportFilters);

    // multiple chained payload filters - double compression

    std::vector<RCF::FilterPtr> payloadFilters;
    payloadFilters.push_back( RCF::FilterPtr(
        new RCF::ZlibStatefulCompressionFilter()));
    payloadFilters.push_back( RCF::FilterPtr(
        new RCF::ZlibStatefulCompressionFilter()));
    client.setPayloadFilters(payloadFilters);
}

...{
    std::string certFile = "server.pem";
    std::string certFilePwd = "server_password";

    // FilterService service enables the server to load the filters it needs

    RCF::FilterServicePtr filterServicePtr( new RCF::FilterService );
    filterServicePtr->addFilterFactory( RCF::FilterFactoryPtr(
        new RCF::ZlibStatelessCompressionFilterFactory) );
    filterServicePtr->addFilterFactory( RCF::FilterFactoryPtr(
        new RCF::ZlibStatefulCompressionFilterFactory) );
    filterServicePtr->addFilterFactory( RCF::FilterFactoryPtr(
        new RCF::OpenSslEncryptionFilterFactory(certFile, certFilePwd)) );

    RCF::RcfServer server(endpoint);
    server.addService(filterServicePtr);
    server.start();
}
远程对象创建
RcfServer类允许用户暴露(expose)一个类的单个实例给远程客户端,但也没有限制远程客户端在服务器上创建对象。远程对象创建就是ObjectFactoryService服务的职责。一旦在服务器端安装了ObjectFactoryService服务,客户端就可以通过I_ObjectFactory接口来创建对象,允许创建对象的个数是在服务中配置的。

ObjectFactoryService服务还实现了一个垃圾回收机制,借此机制那些不再使用(例如没有活动的客户端会话或者在配置的持续时间内没有客户端访问)的对象最终将会被删除。请注意:目前在此服务内还没有一个安全保证机制。一个创建了的对象可以被这个服务器的所有客户端访问。

...{
    // allow max 50 objects to be created
    unsigned int numberOfTokens = 50;

    // delete objects after 60 s, when no clients are connected to them
    unsigned int objectTimeoutS = 60;

    // create object factory service
    RCF::ObjectFactoryServicePtr objectFactoryServicePtr(
        new RCF::ObjectFactoryService(numberOfTokens, objectTimeoutS) );

    // allow clients to create and access Echo objects, through I_Echo
    objectFactoryServicePtr->bind<I_Echo, Echo>();

    RCF::RcfServer server(endpoint);
    server.addService(objectFactoryServicePtr);
    server.start();
}

...{
    RcfClient<I_Echo> client1(endpoint);
    bool ok = RCF::createRemoteObject<I_Echo>(client1);
    // client can now be used to access a newly created object on the server.


    RcfClient<I_Echo> client2(endpoint);
    client2.getClientStub().setToken( client1.getClientStub().getToken() );
    // client1 and client2 will now be accessing the same object

}

发布订阅(Publish/Subscribe)
发布订阅模式是分布式程序中众所周知的术语。一个进程扮演发布者的角色,定期地给订阅者发送消息包。订阅者呼叫发布者并且请求发布者发布的订阅数据。

对于像UDP这样面向包的传输,在已有的RCF原语上建立这个功能是相对简单的。对于像TCP这样的面向流的传输,RCF提供了额外的特征来使能发布订阅功能。订阅者呼叫的连接被逆转,然后发布者利用这个连接来发布。

PublishingService和SubscriptionService两个服务构成了这个功能。下面的例子描述了如何使用这些服务。

RCF_BEGIN(I_Notify, "I_Notify")
    RCF_METHOD_V1(void, func1, int)
    RCF_METHOD_V2(void, func2, std::string, std::string)
RCF_END(I_Notify)

...{
    RCF::RcfServer publishingServer(endpoint);
    RCF::PublishingServicePtr publishingServicePtr(
        new RCF::PublishingService() );
    publishingServer.addService(publishingServicePtr);
    publishingServer.start();

    // start accepting subscription requests for I_Notify
    publishingServicePtr->beginPublish<I_Notify>();

    // call func1() on all subscribers
    publishingServicePtr->publish<I_Notify>().func1(1);

    // call func2(std::string, std::string) on all subscribers
    publishingServicePtr->publish<I_Notify>().func2("one", "two");

    // stop publishing I_Notify and disconnect all subscribers
    publishingServicePtr->endPublish<I_Notify>();

    publishingServer.stop();
}

...{
    RCF::RcfServer subscribingServer(endpoint);
    RCF::SubscriptionServicePtr subscriptionServicePtr(
        new RCF::SubscriptionService() );
    subscribingServer.addService( subscriptionServicePtr );
    subscribingServer.start();

    Notify notify;
    subscriptionServicePtr->beginSubscribe<I_Notify>(
    notify, RCF::TcpEndpoint(ip, port));

    // notify.func1() and notify.func2()
    // will now be remotely invoked by the publisher
    // ...

    subscriptionServicePtr->endSubscribe<I_Notify>(notify);
}

 

 

可扩展性
传输
对于前一个版本的RCF,一个(应得的)批评是关于它和TCP协议过度紧密的关系。现在RCF采用了传输无关的设计,并且对于初用者,可以使用它支持的TCP和UDP协议。更重要的是,它的架构很容易支持第三方开发自己的客户端/服务器端的传输。在客户端,I_ClientTransport基类给客户端调用提供了钩子。在服务器端,需要写一个服务。

RcfServer服务
类RcfServer通过服务的概念来容纳第三方扩展。当服务器开始和停止时,这些服务将会被通知到,服务也可以在服务器运行时被动态添加和移除。一个典型的服务可能是绑定一个对象到服务器的接口,请求服务器创建一个线程然后做一些定期的活动。

服务器的传输就是以服务的形式实现的,所以一个RcfServer对象可以拥有多个传输。一些可用的服务如下:

<!--[if !supportLists]-->l         <!--[endif]-->ObjectFactoryService:允许客户端在服务器端创建对象

<!--[if !supportLists]-->l         <!--[endif]-->FilterService:允许服务器为了响应客户端的请求而动态加载过滤器

<!--[if !supportLists]-->l         <!--[endif]-->PublishingService:使能服务器的发布功能

<!--[if !supportLists]-->l         <!--[endif]-->SubscriptionService:使能服务器的订阅功能

I_Service是所有服务的基类,在相关文档里有描述。查看前面提到的服务的源代码,从而来了解I_Service如何实现的。

可移植性
编译器
RCF 0.4已经在Visual C++ 7.1, Visual C++ 8.0, gcc 3.x, Borland C++ 5.6, 和Metrowerks CodeWarrior 9.2上面测试过。另外,RCF 0.9c支持在Visual C++ 6.0和gcc 2.95上编译。

平台
RCF的服务器实现是基于Win32的I/O完成端口(completion port)的,所以限制在了Windows2000和以后的版本。TCP的客户端和UDP客户端/服务器端都是基于BSD套接字实现的,所以具有很好的移植性。在非Windows平台上(Windows上可选的),RCF利用asio库来实现TCP服务器。

编译
一般来说,为了编译使用RCF的应用程序,你需要在你的应用程序代码里包含src/RCF/RCF.cpp。你还会需要Boost库的头文件(最近的任何版本应该都可以)。如果你打算用Boost.Serialization,你需要1.31.0或者更新的版本。

可以用一些预编译符号(preprocessor symbols)来控制RCF哪些部分将被编译。这些符号需要在全局定义,比如在工程的设置里定义而不是定义在代码里。

<!--[if !supportLists]-->l         <!--[endif]-->RCF_USE_BOOST_THREADS:利用Boost线程库mutex和线程创建功能。如果没有定义,RCF将不再是线程安全的。

<!--[if !supportLists]-->l         <!--[endif]-->RCF_USE_BOOST_READ_WRITE_MUTEX:利用1.32.0版本的Boost库的读/写mutex。如果没有定义,将使用一个简单的替代。只有当RCF_USE_BOOST_THREADS定义时才有效。

<!--[if !supportLists]-->l         <!--[endif]-->RCF_USE_ZLIB:编译对Zlib压缩的支持。

<!--[if !supportLists]-->l         <!--[endif]-->RCF_USE_OPENSSL:编译对OpenSSL加密(过滤器)的支持。

<!--[if !supportLists]-->l         <!--[endif]-->RCF_USE_BOOST_SERIALIZATION:编译对Boost.Serialization库的支持。

<!--[if !supportLists]-->l         <!--[endif]-->RCF_USE_SF_SERIALIZATION:编译对RCF内建的序列化框架的支持。如果RCF_USE_BOOST_SERIALIZATION和RCF_USE_SF_SERIALIZATION都没有被定义,RCF_USE_SF_SERIALIZATION将会被自动定义。

<!--[if !supportLists]-->l         <!--[endif]-->RCF_NO_AUTO_INIT_DEINIT:禁止RCF的自动初始化和反初始化功能。如果定义了,用户需要在适当的时间显示地调用RCF::init()和RCF::deinit()。特别地,当把RCF编译成DLL时,需要去定义这个预编译符号,从而防止过早地初始化。

总的来说,对第三方库的依赖(Boost.Threads, Boost.Serialization, Zlib, OpenSSL)是可选的。编译这些库的说明已经超出本篇文章的范围,但是如果你在编译Boost库时有困难,一个可行的方法是使用Boost的编译工具—bjam,然后编译相应的CPP文件到你的应用程序里。

例如,为了使用Boost.Threads库,你只要包含boost_root/libs/thread/src目录下的CPP文件到你的工程中。然后在boost_root/boost/thread/detail/config.hpp文件里定义一个合适的预编译符号(可能是BOOST_THREAD_USE_LIB)。

测试
在下载的目录里有个/test目录,里面有全面的测试代码,所有的代码都应该能够成功编译运行。这次测试代码应该能够用Boost.Build工具自动编译和运行,或者手动编译运行。这次测试代码不仅有我所描述的这些功能,对于用户来说也可以提供更多有用的信息。

反馈
这个库的现在和将来的形状和范围很大程度上都依赖我从用户那里得到的反馈。所以敬请发表你们的看法,欢迎任何的观点。你可以在这里提出问题,或者给我发email。

当然,我要感谢那些在前一个版本给出帮助的人。

历史
2005-12-23 – 版本 0.1 发布
2006-04-06 – 版本 0.2 发布
RCF现在可以在Linux,Solaris和Windows上编译运行,服务器端和客户端可以分布在多个平台上,并且可以无缝地通讯。

在非Windows平台上,为了使用RCF,asio网络库是首要必备的。下载asio并确保asio头文件对于你的编译器是可用的,在编译RCF时还要确保已经定义了RCF_USE_ASIO预编译符号。Asio库需要1.33.0或者更新的Boost库,为了避免Boost的依赖库,你可能需要定义BOOST_DATE_TIME_NO_LIB预编译符号。

RCF 0.2已经在下列编译器上进行了编译和测试:

gcc 3.2 (MinGW on Windows)
gcc 3.3 (Linux)
gcc 3.4 (Solaris)
Borland C++ 5.6
Metrowerks CodeWarrior 9.2
Microsoft Visual C++ 7.1
Microsoft Visual C++ 8.0
很多在这个论坛上报告的bug已经被修复了。

向那些等待这个版本发布的人道歉,我本来计划几个月之前发布的,但是由于繁忙的工作安排(还有一些难处理的测试例)干扰了版本的正常发布。对不起!

2006-07-30 – 版本 0.3 发布
RCF对asio的支持已经从版本0.3.5升级到版本0.3.7,这个工作是David Bergman.完成的,谢谢David!
去掉了对asion 0.3.5的支持。
预编译符号由RCF_USE_ASIO变为RCF_USE_BOOST_ASIO。
2006-09-19 – 版本 0.4 发布
64位兼容性: RCF可以编译和运行在64位的Solaris, Linux和Windows平台上。
对于std::vector<T>的快速序列化。这里的T是原始类型 (char, int等)
对于使用RCF来进行32位和64位系统见通讯,可以通过使用boost的typedef(boost::int32_t, boost::uint32_t, boost::int64_t, boost::uint64_t)来代替long和std::size_t,从而避免32位系统和64位系统之间的差异导致的序列化错误。
<!--[if !supportLists]-->o        <!--[endif]-->最后,感谢不伦瑞克技术大学的Sören Freudiger,感谢他借给我64位Linux机器的账号。

2007-07-11 – 版本 0.9c 发布
RCF 0.9c是RCF 1.0的预发布版,现在可以从Google Code下载。随着一年多的开发,RCF 0.9c重新构建和升级了RCF 0.4。被用作一个主要的ECM平台的网络基础,RCF建立了自己的地位。
使用RCF 0.4的应用程序升级到RCF 0.9c会有一些困难。如果你在升级到0.9c时候遇到任何认为,请随时和我联系。我会帮助你解决。
RCF 0.9c的特性有:
为了快速和可伸缩的性能的零拷贝,零堆内存分配内核
SSPI过滤器,为了在Windows平台上,传输Kerberos和NTLM认证和加密
OpenSSL过滤器,为传输SSL认证和加密
服务器端的多线程
服务器端的会话对象
内建的运行时版本识别, 为了前向和后向兼容
健壮的发布订阅功能
支持老的编译器,也就是Visual C++ 6, Borland C++ Builder 6和gcc2.95
支持64位编译器
2007-08-23 – 文章内容更新
2008-04-28 – 版本 0.9d-P1 发布
RCF 0.9d-P1是RCF-0.9d的一个预览版。它在Windows上Visual C++编译器下(6.0, 7.1, 8.0, 9.0)经过全面的测试。RCF 0.9d发布版将包含对Linux和Solaris的全面支持,和以用户指导手册形式的丰富的文档。
可以从Google Code的下载页面下载。
RCF 0.9d特性包括:
Win32的命名管道传输实现 (RCF::Win32NamedPipeEndpoint)
不再需要Boost.Thread
UDP的多播和广播
SF序列化对boost::tuple, boost::variant 和boost::any 的支持
支持从DLL导出RCF
兼容Boost的最新版本 (1.35.0)和Boost.Asio (0.3.8+)版本
与RCF 0.9c的兼容
许可
请注意:每个下载的代码都是独立的。对代码和文章的协议如下:

The article is licensed under The Code Project Open License (CPOL)
RCF 0.4 is licensed under the MIT license
RCF 0.9c and successors are licensed under the GPL license
关于作者
<!--[if !vml]--><!--[endif]-->Jarl Lindrud

软件开发者,前瑞典居民,现在居住在澳大利亚的堪培拉。工作在分布式C++应用程序方面。作者喜欢程序设计,但更喜欢滑雪和乒乓球运动。别人提到他时他会感到非常大的满足。