Seigfried, Around the same time your message was posted, I implemented an RPC library on top of C++98 using boost serialization that is communications medium neutral (data can be sent/received with a simple subclass) and supports: * callback and blocking calls * deferred completion * exceptions It also requires no macros by the user. RPC function signatures/prototypes are defined by a simple instance of a template. boost::bind is used to implement an RPC. There would have to be some interest in it before HP would let me release it. Thanks, Eric Siegfried Kettlitz wrote:
Hello boost users and developers,
i'm currently writing a library for making remote procedure calls to C++ objects. The library is based on the variadic templates extension coming with C++0x (already present in GCC 4.3) in conjunction with the boost library (threads, serialization, asio). The aim is to have a library, that is easy to integrate and doesn't require additional tools. Writing it, so far, has thaught me a lot about C++, templates, variadic templates, threading, networking etc..
I post this message, because i think that this library is quite handy in situations, where a developer already uses boost libraries and wants to add some networking functionality to his/her project and the usage of variadic templates instead of additional tools is quite new. One example would be remote monitoring an application by adding a "std::string get_status()"-function to some object. Implemented as example is a chat-system with a server and clients interacting by remote calls. This example is tested with a x64-linux and win32 using the portable boost-text-serialization. Multiple clients can connect to the server via the TCP/IP-acceptor (and disconnect without crashing the system). Implemented is a console-client for text input and display and a morse client, that interacts via a digital output device (relais card), which is connected via a serial port. The serial port and the digital output driver are both also connected via the rpc-system and can be physically located on separate computers.
At this point, i have the basic bits together and a working rpc-library, that needs to be cleaned, refined and extended to match the practical needs. This means, that some programs and libraries have to be written, which use the rpc-library and show its bugs and limitations. The downside of it is, that the interface of the rpc-library may change one more time until it can be considered stable. Therefore it would not be good to test it widely in libraries posing similar requirements, but to test it with a variety of possible applications and provide the implementations as examples and (regression-/unit-)tests.
- One library for Measurement/Automation is already in development. - The chat-server/client exists. - A transaction based game server is under consideration. - Some distributed parallel computing application with dynamic joining/leaving of clients.
Can you give me some more ideas of different applications, which are easy to implement but yet useful? What would you like to have implemented and accessible by an rpc-library?
One speed measurement using local (shared memory) channels resulted in 20000 calls/s and 60000 messages/s on a 1.8GHz Opteron. Do you know how this compares to other implementations on similar hardware?
The TCP/IP-channel implementation using ASIO resulted in only about 100 synchronous calls/second regardless of the payload and almost no CPU load, which suggests that ASIO somewhere "sleeps" on linux systems for a few milliseconds. This would be fixed in some future implementation. Using asynchronous calls (1000 open calls) again leads to about 10000 calls/s.
I'll have a closer look on what can be merged with "channels" from the boost vault[1], but from the first look it seems that "channels" targets a much more general approach. After the first tries, i realized, that my implemention has to be very specific (e.g. templates only where necessary). So, the design approaches might be too different.
After the refinement, cleanup, some testing and documenting, i'd like to publish the rpc-library (and libraries based on it) under GPL- or boost-license. Then you'll hear from me again. In future i'll probably work 2-8 hours per week on the library and use it in some projects, but there's plenty of work left to do in various regions. This will leave space for other developers who might want to use and modify it to fit their needs. Some of the harder parts that would need implementation are redundant parallel connections and advanced routing, authentication of nodes, signing of messages, encryption of payload, access restriction to objects.
Please let me know if * you are interested in this library in some way * have comments * know very similar libraries or * are interested in serialization using variadic function templates.
Regards, Siegfried Kettlitz
---
A short introduction on the library:
* "nodes" are the central parts. They contain a factory for objects, store objects and forward "messages" to objects or other nodes in the network and contain a "scheduler" for executing function calls. The nodes are connected by "channels", which forward messages between the two connected nodes. Currently there is a TCP/IPv4 implementation using ASIO and direct forwarding.
* Nodes connected by channels form a network, create and monitor virtual connections between each other. The network map is used to route messages between connected nodes.
* An object has to supply a template for a class name (only if it can be constructed by the factory) and a template for registering the callable functions (also for abstract base classes which can't be constructed).
* A function call is executed asynchronously (wrapper for synchronous call exists). The call to node.call(...) returns a "link_caller"-object. The function to be executed itself can optionally have "link_callee" as the first argument (which is invisible for the caller). The "link_caller"/"link_callee" is used for synchronization (wait for call to begin/finish execution), message forwarding (push/pop strings at caller/callee) and transfer/extraction of the return value.
* Objects of class hierarchies with abstract base classes are possible and handled transparently.
* Objects can be constructed by the factory of a node or an existing (externally constructed) object can be registered by the node.
* Objects don't necessarily need to know (be modified, inherit something etc.) that they are part of the RPC-system and some of their functions are called remotely.
* Objects can be aware that they are constructed by the factory and can, on construction be informed about their identity and get a reference to the node to make remote calls themselves.
* An object is identified by a "reference" consisting of the "node_id" and the "object_id".
* Parameters are passed (serialized, deserialized) by value. All function parameters must have the possibility to be serialized. Modifying referenced parameters won't affect the parameter at the caller. Pointers obviously won't work between nodes not sharing the same memory-address-space.
* Called functions can use "link_callee" to make remote calls themselves, regardless of the object.
* Priorities are assigned to calls and affect message forwarding and scheduling.
* Calls to objects can be transformed into a boost::function using node.bind(...).
* If a called function throws, the exception is forwarded as rpc::exception or a more specialized exception class. The exception is thrown at the caller, when waiting for interaction with the callee (e.g. wait for return value).
* Template functions as targets are possible when specifying the template parameters of the function pointer when registering the function.
* There is no need to use macros at all, although macros could be used to simplify some things.
----------
// Example for creating and calling an object.
// Create a service. boost::asio::io_service node_io_service;
// Create a node. rpc::node node_s(node_io_service, config);
// Register the builder in order to build the server object later. node_s.register_builderdevicelib::chat_server_impl();
// Register the interface of the client to be able to make calls to it. node_s.register_interfacedevicelib::chat_client();
// Create the server object. rpc::reference cs( node_s.get_id(), node_s.allocate_object_templatedevicelib::chat_server_impl() );
// Store the server in name server. // rpc::reference( node_s.get_id(), rpc::oid_simple_object_name_server ) -> A reference to the object_name_server. // rpc::sched_default() -> Default scheduling parameters. // &rpc::simple_object_name_server::store, cs -> function to call. // cs, "chat_server_object" -> function parameters. node_s.wrap_call( rpc::reference( node_s.get_id(), rpc::oid_simple_object_name_server ), rpc::sched_default(), &rpc::simple_object_name_server::store, cs, "chat_server_object" );
----------
// Example for abstract base class. namespace devicelib { class chat_server : public chat_types { public:
virtual void receive( std::string xml_message ) = 0;
virtual user_id_t connect_user( rpc::reference a_client, std::string xml_user_info ) = 0; virtual std::string query_user( user_id_t id ) = 0; virtual void disconnect_user( user_id_t user ) = 0;
virtual void join_room( user_id_t user, room_id_t room ) = 0;
virtual std::list
list_users( ) = 0; virtual std::list list_rooms( ) = 0; }; }
---------------
// Example for registering the interface of the server object.
// The template is declared in namespace rpc, therefore we need to define it there. namespace rpc {
// For the base object. template<> void register_proxy_functionsdevicelib::chat_server( function_call_proxydevicelib::chat_server& ar_proxy ) { // Register the functions of the abstract base object using unique strings. ar_proxy.register_interface( &devicelib::chat_server::connect_user, "&chat_server::connect_user" ); ar_proxy.register_interface( &devicelib::chat_server::query_user, "&chat_server::query_user" ); ar_proxy.register_interface( &devicelib::chat_server::disconnect_user, "&chat_server::disconnect_user" ); ar_proxy.register_interface( &devicelib::chat_server::join_room, "&chat_server::join_room" ); ar_proxy.register_interface( &devicelib::chat_server::list_users, "&chat_server::list_users" ); ar_proxy.register_interface( &devicelib::chat_server::list_rooms, "&chat_server::list_rooms" ); }
// For each implementation. template<> void register_proxy_functionsdevicelib::chat_server_impl( function_call_proxydevicelib::chat_server_impl& ar_proxy ) { // Register the functions of the base object. register_proxy_functionsdevicelib::chat_server( (function_call_proxydevicelib::chat_server&) ar_proxy ); // Make the functions of this object available. ar_proxy.register_interface( &devicelib::chat_server_impl::connect_user, &devicelib::chat_server::connect_user ); ar_proxy.register_interface( &devicelib::chat_server_impl::disconnect_user, &devicelib::chat_server::disconnect_user ); ar_proxy.register_interface( &devicelib::chat_server_impl::join_room, &devicelib::chat_server::join_room ); ar_proxy.register_interface( &devicelib::chat_server_impl::list_users, &devicelib::chat_server::list_users ); ar_proxy.register_interface( &devicelib::chat_server_impl::list_rooms, &devicelib::chat_server::list_rooms ); }
// The implementation can be built, the base object not. template<> std::string class_builder_namedevicelib::chat_server_impl() { // Give it an unique name here. return "chat_server_impl"; } }
-----------------
1: http://www.boostpro.com/vault/index.php?&direction=0&order=&directory=Distributed%20Computing