A simple header-only library for supporting remote procedure calls using a variety of extensible features.
rpc.hpp
is licensed under the BSD 3-Clause License.
- Cross-platform.
- Supports every major compiler/operating system.
- Easy to use.
- Only requires a few lines of code to provide the implementation of your client or server.
- Modern.
- Takes advantage of a lot of compile-time and generic programming features such as C++17's
constexpr if
.
- Takes advantage of a lot of compile-time and generic programming features such as C++17's
- Fast.
- See COMPARISON to see how this library compares to the likes of
librpc
andgRPC
.
- See COMPARISON to see how this library compares to the likes of
- Support for various types.
std::string
/std::vector
supported out of the box (more STL containers to come).- Users can create serialization member functions for their own custom types.
- Users can also provide
template
methods for serializing types outside of their control
- Users can also provide
- Extensible support via "adapters".
- Currently supported:
- Pointers and arrays (other than
const char*
/const char[]
for strings) cannot be passed across an RPC boundary. - Functions with default arguments should not be used with remote procedure calls as the default argument(s) must be provided each time anyway.
- Function overloads will not work across an RPC boundary as the server will have an
ambiguous call.
- This includes non-explicit template functions (see examples for the syntax for calling template functions remotely).
- Not (statically) type-safe across the RPC boundary.
- This is the nature of a dynamic system like remote procedure calls.
- Return type and explicit parameters must be specified at call site.
- Exceptions will be able to catch function signature mismatches.
- Function signatures can be provided via a shared header to allow for strict compile-time
type-checking (by using
call_header_func()
).
See Doxygen docs here.
For more examples see examples.
server.cpp
#define RPC_HPP_SERVER_IMPL
#include <rpc_adapters/rpc_njson.hpp>
#include <string>
using rpc_hpp::adapters::njson;
using rpc_hpp::adapters::njson_adapter;
int Add(int n1, int n2)
{
return n1 + n2;
}
void AppendStr(std::string& str, const char* append)
{
str.append(append);
}
class RpcServer : public rpc_hpp::server_interface<njson_adapter>
{
public:
RpcServer(const char* address)
{
// initialize server...
}
// ...
void Run()
{
std::string data;
// Get data from client...
// Sink data into the dispatch function to get result data
auto result_data = dispatch(std::move(data));
// Send result data back to client...
}
};
int main()
{
RpcServer my_server{"address"};
my_server.bind("Add", &Add);
my_server.bind("AppendStr",
[](std::string& str, const std::string& append) {
AppendStr(str, append.c_str());
}
);
while (true)
{
my_server.Run();
}
}
client.cpp
#define RPC_HPP_CLIENT_IMPL
#include <rpc_adapters/rpc_njson.hpp>
#include <cassert>
#include <string>
using rpc_hpp::adapters::njson_adapter;
class RpcClient : public rpc_hpp::client_interface<njson_adapter>
{
public:
RpcClient(const char* address)
{
// initialize client...
}
// ...
private:
void send(const std::string& mesg) override
{
// Send mesg to server...
}
void send(std::string&& mesg) override
{
// Send mesg to server...
}
std::string receive() override
{
// Get message back from server...
}
};
int main()
{
RpcClient my_client{ "address" };
const auto result = my_client.template call_func<int>("Sum", 1, 2);
assert(result == 3);
std::string str{ "Hello" };
my_client.call_func("AppendStr", str, " world!");
assert(str == "Hello world!");
}