Example 3

This tutorial explores more about session and server classes, and it also shows how to set attributes of remote pointer. It introduces the items below:

  • Handling session and server events
  • Connect session and server with customized bindings.
  • Setting number of thread for global io_runner
  • Binding multiple instances of object as remote services
  • Binding target as remote service through a session object at client site
  • Change remote pointer to one-way call mode
  • Rethrowing exception pointer

We will make both server and client applications export remote interface. Server will export 2 objects as remote player and 1 object as remote air conditions service. Client will export info_service so that the server can query the name of the connected session. Let's start by defining 3 remote interfaces below which are to be included to both client and server applications.

Note: only the remote interface definition part of code is shown.

// remote_player.hpp
REMOTE_CLASS_BEGIN(remote_player)                   \
    REMOTE_METHOD_M1(void, play, std::string)       \
    REMOTE_METHOD_M0(void, stop)                    \
REMOTE_CLASS_END
// remote_ac.hpp
REMOTE_CLASS_BEGIN(remote_ac)                       \
    REMOTE_METHOD_M1(void, set_temperature, int)    \
    REMOTE_METHOD_C0(int, get_temperature)          \
REMOTE_CLASS_END
// info_service.hpp
REMOTE_CLASS_BEGIN(info_service)                    \
    REMOTE_METHOD_C0(std::string, name)             \
REMOTE_CLASS_END

Then define the service handler classes for server.

// model.hpp
class audio_player
{
public:
    void play(std::string song) { m_playing = true; }
    void stop() { m_playing = false; }
    
private:
    bool m_playing;
};

class air_cond
{
public:
    void set_temperature(int t) { m_temperature = t; }
    int get_temperature() const { return m_temperature; }

private:
    int m_temperature;
};

And the service handler class for client.

// session_info.hpp
class session_info
{
public:
    session_info(std::string name): m_name(name) {}
    std::string name() const { return m_name; }

private:
    std::string m_name;
};

In this example, the server application code will be more complicated. We will create on_start function to handle the server start event and on_accept function to handle the session accepted event. The on_start function will handle the error by rethrowing the exception held by exception_ptr in a try-catch block. The on_accept function will call the what method of the exception_ptr to get the error message directly and if there is no error, it will get the info_service remote pointer from the connected session to query the session name.

In the main function, we will set the global io runner to run in 2 threads and then start the server with customized binding.

// server.cpp
#include <remote/server.hpp>
#include <remote/global.hpp>
#include <remote/make_basic_binding.hpp>
#include <remote/bindings/binary_serializer.hpp>
#include <remote/bindings/ipc_transport.hpp>

void on_start(remote::exception_ptr e)
{
    if(!e)
    {
        std::cout << "server started" << std::endl;
        return;
    }

    // handle error by rethrowing the exception
    try
    {
        remote::rethrow_exception(e);
    }
    catch(remote::system_error& error)
    {
        std::cout << "system error: " << error.what() << std::endl;
    }
    catch(remote::remote_error& error)
    {
        std::cout << "remote error: " << error.what() << std::endl;
    }
    catch(std::exception& error)
    {
        std::cout << "other error: " << error.what() << std::endl;
    }
}

void on_accept(remote::exception_ptr e, remote::session& session)
{
    // another way to handle error is to call the e->what() directly
    if(e)
    {
        std::cout << "error when accepting a session: " << e->what() << std::endl;
        return;
    }

    try
    {
        // get info service interface from the newly connected session.
        auto info = session.get<info_service>("info");
        std::cout << "accepted a session: " << info->name() << std::endl;
    }
    catch(std::exception& error)
    {
        std::cout << "error when get session info: " << error.what() << std::endl;
    }
}

int main()
{
    // set application io runner to run 2 threads
    remote::global::io_runner(2);

    // create target objects
    audio_player walkman;
    audio_player ipod;
    air_cond room_ac;

    // create multiple instances of remote services
    // by binding target objects to their remote interfaces
    remote::server server;
    server.bind<remote_player>(&walkman, "walkman");
    server.bind<remote_player>(&ipod, "ipod");
    server.bind<remote_ac>(&room_ac, "room ac");

    // construct customized binding
    typedef remote::bindings::binary_serializer serializer;
    typedef remote::bindings::ipc_transport transport;
    auto binding = remote::make_basic_binding<serializer, transport>("tutorial");

    // setting event handlers and start the server
    server.set_accept_handler(on_accept);
    server.start(binding, on_start);

    std::cin.get();
    return 0;
}
// client.cpp
#include <remote/session.hpp>
#include <remote/attribute.hpp>
#include <remote/global.hpp>
#include <remote/make_basic_binding.hpp>
#include <remote/bindings/binary_serializer.hpp>
#include <remote/bindings/ipc_transport.hpp>

void on_start(remote::exception_ptr e)
{
    // handle session start event here
}

void on_stop(remote::exception_ptr e)
{
    // handle session stop event here
}

void on_error(remote::exception_ptr e)
{
    // handle io error event here
}

int main()
{
    // set application io runner to run 2 threads
    remote::global::io_runner(2);

    // bind info service to session
    remote::session session;
    session_info info("My Session");
    session.bind<info_service>(&info , "info");

    // construct customized binding
    typedef remote::bindings::binary_serializer serializer;
    typedef remote::bindings::ipc_transport transport;
    auto binding = remote::make_basic_binding<serializer, transport>("tutorial");

    // set event handler and start the session
    session.set_error_handler(on_error);
    session.start(binding, on_start);
    if(session.wait_for_ready() != remote::session::started)
        return -1;

    // get remote pointer
    auto ac = session.get<remote_ac>("room ac");
    auto ipod = session.get<remote_player>("ipod");
    auto walkman = session.get<remote_player>("walkman");

    // use ac
    ac->set_temperature(25);

    // use walkman
    walkman->play("jazz");      // this is 2 way call

    // switch to one way mode
    remote::attribute::set_one_way(ipod, true);
    ipod->play("pop rock");     // this is one way call

    // switch back to normal mode
    remote::attribute::set_one_way(ipod, false);
    ipod->stop();               // this is 2 way call

    // stop the session and set event handler
    session.stop(on_stop);

    return 0;
}
Next Example