Basic Communication

The essential form of communication in RSB consists in participants sending and receiving events. The following sections explain:

Sending Data

To send data in RSB, in the simplest case, an informer has to be created for the desired destination scope and the data then has to be passed to it.

A rsb.Informer object is created by calling rsb.createInformer() with

  • the desired scope (which can be specified as str object, for example, a string literal)
  • a data type (which can be object to allow any kind of data)

Once the informer has been created, data is published by calling rsb.Informer.publishData().

Note

The context manager protocol implementation of RSB takes care of correctly deactivating the informer at the end of the with statement. In case you are not using a with statement, the rsb.Informer object has to be deactivated using its rsb.Informer.deactivate() method at the end of use.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import logging

import rsb

if __name__ == '__main__':
    # Pacify logger.
    logging.basicConfig()

    # Create an informer for strings on scope "/example/informer".
    with rsb.createInformer("/example/informer", dataType=str) as informer:

        # Send and event using a method that directly accepts data.
        informer.publishData("example payload")

Download this example

A rsb::Informer object is created by calling obtaining the RSB factory via rsb::Factory::getInstance and then calling its rsb::Factory::createInformer method with

  • the desired scope (which can be specified as std::string object, for example, a string literal)
  • a data type (which can be rsb::AnyType to allow any kind of data)

Once the informer has been created, data is published by calling rsb::Informer::publish.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdlib.h>

#include <rsb/Informer.h>
#include <rsb/Factory.h>

using namespace std;
using namespace rsb;

int main(void) {

    // First get a factory instance that is used to create RSB domain
    // objects.
    Factory& factory = getFactory();

    // Create an informer that is capable of sending events containing
    // string data on the scope "/example/informer".
    Informer<string>::Ptr informer
        = factory.createInformer<string> ("/example/informer");

    // Create data to send over the informer. Data is always
    // maintained in shared_ptr instances. Informer provides a typedef
    // DataPtr of the appropriate type according to its template
    // parameter
    Informer<string>::DataPtr s(new string("example payload"));

    // Send the data.
    informer->publish(s);

    return EXIT_SUCCESS;
}

Download this example

A rsb.Informer object is created by obtaining the RSB factory via rsb.Factory.getInstance and then calling its rsb.Factory.createInformer method with the desired scope (which can be specified as a string literal). The generic parameter of the rsb.Informer class determines the data type of the informer.

The rsb.Informer has to activated before and deactivated after use via the rsb.Informer.activate and rsb.Informer.deactivate methods.

Once the informer has been created and activated, data is published by calling rsb.Informer.send.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package rsb.examples;

import rsb.Factory;
import rsb.Informer;

public class InformerExample {

    public static void main(final String[] args) throws Throwable {

        // Get a factory instance to create RSB objects.
        final Factory factory = Factory.getInstance();

        // Create an informer on scope "/exmaple/informer".
        final Informer<Object> informer = factory
                .createInformer("/example/informer");

        // Activate the informer to be ready for work
        informer.activate();

        // Send and event using a method that accepts the data and
        // automatically creates an appropriate event internally.
        informer.publish("example payload");

        // As there is no explicit removal model in java, always manually
        // deactivate the informer if it is not needed anymore
        informer.deactivate();

    }

}

Download this example

The macro rsb:with-participant can be used to create an informer for a particular scope and data type (which can be cl:t). The method rsb:send can then be used to send data. rsb:with-participant takes care of destroying the informer after use.

1
2
3
4
(rsb:with-participant (informer :informer "/example/informer"
                                :type 'string)
  (format t "Sending first event~%")
  (rsb:send informer "example payload"))

Alternatively, rsb:make-participant can be used to obtain an informer without automatic destruction:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
(defvar *informer* (rsb:make-participant :informer "/example/informer"
                                         :type 'string))

(format t "Sending second event~%")
(rsb:send *informer* "example payload")

;; The informer will participate in the channel until it is garbage
;; collected or explicitly detached using the `rsb:detach' function.

(rsb:detach *informer*)

Download this example

Receiving Data

Receiving data can be performed in two different ways in RSB:

Synchronous

Wait until events are received.

Asynchronous

Continue execution and execute a callback function (called handler in RSB) when events are received.

The following two sections explain the two ways of receiving data.

Receiving Data Synchronously

To receive data synchronously, a reader object has to be created for the scope from which events should be received. Then, individual events have to be retrieved explicitly from the reader object, hence synchronous receiving.

Note

Synchronous receiving of data is not currently implemented in Python.

A reader is created by obtaining the RSB factory via rsb::Factory::getInstance (line 16) and then calling its rsb::Factory::createReader method with the desired scope (which can be specified as std::string object, for example, a string literal, line 17).

Once the reader has been created, individual events are received by calling the rsb::Reader::read method (line 21).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <stdlib.h>

#include <iostream>

#include <rsc/misc/SignalWaiter.h>

#include <rsb/Factory.h>

using namespace rsb;

int main(int argc, char** argv) {

    rsc::misc::initSignalWaiter();

    // Set up the scope to receive on either from the command line
    // argument or use the default scope of the informer example.
    Scope scope(argc > 1 ? argv[1] : "/example/informer");

    // Create a reader which synchronously receives events on the
    // specified scope.
    Factory& factory = getFactory();
    ReaderPtr reader = factory.createReader(scope);

    // Print events as they are received.
    while (rsc::misc::lastArrivedSignal() == rsc::misc::NO_SIGNAL) {
        EventPtr event = reader->read();
        std::cout << event << std::endl;
    }

    return rsc::misc::suggestedExitCode(rsc::misc::lastArrivedSignal());

}

Download this example

Note

Synchronous receiving of data is not currently implemented in Java.

The macro rsb:with-participant can be used to create a reader for a particular scope. The method rsb:receive can then be used to receive individual events data. rsb:with-participant takes care of destroying the reader after use.

1
2
3
4
(rsb:with-participant (reader :reader "/example/informer")
  (let ((event (rsb:receive reader)))
    (format t "Received event: ~A~%" event)
    event)) ; return the event

Alternatively, rsb:make-participant can be used to obtain a reader without automatic destruction:

1
2
3
4
5
6
(defvar *reader* (rsb:make-participant :reader "/example/informer"))

;; mark-start::receive/block
(let ((event (rsb:receive *reader* :block? t))) ; block? defaults to t
  (format t "Received event: ~A~%" event)
  event) ; return the event

Download this example

Receiving Data Asynchronously

To receive data asynchronously, a listener object has to be created for the scope from which events should be received. Then, individual events are received automatically and in parallel to the execution of the program. For each received event, a user-supplied callback function (a handler in RSB terminology) is executed to process the event.

A rsb.Listener object is created by calling rsb.createListener() with the desired scope (which can be specified as str object, for example, a string literal, line 16)

Once the listener has been created, handlers can be added by calling rsb.Listener.addHandler() (line 20). Any callable() can be used as a handler.

Note

The context manager protocol implementation of RSB takes care of correctly deactivating the listener at the end of the with statement. In case you are not using a with statement, the rsb.Listener object has to be deactivated using its rsb.Listener.deactivate() method at the end of use.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import time
import logging

import rsb


def handle(event):
    print("Received event: %s" % event)

if __name__ == '__main__':
    # Pacify logger.
    logging.basicConfig()

    # Create a listener on the specified scope. The listener will
    # dispatch all received events asynchronously to all registered
    # handlers.
    with rsb.createListener("/example/informer") as listener:

        # Add a handler to handle received events. Handlers are callable
        # objects with the received event as the single argument.
        listener.addHandler(handle)

        # Wait for events; clean up when interrupted.
        while True:
            time.sleep(1)

Download this example

A listener is created by obtaining the RSB factory via rsb::Factory::getInstance (line 19) and then calling its rsb::Factory::createListener method with the desired scope (which can be specified as std::string object, for example, a string literal, line 27).

Once the listener has been created, individual handlers can be added by calling the rsb::Listener::addHandler method (line 36). In general, handlers are objects which implement the rsb::Handler interface. However, there are specialized handlers such as rsb::DataFunctionHandler which allow using different things such as ordinary functions as handlers.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <stdlib.h>

#include <iostream>

#include <rsc/misc/SignalWaiter.h>

#include <rsb/Handler.h>
#include <rsb/Listener.h>
#include <rsb/Factory.h>

using namespace rsb;

void printData(boost::shared_ptr<std::string> e) {
    std::cout << "Received event: " << *e << std::endl;
}

int main(int argc, char** argv) {

    rsc::misc::initSignalWaiter();

    // First get a factory instance that is used to create RSB
    // objects.
    Factory& factory = getFactory();

    // Set up the scope to receive on either from the command line
    // argument or use the default scope of the informer example.
    Scope scope((argc > 1) ? argv[1] : "/example/informer");

    // Create a listener that asynchronously receives events from the
    // bus and dispatches them to registered handlers.
    ListenerPtr listener = factory.createListener(scope);

    // Add a handler that is notified about every new event.  This
    // time a special handler instance is used that wraps a function
    // pointer of a function that is only interested in the received
    // data contained in the event and not the additional meta data
    // provided by the event instance. Other handlers exist that also
    // receive Event instances, either as class instances or by
    // wrapping function pointers.
    listener->addHandler(HandlerPtr(new DataFunctionHandler<std::string> (&printData)));

    // As events are received asynchronously we have to wait here for
    // them.
    return rsc::misc::suggestedExitCode(rsc::misc::waitForSignal());

}

Download this example

A rsb.Listener object is created by obtaining the RSB factory via rsb.Factory.getInstance (line 15) and then calling its rsb.Factory.createListener method with the desired scope (which can be specified as a string literal, line 20).

The rsb.Listener has to activated before and deactivated after use via the rsb.Listener.activate (line 21) and rsb.Listener.deactivate (line 34) methods.

Once the listener has been created and activated, handlers can be added by calling the rsb.Listener.addHandler method (line 26). Objects implementing the rsb.Handler interface can be used as handlers.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package rsb.examples;

import rsb.AbstractEventHandler;
import rsb.Event;
import rsb.Factory;
import rsb.Listener;

public class EventListenerExample extends AbstractEventHandler {

    @Override
    public void handleEvent(final Event event) {
        System.out.println("Received event " + event.toString());
    }

    public static void main(final String[] args) throws Throwable {
        // Get a factory instance to create new RSB objects.
        final Factory factory = Factory.getInstance();

        // Create a Listener instance on the specified scope that will
        // receive events and dispatch them asynchronously to all
        // registered handlers; activate the listener.
        final Listener listener = factory.createListener("/example/informer");
        listener.activate();

        try {
            // Add an EventHandler that will print events when they
            // are received.
            listener.addHandler(new EventListenerExample(), true);

            // Wait for events.
            while (true) {
                Thread.sleep(1);
            }
        } finally {
            // Deactivate the listener after use.
            listener.deactivate();
        }
    }

}

Download this example

The macro rsb:with-participant can be used to create a listener for a particular scope. Inside the lexical scope of rsb:with-participant (or for listeners created differently), the macro rsb:with-handler can be used to add a handler to the listener. While the body of rsb:with-handler executes, events are handled by the supplied code.

1
2
3
4
5
6
(rsb:with-participant (listener :listener "/example/informer")
  (rsb:with-handler listener
      ((event)
       (format t "Received event: ~A~%" event))
    (format t "Waiting for events~%")
    (sleep 20)))

Alternatively, rsb:make-participant can be used to obtain a listener without automatic destruction:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
(defvar *listener* (rsb:make-participant :listener "/example/informer"))

;; Just after creation, the listener will not act upon received
;; events. In order to process received events, handlers have to be
;; added to the listener. A handler is a function of one argument, the
;; event.

(push (lambda (event)
        (format t "Received event: ~A~%" event))
      (rsb.ep:handlers *listener*))

;; The listener will participate in the channel until it is garbage
;; collected or explicitly detached using the `rsb:detach' function.

(rsb:detach *listener*)

Download this example

Remote Procedure Calls

See also

Request-Reply Communication
For a detailed description of the underlying implementation.

Remote procedure calls (RPCs) execute methods of objects located in different processes, and potentially different computers, than the calling entity. Some things are easier to implement using RPCs than using events. However, using RPCs generally makes a system less flexible and often more error-prone. RSB includes means for providing and using a simple form of remote procedure calls.

The following two sections describe

Client

The RPC client calls methods provided by one or more RPC servers. In RSB, such an RPC client is implemented as a remote server object which is similar to other participants. Such an object has to be created in order to perform method calls.

After the remote server object has been created, a method can be called by supplying its name as string and, optionally, the parameter (there are only one or zero parameters). Methods can be called in blocking and non-blocking way:

  • When called in a blocking way, the method call returns only after the server has processed the request and returned a result.
  • When called in a non-blocking way, the method call returns immediately and the result can be obtained later, when the server completes its processing.

Important

When a non-existent method is called (for example, because the name of the method has been misspelled), nothing happens: blocking calls block forever and non-blocking calls never provide a result.

Conversely, if a method is provided by multiple servers, all servers process the request but only one reply is returned to the caller. It is unspecified, which reply is received by the caller, in such a situation.

A rsb.patterns.RemoteServer object is created by calling rsb.createRemoteServer() with the scope on which the service is provided (line 12). Remote methods can then be called on the rsb.patterns.RemoteServer object as if they were ordinary Python methods using the function call syntax OBJECT.METHOD(ARGUMENTS) (see line 17). Asynchronous calls can be made by using the syntax OBJECT.METHOD.async(ARGUMENTS) (see line 20).

Note

The context manager protocol implementation of RSB takes care of correctly deactivating the remote server at the end of the with statement. In case you are not using a with statement, the rsb.patterns.RemoteServer object has to be deactivated using its rsb.patterns.RemoteServer.deactivate() method at the end of use.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import logging

import rsb

if __name__ == "__main__":
    # Pacify logger.
    logging.basicConfig()

    # Create a RemoteServer object for the remote server at scope
    # /example/server. Method calls should complete within five
    # seconds.
    with rsb.createRemoteServer('/example/server') as server:

        # Call the method 'echo' on the remote server passing it a
        # string argument. The server's reply is returned from the call as
        # for a regular function call.
        print('server replied to synchronous call: "%s"' % server.echo('bla'))

        # Call the method 'echo' again, this time asynchronously.
        future = server.echo.async('bla')
        # do other things
        print('server replied to asynchronous call: "%s"'
              % future.get(timeout=10))

Download this example

A rsb::patterns::RemoteServer object is created by calling rsb::Factory::createRemoteServer with the scope on which the service is provided (lines 12 and 13). Remote methods can then be called using the rsb::patterns::RemoteServer::call method (see line 21) and the rsb::patterns::RemoteServer::callAsync method (see lines 30 to 36). The expected return type is specified as a template argument to the function call while the argument type is derived from the supplied argument.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <stdlib.h>

#include <rsb/Factory.h>

using namespace rsb;
using namespace rsb::patterns;

int main(int /*argc*/, char** /*argv*/) {
    // Use the RSB factory to create a RemoteServer instance for the
    // server at scope /example/server.
    Factory& factory = getFactory();
    RemoteServerPtr remoteServer
        = factory.createRemoteServer("/example/server");

    // Call the method "echo", passing it a string value as argument
    // and accepting a string value as result. Note that the types of
    // arguments and return values are defined by the server providing
    // the respective methods and have to be matched in method calls.
    boost::shared_ptr<std::string> request(new std::string("bla"));
    boost::shared_ptr<std::string> result
        = remoteServer->call<std::string>("echo", request);
    std::cout << "Server replied: " << *result << std::endl;

    // Call the method "echo" without waiting for the call to return a
    // result: instead of a result, a "future" object is returned,
    // from which the actual result can be obtained at a later point
    // in time. In this example, the future.get(10) call may block for
    // up to 10 seconds and throw an exception if a result is not
    // received within that time.
    RemoteServer::DataFuture<std::string> future
        = remoteServer->callAsync<std::string>("echo", request);

    // We could do something else here while the server processes the
    // call.

    std::cout << "Server replied: " << *future.get(10.0) << std::endl;
    // Note: timeout is in seconds.

    // Also call all the other methods once
    remoteServer->call<void>("void");
    remoteServer->call<void>("void2");
    {
        boost::shared_ptr<std::string> request(new std::string("bla"));
        boost::shared_ptr<std::string> result = remoteServer->call<std::string>(
                "echo2", request);
        std::cout << "Server replied: " << *result << std::endl;
    }
    {
        boost::shared_ptr<std::string> result = remoteServer->call<std::string>(
                "voidArg");
        std::cout << "Server replied: " << *result << std::endl;
    }
    {
        boost::shared_ptr<std::string> request(new std::string("bla"));
        remoteServer->call<void>("voidReturn", request);
    }

    return EXIT_SUCCESS;
}

Download this example

A rsb.patterns.RemoteServer object is created by calling rsb.Factory.createRemoteServer with the scope on which the service is provided (line 10). Remote methods can then be called using the rsb.patterns.RemoteServer.call method (see line 18) and the rsb.patterns.RemoteServer.callAsync method (see lines 20 and 21).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import rsb.Factory;
import rsb.patterns.RemoteServer;

public class ClientExample {

    public static void main(final String[] args) throws Throwable {
        // Get remote server object to call exposed request methods of
        // participants
        final RemoteServer server = Factory.getInstance().createRemoteServer(
                "/example/server");
        server.activate();

        // Call remote method in blocking and non-blocking fashion and
        // deactivate the server.
        try {

            System.out.println("Server replied: " + server.call("echo", "bla"));

            final Future<String> future = server.callAsync("echo", "bla");
            System.out.println("Server replied: "
                    + future.get(10, TimeUnit.SECONDS));

        } finally {
            server.deactivate();
        }
    }

}

Download this example

A remote server can be created and managed with the rsb:with-participant macro. The rsb.patterns.request-reply:call method can be used on the remote server object to call remote methods. The method name and the argument of the call have to be passed as the second and third argument respectively.

1
2
3
(rsb:with-participant (remote-server :remote-server "/example/clientserver")
  (format t "Server replied: ~A~%"
          (rsb.patterns.request-reply:call remote-server "echo" "bla")))

Alternatively, rsb:make-participant can be used to obtain a remote server without automatic destruction:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
(defvar *remote-server* (rsb:make-participant :remote-server
                                              "/example/clientserver"))

(rsb.patterns.request-reply:call *remote-server* "echo" "bla")

;; The remote server will remain connected to the bus until it is
;; garbage collected or explicitly detached using the `rsb:detach'
;; function.

(rsb:detach *remote-server*)

Blocking and non-blocking calls are both performed by calling rsb.patterns.request-reply:call. The :block? keyword parameter controls blocking. :block? nil causes a future object to be returned from which the result can be obtained via rsb.patterns.request-reply:future-result at a later point in time.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
(rsb:with-participant (remote-server :remote-server "/example/clientserver")

  ;; The default behavior of returning the reply payload can be
  ;; changed using the :return keyword parameter.
  (rsb.patterns.request-reply:call remote-server "echo" "bla"
                                   :return :event)

  ;; Non-blocking calls can be made using the :block? keyword
  ;; parameter. In that case, an object implementing the future
  ;; protocol is returned to represent the result of the computation.
  (let ((future (rsb.patterns.request-reply:call remote-server "echo" "bla"
                                                 :block? nil)))
    (rsb.patterns.request-reply:future-result future))

  ;; These behaviors can be combined:
  (let ((future (rsb.patterns.request-reply:call remote-server "echo" "bla"
                                                 :block? nil
                                                 :return :event)))
    (rsb.patterns.request-reply:future-result future)))

Download this example

Server

Methods which are callable via RPC are provided by local server objects which are similar to other participants. To provide such methods a local server object has be created.

After the local server object has been created, methods have to be registered, supplying the desired method name as a string and a callback function which implements the desired behavior of the method.

A rsb.patterns.LocalServer object is created by calling rsb.createLocalServer() with the scope on which the service is provided (line 12). Methods with their request and reply data types and the callable() s implementing their behavior are registered using the rsb.patterns.LocalServer.addMethod() method (line 21).

Note

The context manager protocol implementation of RSB takes care of correctly deactivating the local server at the end of the with statement. In case you are not using a with statement, the rsb.patterns.LocalServer object has to be deactivated using its rsb.patterns.LocalServer.deactivate() method at the end of use.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import time
import logging

import rsb

if __name__ == '__main__':
    # Pacify logger.
    logging.basicConfig()

    # Create a LocalServer object that exposes its methods under the
    # scope /example/server.
    with rsb.createLocalServer('/example/server') as server:

        # Create a function which processes requests and returns a
        # result. Note that the name of the function does not determine
        # the name of the exposed method. See addMethod below.
        def echo(x):
            return x

        # Add the function to the server under the name "echo".
        server.addMethod('echo', echo, str, str)

        # Wait for method calls by clients.
        while True:
            time.sleep(1)

Download this example

A rsb::patterns::Server object is created by calling rsb::Factory::createServer with the scope on which the server should provide its service (line 20). Methods and the callback objects implementing their behavior can be registered using the rsb::patterns::LocalServer::registerMethod method (see line 23). Callback classes are derived from rsb::patterns::Server::Callback (with template arguments specifying the request and reply data types) and override the rsb::patterns::Server::Callback::call method (see lines 8 to 14).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include <boost/thread.hpp>

#include <rsc/misc/SignalWaiter.h>

#include <rsb/Factory.h>

using namespace rsb;
using namespace rsb::patterns;

class EchoCallback: public LocalServer::Callback<std::string, std::string> {
public:
    boost::shared_ptr<std::string> call(const std::string& /*methodName*/,
                                        boost::shared_ptr<std::string> input) {
        return input;
    }
};

class VoidVoidCallback: public LocalServer::Callback<void, void> {
    void call(const std::string& /*methodName*/) {
        std::cout << "void-void method called" << std::endl;
    }
};

boost::shared_ptr<std::string> echoFunction(
        boost::shared_ptr<std::string> input) {
    return input;
}

void voidReturnFunction(boost::shared_ptr<std::string> input) {
    std::cout << "void function called: " << *input << std::endl;
}

boost::shared_ptr<std::string> voidArgFunction() {
    return boost::shared_ptr<std::string>(new std::string("test"));
}

void voidVoidFunction() {
    std::cout << "void-void function called" << std::endl;
}

int main(int /*argc*/, char** /*argv*/) {

    rsc::misc::initSignalWaiter();

    // Use the RSB factory to create a Server instance that provides
    // callable methods under the scope /example/server.
    Factory& factory = getFactory();
    LocalServerPtr server = factory.createLocalServer("/example/server");

    // Register method with name and implementing callback object.
    server->registerMethod("echo", LocalServer::CallbackPtr(new EchoCallback()));
    server->registerMethod("void", LocalServer::CallbackPtr(new VoidVoidCallback()));

    // The same is also possible by binding functions
    server->registerMethod("echo2",
            LocalServer::CallbackPtr(
                    new LocalServer::FunctionCallback<std::string, std::string>(
                            &echoFunction)));
    server->registerMethod("voidArg",
            LocalServer::CallbackPtr(
                    new LocalServer::FunctionCallback<void, std::string>(
                            &voidArgFunction)));
    server->registerMethod("voidReturn",
            LocalServer::CallbackPtr(
                    new LocalServer::FunctionCallback<std::string, void>(
                            &voidReturnFunction)));
    server->registerMethod("void2",
            LocalServer::CallbackPtr(
                    new LocalServer::FunctionCallback<void, void>(
                            &voidVoidFunction)));

    // Wait here so incoming method calls can be processed.
    return rsc::misc::suggestedExitCode(rsc::misc::waitForSignal());

}

Download this example

A rsb.patterns.LocalServer object is created by calling rsb.Factory.createLocalServer with the scope on which server should provide its service (line 20). Methods are registered by calling the rsb.patterns.LocalServer.addMethod method (see line 25) with a suitable callback object. The callback class supplies the behavior of server methods by overriding the rsb.patterns.EventCallback.invoke method (see lines 8 to 15).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package rsb.examples;

import rsb.Event;
import rsb.Factory;
import rsb.patterns.EventCallback;
import rsb.patterns.LocalServer;

public class ServerExample {

    public static class EchoCallback extends EventCallback {

        @Override
        public Event invoke(final Event request) throws Exception {
            return new Event(String.class, request.getData());
        }

    }

    public static void main(final String[] args) throws Throwable {
        // Get local server object which allows to expose remotely
        // callable methods.
        final LocalServer server = Factory.getInstance().createLocalServer(
                "/example/server");
        server.activate();

        // Add method an "echo" method, implemented by EchoCallback.
        server.addMethod("echo", new EchoCallback());

        // Block until server.deactivate or process shutdown
        server.waitForShutdown();
    }

}

Download this example

A local server can be created and managed with the rsb:with-participant macro. The rsb.patterns.request-reply:with-methods macro can be used to register methods and their implementations in the local server.

1
2
3
4
(rsb:with-participant (server :local-server "/example/clientserver")
  (rsb.patterns.request-reply:with-methods (server)
      (("echo" (arg string)
         arg))))

Alternatively, rsb:make-participant can be used to obtain a local server without automatic destruction. Similarly, methods can be added without the rsb.patterns.request-reply:with-methods macro:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
(defvar *local-server* (rsb:make-participant :local-server
                                             "/example/clientserver"))

(setf (rsb.patterns.request-reply:server-method *local-server* "echo")
      (lambda (arg) arg))

;; The local server and its methods will remain connected to the bus
;; until they are garbage collected or explicitly detached using the
;; `rsb:detach' function.

(rsb:detach *local-server*)

Download this example