RCF - C++ 的进程间通信






4.94/5 (138投票s)
一个服务器/客户端 IPC 框架,使用 C++ 预处理器作为 IDL 编译器。
引言
RCF (Remote Call Framework) 是一个 C++ IPC 框架,它提供了一种简单一致的方式在 C++ 程序中实现进程间通信。它基于强类型客户端/服务器接口的概念,这一概念对于使用基于 IDL 的中间件(如 CORBA 和 DCOM)的用户来说很熟悉。然而,RCF 仅针对 C++,可以利用 C++ 特有的语言特性,以更简单、更简洁的方式表达进程间调用。
RCF 通过多种传输方式(TCP、UDP、Windows 命名管道、UNIX 本地域套接字)提供多种 IPC 消息范例(单向、批量单向、请求/响应、发布/订阅),并支持压缩 (zlib) 以及多种加密和认证技术(Kerberos、NTLM、Schannel、OpenSSL)。您可以在各种 IPC 场景中使用 RCF,从简单的父子进程通信到大规模分布式系统。最重要的是,RCF 是 100% 标准 C++,可以在任何具有 C++ 编译器的平台上运行。
RCF 版本和文档可在 RCF 网站上获取。本文简要介绍了 RCF。
基础
我们将从一个简单的回显服务器和客户端开始。我们希望服务器向其客户端公开一个函数,该函数接受一个字符串并返回相同的字符串。使用 RCF,服务器代码变为
#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;
}
...客户端代码变为
#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
宏定义的 RCF 接口。这些宏对应于 CORBA 中的 IDL 定义,但在本例中,接口定义直接放置在 C++ 源代码中,不需要像 IDL 接口那样单独的编译步骤。接口只是包含在服务器和客户端的源代码中,并与程序的其余部分一起编译。
客户端存根调用中的 RCF::Twoway
参数是一个标志,它告诉 RCF 进行双向客户端调用;客户端发送请求,等待响应,如果在可配置的时间内未收到响应,则会抛出异常。另一个选项是使用 RCF::Oneway
;发送请求,但服务器不发送响应,客户端存根调用将立即将控制权返回给用户。如果省略方向参数,则默认使用 RCF::Twoway
。
客户端存根不以任何方式同步,并且每次只能由一个线程访问。然而,服务器本质上是多线程的,尽管在上面的示例中,它被编写为一个单线程进程。RcfServer::startInThisThread()
会劫持调用线程并将其转换为服务器的工作线程。
我们也可以调用 RcfServer::start()
,然后驱动服务器所需的线程将自动创建。RcfServer::start()
在启动服务器线程后立即返回。如果我们这样做,我们将需要在主线程中放入一个等待循环,以防止它离开 main()
,从而关闭整个进程。
我们可以重写我们的客户端/服务器对以改用 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;
}
当然,UDP 与 TCP 显著不同。TCP 提供可靠、有序的网络数据包传输,而 UDP 对发送数据包的顺序或数据包是否会到达根本不作任何保证。在回环连接上,就像上面的示例一样,使用 UDP 进行双向语义可能可以避免问题,因为数据包不会受到真实网络的变幻莫测的影响。然而,一般来说,您只会希望使用 UDP 发送单向调用。
接口
接口定义宏的功能与 RCF 的上一代完全相同。RCF_BEGIN()
宏以给定名称和运行时描述开始接口定义,而 RCF_END()
结束接口定义。在这两者之间,可以有多达 25 个 RCF_METHOD_xx()
宏来定义接口的成员方法。
RCF_METHOD_xx()
宏的最后两个字母表示参数的数量以及返回类型是否为 void
。例如,RCF_METHOD_V3
用于定义一个具有三个参数和 void
返回值的方法,而 RCF_METHOD_R2
定义一个具有两个参数和非 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);
}
默认情况下,客户端可以通过接口名称访问绑定。服务器也可以通过同一个接口公开多个对象,但在这种情况下,它需要显式设置绑定名称
{
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");
}
封送处理
RCF 在确定参数编组方向时遵循 C++ 约定。特别是,接口方法的所有参数都是 in
参数,所有非 const 引用参数都是 inout
参数,并且返回值是 out
参数。目前没有规定可以像 IDL 定义中的 in
/out
/inout
限定符那样覆盖这些约定。
并非 C++ 中的所有内容都可以安全地进行编组,这对手法方法的参数类型施加了一些限制。即:允许指针和引用作为参数;不允许引用指针作为参数;不允许指针和引用作为返回值。
这意味着如果接口方法需要返回一个指针——例如,一个多态指针——那么返回类型需要是一个智能指针,例如 std::auto_ptr<>
或 boost::shared_ptr<>
。或者,其中一个参数可以是对智能指针的非 const 引用。
序列化
回显示例只序列化了一个 std::string
对象,但几乎所有 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()) );
}
继承
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)
// 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();
}
过滤器。
RCF 通过过滤器概念,开箱即用支持消息压缩和加密。过滤器应用于服务器端和客户端,可以应用于传输层——例如,将 SSL 过滤器应用于 TCP 等面向流的传输——也可以应用于单个消息负载,例如,压缩发送到 UDP 等面向数据包的传输的消息。第一种情况我们称之为传输过滤器,第二种情况称之为消息过滤器。
传输过滤器
在服务器-客户端会话中安装传输过滤器的过程由客户端发起。客户端查询服务器以确定服务器是否支持给定过滤器。如果服务器支持,则在传输的两端安装过滤器,然后通信恢复。
查询服务器和安装过滤器的过程由客户端存根处理。客户端调用 ClientStub::requestTransportFilters()
函数,如果与服务器协商成功,则过滤器序列变为活动状态并应用于所有后续远程调用。
传输过滤器可以是双向的,这意味着对过滤器的单个读取或写入请求可能导致对连接进行多次读取和写入请求。例如,通过加密过滤器发送的第一条消息将启动某种握手,涉及多个下游读取和写入操作。
消息过滤器
消息过滤器不需要服务器或客户端进行任何协商。如果客户端存根通过 ClientStub::setMessageFilters()
注入了一系列消息过滤器,则消息将通过给定的过滤器。编码后的消息将以描述使用了哪些过滤器的数据为前缀,从而允许服务器解码消息。如果服务器无法识别过滤器,则会将异常传递回客户端。
RCF 开箱即用地提供了几种过滤器:两个基于 Zlib 的压缩过滤器,两个基于 OpenSSL 和 Schannel 的 SSL 加密过滤器,以及两个基于 Windows 的 Kerberos 和 NTLM 协议过滤器。这些过滤器也可以相互链式连接以创建过滤器序列。
加密过滤器只能用作传输过滤器,因为加密和解密过程需要双向握手。压缩过滤器可以用作传输过滤器或消息过滤器,但对于 UDP 等非面向流的传输,只有使用无状态压缩过滤器才有意义。
示例
{
// 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();
}
远程对象
RcfServer
类允许用户将类的单个实例公开给远程客户端,但它没有规定远程客户端可以在服务器上创建任何对象。此功能是 ObjectFactoryService
服务的一部分。一旦 ObjectFactoryService
服务安装到服务器中,客户端就可以通过 ClientStub::createRemoteObject()
函数创建服务允许的任意数量的对象。
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 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
}
会话对象
由 ObjectFactoryService
创建的对象通过令牌标识,并且可以由指定该特定令牌的任何客户端访问。因此,一旦创建,此类对象可能被许多客户端访问。但是,客户端通常希望创建仅由该特定客户端访问的对象。在 RCF 中,此类对象称为会话对象,由 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.
// ...
}
发布/订阅
发布/订阅模式是一种众所周知的分布式编程模式。一个进程扮演发布者的角色,并定期向一组订阅者发送信息包。订阅者调用发布者并请求订阅发布者正在发布的数据。
对于 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 called by the publisher.
// ...
subscriptionServicePtr->endSubscribe<I_Notify>(notify);
}
可扩展性
RcfServer 服务
RcfServer
类通过服务概念来容纳第三方扩展。服务在服务器启动和停止时会收到通知,等等。此外,服务可以在服务器运行时动态地添加到服务器或从服务器中移除。典型的服务可能会将对象绑定到服务器上的接口,并且还可能请求服务器生成一个线程(或生成自己的线程)来执行某种周期性活动。
服务器传输作为服务实现。因此,可以有多个传输为单个 RcfServer
对象服务。可用的一些服务是
ObjectFactoryService
:允许客户端在服务器上创建对象FilterService
:允许服务器根据客户端请求动态加载过滤器PublishingService
:在服务器上启用发布功能SubscriptionService
:在服务器上启用订阅功能
可移植性
编译器
RCF 已针对大量编译器进行测试,其中包括 Microsoft Visual C++(从 6.0 版开始)、Borland C++(从 5.6 版开始)和 GCC(从 2.95 版开始)。有关完整列表,请参阅 RCF 用户指南。
平台
RCF 的 TCP 服务器实现现在基于 Win32 I/O 完成端口,这是一项仅限于 Windows 2000 及更高版本的技术。TCP 客户端实现和 UDP 服务器/客户端实现基于 BSD 套接字,应具有广泛的可移植性。在非 Windows 平台,以及可选地在 Windows 上,RCF 利用 Boost.Asio 库来实现 TCP 服务器。
构建
通常,要使用 RCF 构建应用程序,您需要将文件 src/RCF/RCF.cpp 包含在应用程序的源文件中。您还需要 Boost 库的头文件;1.33.0 或更高版本都可以。
有几个预处理器符号可用于控制 RCF 的哪些部分被编译。这些符号需要全局定义,即,它们应该放在项目设置中,而不是在源代码中定义/未定义。
RCF_USE_BOOST_THREADS
:利用 Boost.Thread 库提供互斥锁和线程生成功能。如果未定义,RCF 将使用其自己的内部线程库。RCF_USE_BOOST_ASIO
:利用 Boost.Asio 网络库。在非 Windows 平台上,服务器端代码是必需的。RCF_USE_ZLIB
:编译支持 Zlib 压缩。RCF_USE_OPENSSL
:编译支持 OpenSSL 加密。RCF_USE_BOOST_SERIALIZATION
:编译支持 Boost.Serialization 库。RCF_USE_SF_SERIALIZATION
:编译支持 RCF 内置的序列化框架。如果未定义RCF_USE_BOOST_SERIALIZATION
或RCF_USE_SF_SERIALIZATION
,则自动定义。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 工具自动构建和运行,也可以手动构建和运行。这些测试比我在此处提到的功能更多,可能对用户作为信息来源有用。
历史
- 2005-12-23 - 版本 0.1 发布
- 2006-04-06 - 版本 0.2 发布
- GCC 3.2 (Windows 上的 MinGW)
- 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
- 2006-07-30 - 版本 0.3 发布
- RCF 对 Asio 的支持现在已由 David Bergman 从 0.3.5 升级到 0.3.7。谢谢,David!
- 对 Asio 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 位系统之间通信的用户,可以通过不在 RCF 接口中使用
long
或std::size_t
等类型来避免由于 32 位和 64 位系统之间的尺寸差异导致的序列化错误。请改用其中一个 boosttypedef
(boost::int32_t
、boost::uint32_t
、boost::int64_t
、boost::uint64_t
)。 - 最后,感谢布伦瑞克工业大学的 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 0.4 的应用程序升级到 RCF 0.9c 应该相对容易。如果您对将应用程序移植到 0.9c 有任何疑问,请随时通过此论坛或电子邮件与我联系。我很乐意为您提供帮助。
- RCF 0.9c 功能包括
- 零拷贝、零堆分配核心,实现快速可扩展的性能。
- SSPI 过滤器,用于 Windows 平台上的透明 Kerberos 和 NTLM 身份验证和加密。
- OpenSSL 过滤器,用于透明 SSL 身份验证和加密。
- 服务器端多线程。
- 服务器端会话对象。
- 内置运行时版本控制,用于向后和向前运行时兼容性。
- 强大的发布/订阅功能。
- 支持传统编译器,特别是 Visual C++ 6、Borland C++ Builder 6 和 GCC 2.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 组播和广播。
boost::tuple
、boost::variant
和boost::any
的 SF 序列化。- 支持从 DLL 导出 RCF。
- 与最新的 Boost (1.35.0) 和 Boost.Asio (0.3.8+) 版本兼容。
- 保证与 RCF 0.9c 线路兼容。
- 2008-07-20 - 文章内容更新
- 2008-07-20 - 版本 0.9d 发布
- 2008-10-22 - 版本 1.0 发布
- RCF 1.0 现已可供下载。
- RCF 1.0 功能包括
- 支持更多编译器(适用于 Windows 的 Intel C++ 9 和 10.1,GCC 4.3)
- 支持更多平台 (FreeBSD 和 OS X)。
- 与 Boost 1.36.0 和 1.37.0 兼容。
- 支持使用 UNIX 域套接字作为传输 (
RCF::UnixLocalEndpoint
)。 - 更新了RCF 用户指南。
- 感谢 Duane Murphy 提供原始的 OS X 移植。
- 2009-07-10 - 版本 1.1 发布
- RCF 1.1 现已可供下载。
- RCF 1.1 功能包括
- 已向
RCF::ClientStub
添加 ping 函数。 - 服务器到客户端的 pingback,用于在长时间运行的调用期间保持连接 (
RCF::PingBackService
)。 - 服务器到客户端回调。
- 动态线程池可根据客户端负载增长和收缩。用户级代码不再需要调用
ThreadManager::notifyBusy()
。 - 所有传输上的进度回调。
- 基于 Schannel 的传输过滤器,用于 Windows 平台上的 SSL 加密。
- 已针对 Boost 1.39.0 及更高版本进行测试。
- 在使用 GCC 4.x 构建共享库时支持
__attribute__(visibility())
。 - 内存使用优化。
- Visual C++ 6 用户需要对 Visual C++ 6 STL 实现中的
auto_ptr<>::release()
应用修复。 - 详细信息一如既往地在RCF 用户指南中。
- 2010-02-15 - 版本 1.2 发布
- RCF 1.2 现已可供下载。
- RCF 1.2 功能包括
- 支持 Google 的 Protocol Buffers(文档此处)。
- 支持批量单向调用(文档此处)。
- 已针对 Boost 1.42.0 及更高版本进行测试。
- 如果您正在从 RCF 的早期版本升级到 RCF 1.2,则需要注意几个重大更改
- SF 序列化函数现在具有不同的签名。冗余的版本参数已移除,因此对于外部序列化函数,签名现在是 "
void serialize(SF::Archive &, SomeType & t)
"。对于内部序列化函数,签名现在是 "void SomeType::serialize(SF::Archive &)
"。 - 全局
serializeParent()
函数已移至SF
命名空间。 RCF::Exception::getError()
函数已重命名为getErrorId()
。- 发布说明可在RCF 用户指南中找到。
- 2011-01-06 - 版本 1.3 发布
- RCF 1.3 现已可供下载。
- RCF 1.3 功能包括
- 支持 IPv6。
- 基于 IP 的访问规则,用于授予或拒绝访问基于 IP 的服务器。
- 请求和响应头中的用户数据字段。
- 在同一线程池上运行多个服务器传输。
- 应用程序对象的服务器端缓存。
- 默认最大消息长度从 10KB 更改为 1MB。
- RCF 接口中的最大方法数量从 35 增加到 100。
- 扩展了自动版本控制,以协商存档版本和运行时版本。
- SF 序列化
- 支持
tr1
容器和tr1
智能指针。 - 支持
boost::intrusive_ptr<>
。 std::wstring
的序列化更改为使用 UTF-8。- 已针对 Boost 1.45.0 及更高版本进行测试。
- 发布说明可在RCF 用户指南中找到。
- 2011-10-25 - 版本 1.3.1 发布
- RCF 1.3.1 现已可供下载。
- RCF 1.3.1 是 RCF 1.3 的一个错误修复版本。修复的错误
- 修复了在 RCF 方法签名中使用有符号字符时的编译器错误。
- 修复了使用
RCF::SspiFilter
时的性能问题。多个小消息块现在合并成一个更大的块以提高网络性能。 - 改进了 SF 序列化性能。仅在必要时调用
typeid()
。 - 减小了 SF 存档大小。使用单个字节而不是 4 字节编码小整数。
- 修复了使用基于 Boost.Asio 的传输的多线程线程池时 CPU 使用率过高的问题。
- 修复了
boost::any
序列化中的错误。空的boost::any
实例会导致抛出异常。 - 修复了在使用 Windows
MsgWaitForMultipleObjects()
函数轮询网络连接时客户端超时逻辑中的错误。 - 在
RcfServer
运行时,服务不能再添加或移除。 - 修复了编组逻辑中潜在的空指针崩溃。
- 重命名了名为
signals
和slots
的变量,以避免与 QT 预处理器冲突。 - 修复了导致 OSX 上编译器警告的预处理器重定义。
- 发布说明可在RCF 用户指南中找到。
- 网络论坛现已可用于 RCF 相关问题和评论。
RCF 现在可以在 Linux 和 Solaris 以及 Windows 上编译和运行。服务器和客户端可以跨多个平台分发,并且仍然可以无缝通信。
Asio 网络库是在非 Windows 平台上使用 RCF 的先决条件。下载 Asio,确保 Asio 头文件可供您的编译器使用,并确保在编译 RCF 时定义预处理器符号 RCF_USE_ASIO
。Asio 需要 Boost 1.33.0 或更高版本,并且在使用它时您可能需要定义 BOOST_DATE_TIME_NO_LIB
以避免不必要的 Boost 依赖。
RCF 0.2 已在以下编译器上编译和测试
此论坛上报告的各种错误也已修复。
向所有一直在等待此版本发布的人表示歉意……我最初计划在几个月前发布它,但繁忙的工作日程(和一些顽固的测试用例)打断了。抱歉!
许可证
请注意:每个下载的代码都是完全独立的。文章和代码的许可证如下
- 本文根据 Code Project Open License (CPOL) 许可。
- RCF 0.4 根据 MIT 许可证许可。
- RCF 0.9c 及更高版本根据 GPL 许可证许可。