Cookbook

Welcome to the Cookbook! This is a collection of short examples that allows you to quickly learn the ins and outs of rpclib. This guide is written in the spirit of "less talk and more code" (it's mostly only the titles, but also look out for the comments, they contain important information). If you prefer detailed instructions and explanation, you might want to start with the Primer.

Server examples

Creating a server

#include "rpc/server.h"

int main() {
    rpc::server srv(8080); // listen on TCP port 8080

    srv.run(); // blocking call
    return 0;
}

Binding (exposing) free functions

#include "rpc/server.h"
#include <iostream>

void foo() {
    std::cout << "Hey, I'm a free function." << std::endl;
}

int main() {
    rpc::server srv(8080); // listen on TCP port 8080

    srv.bind("foo", &foo);

    srv.run(); // blocking call, handlers run on this thread.
    return 0;
}

Binding lambdas

#include "rpc/server.h"

int main() {
    rpc::server srv(8080); // listen on TCP port 8080

    srv.bind("hl3", []() {
        std::cout << "Hey, I'm a lambda!" << std::endl;
    });

    srv.run(); // blocking call
    return 0;
}

Binding member functions

Consider this class:

class foo_class {
public:
    void quaz() {
        std::cout << "Hey, I'm a member function!" << std::endl;
    }
};
Version 1: Binding through a lambda (preferred)
#include "rpc/server.h"

int main() {
    rpc::server srv(8080); // listen on TCP port 8080
    foo_class foo_obj;

    srv.bind("quaz", [&foo_obj](){ foo_obj.quaz(); });

    srv.run(); // blocking call

    // NOTE: you have to make sure that the lifetime of foo_obj
    // exceeds that of the server.

    return 0;
}
Version 2: using std::bind
#include <functional>

#include "rpc/server.h"

int main() {
    rpc::server srv(8080); // listen on TCP port 8080
    foo_class foo_obj;

    std::function<void()> functor{std::bind(&foo_class::quaz, &foo_obj)};

    srv.bind("quaz", functor);

    srv.run(); // blocking call

    // NOTE: you have to make sure that the lifetime of foo_obj
    // exceeds that of the server.

    return 0;
}

Multiple worker threads

#include "rpc/server.h"
#include <iostream>

void foo() {
    std::cout << "Hey, I'm a free function." << std::endl;
}

int main() {
    rpc::server srv(8080); // listen on TCP port 8080

    srv.bind("foo", &foo);

    constexpr size_t thread_count = 8;

    srv.async_run(thread_count); // non-blocking call, handlers execute on one of the workers

    std::cin.ignore();
    return 0;
}

Binding using custom types

#include "rpc/server.h"

struct custom_type {
    int x;
    double y;
    std::string str;
    MSGPACK_DEFINE_ARRAY(x, y, str); // or MSGPACK_DEFINE_MAP
};

int main() {
    rpc::server srv(8080); // listen on TCP port 8080

    srv.bind("cool_function", [](custom_type const& c) {
        std::cout << "c = { " << c.x << ", "
                  << c.y << ", " << c.str << "}" << std::endl;
    });

    srv.run(); // blocking call
    return 0;
}

Responding with errors

#include "rpc/server.h"
#include "rpc/this_handler.h"

int main() {
    rpc::server srv(8080); // listen on TCP port 8080

    srv.bind("error", []() {
        auto err_obj = std::make_tuple(13, "Errors are arbitrary objects");
        rpc::this_handler().respond_error(err_obj);
    });

    srv.run(); // blocking call
    return 0;
}

Responding with arbitrary objects

Even though C++ isn't, msgpack-rpc is very lenient on types. Your client might be implemented in a dynamic language.

#include "rpc/server.h"
#include "rpc/this_handler.h"

int main() {
    rpc::server srv(8080); // listen on TCP port 8080

    srv.bind("oh", [](bool quirky) -> std::string {
        if (quirky) {
            rpc::this_handler().respond(5);
        }
        return "I'm not quirky.";
    });

    srv.run(); // blocking call
    return 0;
}

Disabling response

This prevents the server from ever writing a response to a particular call.

#include "rpc/server.h"
#include "rpc/this_handler.h"

int main() {
    rpc::server srv(8080); // listen on TCP port 8080

    srv.bind("plz_respond", [](bool nice) {
        if (nice) {
            rpc::this_handler().disable_response();
        }
        return "ok";
    });

    srv.run(); // blocking call
    return 0;
}

Exiting a session

A session represents a client connection on the server. All ongoing writes and reads are completed first.

#include "rpc/server.h"
#include "rpc/this_session.h"

int main() {
    rpc::server srv(8080); // listen on TCP port 8080

    srv.bind("exit", []() {
        rpc::this_session().post_exit(); // post exit to the queue
    });

    srv.run(); // blocking call
    return 0;
}

post_exit will add exiting the session as a work item to the queue. This means that exiting is not instantenous. The TCP connection will be closed gracefully.

Stopping a server

To gracefully stop all sessions on the server. All ongoing writes and reads are completed first.

#include "rpc/server.h"
#include "rpc/this_server.h"

int main() {
    rpc::server srv(8080); // listen on TCP port 8080

    srv.bind("stop_server", []() {
        rpc::this_server().stop();
    });

    srv.run(); // blocking call
    return 0;
}

Storing per-session data

#include <unordered_map>
#include "rpc/server.h"
#include "rpc/this_session.h"

int main() {
    rpc::server srv(8080); // listen on TCP port 8080
    std::unordered_map<rpc::session_id_t, std::string> data;

    srv.bind("store_me_maybe", [](std::string const& value) {
        auto id = rpc::this_session().id();
        data[id] = value;
    });

    srv.run(); // blocking call
    return 0;
}

Client examples

Creating a client

#include "rpc/client.h"

int main() {
    rpc::client c("127.0.0.1", 8080);

    // client initiates async connection upon creation
    return 0;

    // destructor of client disconnects
}

Calling functions

#include "rpc/client.h"

int main() {
    rpc::client c("127.0.0.1", 8080);

    // client initiates async connection upon creation

    // call blocks until:
    // - connection is established
    // - result is read
    c.call("foo", 2, 3.3, "str");

    return 0;
}

Getting return values

#include "rpc/client.h"

int main() {
    rpc::client c("127.0.0.1", 8080);
    int a = c.call("add", 2, 3).as<int>();

    return 0;
}

Calling functions asynchronously

#include "rpc/client.h"

int main() {
    rpc::client c("127.0.0.1", 8080);

    auto a_future = c.async_call("add", 2, 3); // non-blocking, returns std::future

    std::cout << "I can do something here immediately" << std::endl;

    int a = a_future.get().as<int>(); // possibly blocks if the result is not yet available

    return 0;
}

Querying the connection state

#include "rpc/client.h"

int main() {
    rpc::client c("127.0.0.1", 8080);
    client::connection_state cs = c.get_connection_state();

    return 0;
}

Applying a timeout to synchronous calls

#include "rpc/client.h"

int main() {
    rpc::client c("127.0.0.1", 8080);

    try {
        // default timeout is 5000 milliseconds
        const uint64_t short_timeout = 50;
        client.set_timeout(short_timeout);
        client.call("sleep", short_timeout + 10);
    } catch (rpc::timeout &t) {
        // will display a message like
        // rpc::timeout: Timeout of 50ms while calling RPC function 'sleep'
        std::cout << t.what() << std::endl;
    }

    return 0;
}

Where to go from here

If you want to know even more about rpclib, look behind the abstractions in the Internals chapter which explains the internal workings and design decisions.