Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

RCF - Interprocess Communication for C++

0.00/5 (No votes)
25 Oct 2011 353  
A server/client IPC framework, using the C++ preprocessor as an IDL compiler.

Introduction

RCF (Remote Call Framework) is a C++ IPC framework, providing an easy and consistent way of implementing interprocess communication in C++ programs. It is based on the concept of strongly typed client/server interfaces, a concept familiar to users of IDL-based middlewares such as CORBA and DCOM. However, by catering only to C++, RCF can harness C++-specific language features that allow the formulation of interprocess calls in a simpler and less cluttered manner.

RCF provides a number of IPC messaging paradigms (oneway, batched oneway, request/response, publish/subscribe) over a number of transports (TCP, UDP, Windows named pipes, UNIX local domain sockets) with support for compression (zlib) and a number of encryption and authentication technologies (Kerberos, NTLM, Schannel, OpenSSL). You can use RCF in all kinds of IPC scenarios - from simple parent-child process communication, right up to large-scale distributed systems. Best of all, RCF is 100% standard C++, and will run on any platform with a C++ compiler.

RCF releases and documentation are available on the RCF website. This article presents a brief overview of RCF.

Basics

We'll begin with a simple echo server and client. We want a server to expose to its clients a function that accepts a string, and which returns the same string. Using RCF, the server code becomes:

#include <RCF/Idl.hpp>
#include <RCF/RcfServer.hpp>
#include <RCF/TcpEndpoint.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;
}

... and the client code becomes:

#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 is an RCF interface defined by the RCF_BEGIN/RCF_METHOD/RCF_END macros. These macros correspond to what in CORBA would be an IDL definition, but in this case, the interface definition is placed inline in C++ source code, and requires no separate compilation step, as is the case for IDL interfaces. The interface is simply included in the source code of the server and the client, and compiled along with the rest of the program.

The RCF::Twoway argument in the client stub invocation is a flag that tells RCF to make a two-way client call; the client sends a request, waits for a response, and if none is received within a configurable duration of time, an exception is thrown. The other option is to use RCF::Oneway; a request is sent, but the server does not send a response, and the client stub call will return control to the user immediately. If the directional argument is omitted, RCF::Twoway is used by default.

The client stub is not synchronized in any way, and should only be accessed by a single thread at a time. The server, though, is inherently multi-threaded, although in the example above, it has been written as a single-threaded process. RcfServer::startInThisThread() hijacks the calling thread and converts it to a worker thread of the server.

We can also call RcfServer::start(), and the threads needed to drive the server would then automatically be created. RcfServer::start() returns immediately after launching the server threads. If we did that, we would need to put in a wait loop for the main thread so that it doesn't leave main(), shutting down the entire process.

We can rewrite our client/server pair to use the UDP protocol instead. This time, we'll let the server and the client both reside in the same process, but in different threads:

#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;
}

UDP, of course, is significantly different from TCP. TCP provides reliable, ordered delivery of network packets, while UDP makes no guarantees at all regarding the order of packets that are sent or whether the packets will even arrive at all. On a loopback connection, like in the example above, one might get away with using two-way semantics over UDP, since the packets are not being subjected to the vagaries of a real network. In general, however, you would only want to send one-way calls with UDP.

Interfaces

The interface definition macros function exactly like they did in the previous generation of RCF. The RCF_BEGIN() macro commences an interface definition with the given name and runtime description, and RCF_END() ends the interface definition. In between, one can have up to 25 RCF_METHOD_xx() macros to define the member methods of the interface.

The last two letters in the RCF_METHOD_xx() macros indicate the number of arguments and whether the return type is void. For instance, RCF_METHOD_V3 is used to define a method with three parameters and a void return value, while RCF_METHOD_R2 defines a method with two arguments and a non-void return value.

The net result of these macros is to define the RcfClient<type> class, where type is the name of the interface. This class is then used directly by the client as a client stub, and indirectly by the server as a server stub. RCF interfaces can be defined in any namespace:

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;
    // ...

}

Server Bindings

On the server side, the interface needs to be bound to a concrete implementation. This is done through the templated RcfServer::bind() functions. There are several variations that accommodate various memory management idioms. The following calls all accomplish pretty much the same thing, the binding of an Echo object to the I_Echo interface:

{
    // 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);
}

By default, the binding is available to clients through the name of the interface. The server can also expose several objects through the same interface, but in that case, it needs to explicitly set the binding names:

{
    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 follows C++ conventions when it comes to determining in which directions arguments are marshaled. In particular, all arguments to an interface method are in parameters, all non-const reference arguments are inout parameters, and the return value is an out parameter. There are currently no provisions for overriding these conventions along the lines of the in/out/inout qualifiers in IDL definitions.

Not everything in C++ can be safely marshaled, and this places some limitations on the types of arguments to interface methods. To wit: pointers and references are allowed as arguments; references to pointers are not allowed as arguments; pointers and references are not allowed as return values.

This means that if an interface method needs to return a pointer -- for instance, a polymorphic pointer -- then the return type needs to be a smart pointer such as std::auto_ptr<> or boost::shared_ptr<>. Alternatively, one of the arguments could be a non-const reference to a smart pointer.

Serialization

The echo example only serialized a std::string object, but it is possible to send almost any C++ class or structure within the limits of the serialization system in use. RCF has its own serialization framework, prosaically named Serialization Framework (SF), but it also supports the Boost.Serialization framework, part of the Boost library.

In general, it is necessary to include serialization code for the classes that are being marshaled. If you have a std::vector<> argument in an interface, you will need to include <SF/vector.hpp> or <boost/serialization/vector.hpp> (or both), and similarly for other STL and Boost classes.

To use your own classes as arguments in RCF interfaces, you will need to define custom serialization functions. In most cases, it's quite simple:

#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;
//}

Serialization can quickly become a complex affair, especially when dealing with pointers of polymorphic objects and cycles of pointers. Both SF and Boost.Serialization handle these situations, but in different ways, and catering to both may require writing separate serialization code for each serialization system. The following is an example of sending polymorphic objects, using both SF and 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

Multiple inheritance is now supported for RCF interfaces. Interfaces may inherit not only from each other, but also from standard C++ classes. Methods in an interface are identified by the combination of their dispatch ID and the name of the interface that they belong to. This information suffices for the server to map an incoming client call to the correct function on the server binding. The example below demonstrates interface inheritance:

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)

// 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();
}

Filters

RCF supports compression and encryption of messages out of the box, by way of a filter concept. Filters are applied to both server- and client-side, and can be applied either to the transport layer -- for instance, applying an SSL filter to a stream-oriented transport like TCP -- or to individual message payloads such as, for instance, compression of messages destined for a packet-oriented transport like UDP. The first case we call a transport filter, and the second case, a message filter.

Transport Filters

The process of installing a transport filter on a server-client conversation is initiated by the client. The client queries the server to determine if the server supports a given filter. If the server does, the filter is installed on both ends of the transport, and the communication resumes.

The procedure of querying the server and installing the filter is handled by the client stub. The client calls the ClientStub::requestTransportFilters() function, and if negotiation with the server is successful, the filter sequence is then active and applies to all subsequent remote calls.

Transport filters can be two-way, in the sense that a single read or write request on a filter may result in multiple read and write requests being made on the connection. For example, the first message sent over an encryption filter will initiate some kind of a handshake involving multiple downstream read and write operations.

Message Filters

Message filters do not require any negotiation on behalf of either the server or the client. If a client stub has been imbued with a sequence of message filters via ClientStub::setMessageFilters(), then the message will be passed through the given filters. The encoded message will be prefixed with data describing which filters have been used, thereby allowing the server to decode the message. Should the server not recognize a filter, an exception is passed back to the client.

RCF comes with several filters out of the box: two for compression, based on Zlib, two for SSL encryption, based on OpenSSL and Schannel, and two Windows based filters for the Kerberos and NTLM protocols. These filters can also be chained to each other to create sequences of filters.

The encryption filters can only be used as transport filters, since the process of encryption and decryption requires a two-way handshake. The compression filters can be used either as transport or message filters, but over non-stream-oriented transports such as UDP, it's only meaningful to use the stateless compression filter.

An example:

{
    // Compression of payload.
    RcfClient<I_X> client(endpoint);
    client.getClientStub().setMessageFilters( RCF::FilterPtr(
        new RCF::ZlibStatelessCompressionFilter() ) );

    // Encryption of transport.
    std::string certFile = "client.pem";
    std::string certFilePwd = "client_password";

    client.getClientStub().requestTransportFilters( RCF::FilterPtr(
        new RCF::OpenSslEncryptionFilter(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(certFile, certFilePwd)) ) );

    client.getClientStub().requestTransportFilters(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.getClientStub().setMessageFilters(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();
}

Remote Objects

The RcfServer class allows users to expose single instances of a class to remote clients, but it makes no provisions for remote clients to create any objects on the server. This functionality is instead part of the ObjectFactoryService service. Once an ObjectFactoryService service has been installed into a server, it becomes possible for clients, via the ClientStub::createRemoteObject() function, to create as many objects as the service is configured to allow.

The ObjectFactoryService service implements a garbage collection policy, whereby objects that are no longer in use (i.e., have no active client sessions and have not been accessed for a configurable duration of time) are eventually deleted.

{
    // 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 ofsPtr(
        new RCF::ObjectFactoryService(numberOfTokens, objectTimeoutS) );

    // Allow clients to create and access Echo objects, through I_Echo.
    ofsPtr->bind<I_Echo, Echo>();

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

{
    RcfClient<I_Echo> client1(endpoint);
    bool ok = client1.getClientStub().createRemoteObject();

    // client1 can now be used to make calls
    // to a newly created object on the server.

    RcfClient<I_Echo> client2(endpoint);
    client2.getClientStub().setToken( client1.getClientStub().getToken() );

    // client1 and client2 will now be making calls to the same object

}

Session Objects

Objects created by ObjectFactoryService are identified by a token, and can be accessed by any client that specifies that particular token. So once created, such an object can potentially be accessed by many clients. It is quite common, though, for a client to want to create objects that are only accessible by that particular client. In RCF, such objects are called session objects, and are created by SessionObjectFactoryService:

{
    // Create session object factory service.
    RCF::SessionObjectFactoryServicePtr sofsPtr(
        new RCF::SessionObjectFactoryService() );

    // Allow clients to create and access Echo objects, through I_Echo.
    sofsPtr->bind<I_Echo, Echo>();

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

{
    RcfClient<I_Echo> client1(endpoint);
    bool ok = client1.getClientStub().createRemoteSessionObject();

    // client1 can now make calls to its
    // own private Echo instance on the server.

    RcfClient<I_Echo> client2(endpoint);
    ok = client2.getClientStub().createRemoteSessionObject();

    // client1 and client2 will now be making calls
    // to their own session specific Echo instances.

    // When the clients close their connections, the server
    // will immediately delete the associated session objects.
    // ...

}

Publish/Subscribe

The Publish/Subscribe pattern is a well-known pattern of distributed programming. One process plays the role of a publisher and sends regular packets of information to a group of subscribers. The subscribers call in to the publisher and request subscriptions to the data that the publisher is publishing.

For packet-oriented transports such as UDP, it is relatively straightforward to build this functionality on top of existing RCF primitives. For stream-oriented transports, TCP in particular, RCF provides some extra features to enable publish/subscribe functionality. The connection that a subscriber calls in on is reversed and then subsequently used by the publisher for publishing.

This functionality is encompassed by the dual PublishingService and SubscriptionService services. The following example describes how these services are used:

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 called by the publisher.
    // ...

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

Extensibility

RcfServer Services

The RcfServer class accommodates third party extensions through a service concept. Services are notified, among other things, when the server starts and stops. Also, services can be dynamically added and removed from the server while the server is running. A typical service may bind an object to an interface on the server, and may also request the server to spawn a thread (or spawn its own threads) to do some kind of periodic activity.

Server transports are implemented as services. It is thus possible to have several transports serving a single RcfServer object. Some of the services available are:

  • ObjectFactoryService: Allows clients to create objects on the server
  • FilterService: Allows the server to dynamically load filters in response to client requests
  • PublishingService: Enables publishing functionality on a server
  • SubscriptionService: Enables subscription functionality on a server

Portability

Compilers

RCF has been tested against a large number of compilers, among them Microsoft Visual C++ (from version 6.0), Borland C++ (from version 5.6), and GCC (from version 2.95). For a complete list, see the RCF User Guide.

Platforms

RCF's TCP server implementation is now based on Win32 I/O completion ports, a technology limited to Windows 2000 and later. The TCP client implementation and the UDP server/client implementations are based on BSD sockets, and should be widely portable. On non-Windows platforms, and optionally on Windows as well, RCF leverages the Boost.Asio library to implement TCP servers.

Building

In general, to build an application using RCF, you need to include the file src/RCF/RCF.cpp among the sources of your application. You will also need the header files of the Boost library; any version since 1.33.0 should be OK.

There are several preprocessor symbols that can be used to control which parts of RCF are compiled. These symbols need to be defined globally, i.e., they should be placed in the project settings, and not defined/undefined in source code.

  • RCF_USE_BOOST_THREADS: Utilize the Boost.Thread library for mutex and thread generation capabilities. If not defined, RCF will use its own internal thread library.
  • RCF_USE_BOOST_ASIO: Utilize the Boost.Asio networking library. Necessary for server-side code on non-Windows platforms.
  • RCF_USE_ZLIB: Compile in support for Zlib compression.
  • RCF_USE_OPENSSL: Compile in support for OpenSSL encryption.
  • RCF_USE_BOOST_SERIALIZATION: Compile in support for the Boost.Serialization library.
  • RCF_USE_SF_SERIALIZATION: Compile in support for RCF's built-in serialization framework. Automatically defined if neither RCF_USE_BOOST_SERIALIZATION nor RCF_USE_SF_SERIALIZATION is defined.
  • RCF_NO_AUTO_INIT_DEINIT: Disables RCF's automatic initialization/deinitialization facility. If defined, the user will have to explicitly call RCF::init() and RCF::deinit() at appropriate times. In particular, this is necessary when compiling RCF into a DLL in order to avoid premature initialization.

All third party build dependencies (Boost.Threads, Boost.Serialization, Zlib, OpenSSL) are optional. Instructions for building these libraries are beyond the scope of this article, but if you're having trouble building the Boost libraries, an option is to bypass Boost's build utility, bjam, and instead, simply compile the corresponding CPP files into your application.

To use the Boost.Threads library, for instance, you can just include the CPP files in boost_root/libs/thread/src in your project. Then, define one of the symbols in boost_root/boost/thread/detail/config.hpp, whichever one is appropriate (probably BOOST_THREAD_USE_LIB).

Testing

There is a comprehensive set of tests in the /test directory of the download, all of which should compile and run without failures. The tests can be built and run automatically using the Boost.Build utility, or built and run by hand. The tests exercise more functionality than I've mentioned here, and may be useful as a source of information for users.

History

  • 2005-12-23 - Version 0.1 released
  • 2006-04-06 - Version 0.2 released
  • RCF now compiles and runs on Linux and Solaris, as well as on Windows. Servers and clients can be distributed across multiple platforms, and still communicate seamlessly.

    The Asio networking library is a prerequisite for using RCF on non-Windows platforms. Download Asio, make sure that the Asio headers are available to your compiler, and make sure the preprocessor symbol RCF_USE_ASIO is defined when compiling RCF. Asio requires version 1.33.0 or later of Boost, and you'll probably want to define BOOST_DATE_TIME_NO_LIB when using it in order to avoid unnecessary Boost dependencies.

    RCF 0.2 has been compiled and tested on the following compilers:

    • 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

    Various bugs reported on this forum have also been fixed.

    Apologies to anyone who's been waiting for this release... I originally planned to release it several months ago, but a busy work schedule (and some stubborn test cases) intervened. Sorry!

  • 2006-07-30 - Version 0.3 released
    • RCF support for Asio has now been advanced to version 0.3.7 from 0.3.5, by David Bergman. Thanks, David!
    • Support for Asio 0.3.5 has been dropped.
    • The preprocessor symbol RCF_USE_ASIO is now named RCF_USE_BOOST_ASIO.
    • Miscellaneous issues reported on this forum have also been fixed.
  • 2006-09-19 - Version 0.4 released
    • 64 bit compatibility: RCF now compiles and runs on 64 bit Solaris, Linux, and Windows platforms.
    • Fast serialization of std::vector<T>, where T is primitive (char, int, etc.).
    • For those using RCF to communicate between 32 and 64 bit systems, you can avoid serialization errors caused by size discrepancies between 32 and 64 bit systems, by not using types like long or std::size_t in RCF interfaces. Use one of the boost typedefs instead (boost::int32_t, boost::uint32_t, boost::int64_t, boost::uint64_t).
    • Finally, thanks to Sören Freudiger at the Technical University of Braunschweig, for lending me an account on their 64-bit Linux machines!
  • 2007-07-11 - Version 0.9c released
    • RCF 0.9c, a pre-release version of RCF 1.0, is now available from the download section of Google Code. In development for over a year, RCF 0.9c constitutes a major restructuring and upgrade of RCF 0.4. It has been thoroughly put through its paces as the networking backend of a major commercial ECM platform.
    • Applications using RCF 0.4 should have relatively few difficulties upgrading to RCF 0.9c. If you have any questions on porting your applications to 0.9c, feel free to contact me either on this forum or via email. I will be happy to help you out.
    • Among RCF 0.9c features are:
      • Zero-copy, zero-heap allocation core for fast and scalable performance.
      • SSPI filters, for transparent Kerberos and NTLM authentication and encryption on Windows platforms.
      • OpenSSL filter, for transparent SSL authentication and encryption.
      • Server-side multithreading.
      • Server-side session objects.
      • Built-in runtime versioning, for backward and forward runtime compatibility.
      • Robust publish/subscribe functionality.
      • Support for legacy compilers, in particular, Visual C++ 6, Borland C++ Builder 6, and GCC 2.95.
      • Support for 64-bit compilers.
  • 2007-08-23 - Article content updated
  • 2008-04-28 - Version 0.9d-P1 released
    • RCF 0.9d-P1 is a preview of RCF-0.9d. It has been fully tested on Windows, against the Visual C++ range of compilers (6.0, 7.1, 8.0, 9.0). The RCF 0.9d release will include full support for Linux and Solaris, and extensive documentation in the form of a User Guide.
    • Get it from the download page of the Google Code site.
    • RCF 0.9d features include:
      • Win32 named pipe transport implementations (RCF::Win32NamedPipeEndpoint).
      • Boost.Thread no longer required.
      • UDP multicasting and broadcasting.
      • SF serialization for boost::tuple, boost::variant, and boost::any.
      • Support for exporting RCF from a DLL.
      • Compatible with latest Boost (1.35.0) and Boost.Asio (0.3.8+) versions.
      • Guaranteed wire-compatible with RCF 0.9c.
  • 2008-07-20 - Article content updated
  • 2008-07-20 - Version 0.9d released
    • RCF 0.9d is now available for download.
    • As part of the 0.9d release, the RCF User Guide is now available online.
    • As always, questions and comments are welcome!
  • 2008-10-22 - Version 1.0 released
    • RCF 1.0 is now available for download.
    • RCF 1.0 features include:
      • Support for more compilers (Intel C++ 9 and 10.1 for Windows, GCC 4.3)
      • Support for more platforms (FreeBSD and OS X).
      • Compatible with Boost 1.36.0 and 1.37.0.
      • Supports use of UNIX domain sockets as transport (RCF::UnixLocalEndpoint).
      • Updates to the RCF User Guide.
    • Thanks to Duane Murphy for providing the original OS X port.
  • 2009-07-10 - Version 1.1 released
    • RCF 1.1 is now available for download.
    • RCF 1.1 features include:
      • Ping function added to RCF::ClientStub.
      • Server-to-client pingbacks, for maintaining connectivity during long-running calls (RCF::PingBackService).
      • Server-to-client callbacks.
      • Dynamic thread pool grows and shrinks, to accommodate client load. User level code no longer needs to call ThreadManager::notifyBusy().
      • Progress callbacks on all transports.
      • Schannel-based transport filter, for SSL encryption on Windows platforms.
      • Tested against Boost versions up to 1.39.0.
      • Support for __attribute__(visibility()) when building shared libraries with GCC 4.x.
      • Memory usage optimizations.
    • Visual C++ 6 users will need to apply a fix to auto_ptr<>::release() in the Visual C++ 6 STL implementation.
    • Details, as usual, are in the RCF User Guide.
  • 2010-02-15 - Version 1.2 released
    • RCF 1.2 is now available for download.
    • RCF 1.2 features include:
      • Support for Google's Protocol Buffers (documentation here).
      • Support for batched oneway calls (documentation here).
      • Tested against Boost versions up to 1.42.0.
      • If you are upgrading to RCF 1.2 from an earlier version of RCF, there are several breaking changes to be aware of:
        • SF serialization functions now have a different signature. The redundant version parameter has been removed, so for external serialization functions, the signature is now "void serialize(SF::Archive &, SomeType & t)". For internal serialization functions, the signature is now "void SomeType::serialize(SF::Archive &)".
        • The global serializeParent() function has been moved into the SF namespace.
        • The RCF::Exception::getError() function has been renamed to getErrorId().
    • Release notes are available in the RCF User Guide.
  • 2011-01-06 - Version 1.3 released
    • RCF 1.3 is now available for download.
    • RCF 1.3 features include:
      • Support for IPv6.
      • IP-based access rules, to grant or deny access to IP-based servers.
      • User data fields in request and response headers.
      • Running multiple server transports on the same thread pool.
      • Server-side caching of application objects.
      • Default max message length changed from 10KB to 1MB.
      • Maximum number of methods in an RCF interface increased from 35 to 100.
      • Extended auto-versioning to negotiate archive version as well as runtime version.
      • SF serialization
        • Support for tr1 containers and tr1 smart pointers.
        • Support for boost::intrusive_ptr<>.
        • Serialization of std::wstring changed, to use UTF-8.
      • Tested against Boost versions up to 1.45.0.
    • Release notes are available in the RCF User Guide.
  • 2011-10-25 - Version 1.3.1 released
    • RCF 1.3.1 is now available for download.
    • RCF 1.3.1 is a bug-fix release for RCF 1.3. Bugs fixed:
      • Fix for compiler error when using signed char in RCF method signatures.
      • Fixed performance problem when using RCF::SspiFilter. Multiple small message chunks are now merged into a single larger chunk to improve network performance.
      • Improved SF serialization performance. Only calls typeid() when necessary.
      • Reduced SF archive sizes. Encodes small integers using a single byte rather than 4 bytes.
      • Fixed excessive CPU usage when using multithreaded thread pools with Boost.Asio based transports.
      • Fix for boost::any serialization. Empty boost::any instances were causing an exception to be thrown.
      • Fixed bug in client-side timeout logic when polling network connection using the Windows MsgWaitForMultipleObjects() function.
      • Services can no longer be added or removed while an RcfServer is running.
      • Fixed potential null pointer crash in marshaling logic.
      • Renamed variables named signals and slots in order not to interfere with QT preprocessor.
      • Fixed preprocessor redefinition which was causing a compiler warning on OSX.
    • Release notes are available in the RCF User Guide.
    • Web forums are now available for RCF-related questions and comments.

Licenses

Please note: The code for each download is entirely separate. The licenses for the article and code are as follows:

  • The article is licensed under The Code Project Open License (CPOL).
  • RCF 0.4 is licensed under the MIT license.
  • RCF 0.9c and later are licensed under the GPL license.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here