nngpp
C++ wrapper around the nanomsg NNG API
What's nanomsg? In a nutshell it's a spiritual successor to ZeroMQ, and NNG is its latest incarnation.
Features
- Header-only -- just add the
include
directory to your includes - Supports all nng protocols, transports and supplemental code
- Zero overhead -- no virtual functions, no extra allocations or copies
- Owning RAII classes, e.g.
nng::socket
andnng::msg
- Non-owning views, e.g.
nng::socket_view
andnng::msg_view
- Failure is communicated with exceptions instead of error codes
- Compatible with C++11 but uses C++17 features when available
Hello world
#include <nngpp/nngpp.h>
#include <nngpp/protocol/req0.h>
#include <nngpp/protocol/rep0.h>
#include <cstdio>
int main() try {
// create a socket for the rep protocol
nng::socket rep_sock = nng::rep::open();
// rep starts listening using the tcp transport
rep_sock.listen( "tcp://localhost:8000" );
// create a socket for the req protocol
nng::socket req_sock = nng::req::open();
// req dials and establishes a connection
req_sock.dial( "tcp://localhost:8000" );
// req sends "hello" including the null terminator
req_sock.send("hello");
// rep receives a message
nng::buffer rep_buf = rep_sock.recv();
// check the content
if( rep_buf == "hello" ) {
// rep sends "world" in response
rep_sock.send("world");
}
// req receives "world"
nng::buffer req_buf = req_sock.recv();
}
catch( const nng::exception& e ) {
// who() is the name of the nng function that produced the error
// what() is a description of the error code
printf( "%s: %s\n", e.who(), e.what() );
return 1;
}
// req_buf is freed
// rep_buf is freed
// req_sock is closed
// rep_sock is closed
Demos
All 5 nng demos have been ported to nngpp to illustrate the use of the library.
I have kept the original structure of the demos intact, just replacing the nng API with nngpp.
This allows for easy comparison with the nng demos, but may mean they are non-idiomatic in places.
See the demo
directory.
Dependencies
Since nngpp is a wrapper around nng, you will need to link with libnng.
- If you built nng without tls support:
-lnng -lpthread
- If you built nng with tls support:
-lnng -lmbedtls -lmbedx509 -lmbedcrypto -lpthread
Design
The nng API consists of a number of conceptual objects, such as 'socket' and 'pipe'. Each of these is identified and referred to by a handle, which is typically either a pointer or a (structured) integer id.
In nngpp these handles are wrapped in views that expose all of the corresponding functionality as member functions.
For example, the nng_socket
handle is wrapped by a nng::socket_view
that has the member function dial()
, which calls the nng function nng_dial()
.
All views provide a common set of member functions:
- default constructor of null handle
- implicit construction from a given handle
get()
-- get the handleoperator->()
(for pointer handles) -- access to the handle's membersoperator bool()
-- test for the presence of a non-null handle
Most of these conceptual objects are also resources, i.e. they must be acquired and released.
To manage the ownership of these resources, nngpp provides RAII classes, such as nng::socket
.
Each RAII class is an extension of the corresponding view and has the following:
- default constructor of null handle
- explicit construction from a given handle
- specific constructors -- acquire a new handle using the given arguments
- destructor -- release the resource (if held)
- move -- transfer ownership to another instance
- copy (if possible) -- duplicate the resource
release()
-- get the underlying handle, detaching its ownership from the object
In addition to constructors, there are make functions:
auto msg1 = nng::make_msg(32); // acquire using make function
nng::msg msg2(32); // acquire using constructor
The make function is needed to acquire with zero arguments, e.g.
auto mtx1 = nng::make_mtx(); // acquire new mutex
nng::mtx mtx2; // default constructor gives null mutex
Summary
Name | nng handle | nngpp view | nngpp RAII |
---|---|---|---|
socket | nng_socket |
nng::socket_view |
nng::socket |
context | nng_ctx |
nng::ctx_view |
nng::ctx |
dialer | nng_dialer |
nng::dialer_view |
nng::dialer |
listener | nng_listener |
nng::listener_view |
nng::listener |
pipe | nng_pipe |
nng::pipe_view |
nng::pipe |
message | nng_msg* |
nng::msg_view |
nng::msg |
async i/o | nng_aio* |
nng::aio_view |
nng::aio |
url | nng_url* |
nng::url_view |
nng::url |
buffer | void*, size_t |
nng::view |
nng::buffer |
stat | nng_stat* |
nng::stat_view |
nng::stat |
thread | nng_thread* |
nng::thread_view |
nng::thread |
mutex | nng_mtx* |
nng::mtx_view |
nng::mtx |
condition variable | nng_cv* |
nng::cv_view |
nng::cv |
tls config | nng_tls_config* |
nng::tls::config_view |
nng::tls::config |
http client | nng_http_client* |
nng::http::client_view |
nng::http::client |
http connection | nng_http_conn* |
nng::http::conn_view |
nng::http::conn |
http handler | nng_http_handler* |
nng::http::handler_view |
nng::http::handler |
http request | nng_http_req* |
nng::http::req_view |
nng::http::req |
http response | nng_http_res* |
nng::http::res_view |
nng::http::res |
http server | nng_http_server* |
nng::http::server_view |
nng::http::server |
stream | nng_stream* |
nng::stream::stream_view |
nng::stream::stream |
stream dialer | nng_stream_dialer* |
nng::stream::dialer_view |
nng::stream::dialer |
stream listener | nng_stream_listener* |
nng::stream::listener_view |
nng::stream::listener |
Error handling
Many of nng's functions can fail and do so by returning an error code.
In nngpp, the wrapper will throw these error codes as an nng::exception
that has:
get_error()
-- get the error codewho()
-- name of the nng function that failedwhat()
-- description of the error
All nngpp functions that don't throw are marked noexcept
.
Failure in destructors
The following objects have destructors that can fail:
nng::socket
nng::ctx
nng::listener
nng::dialer
nng::pipe
In this case the destructor completes without throwing.
Protocol versioning
NNG has version numbers on its protocols, e.g. pair0 and pair1. This is expressed in nngpp with namespaces:
auto s0 = nng::pair::v0::open(); // version 0
auto s1 = nng::pair::v1::open(); // version 1
auto s2 = nng::pair::open(); // latest version (currently v1)
An inline namespace is used for the latest version of each protocol.
Socket options
Some objects (socket, ctx, dialer, listener, pipe) have options that can be get/set.
The nng API requires that you use the correct type of getter/setter function for each option.
For example, the receive timeout socket option (NNG_OPT_RECVTIMEO
) is of type nng_duration
and must set with nng_setopt_ms()
.
Another issue is that some options are read-only.
In both cases you will only find out at run-time if there is a problem with your code.
In nngpp every option on every object has a dedicated getter and setter of the correct type:
nng::set_opt_recv_timeout( socket, 1000 );
nng_duration timeout = nng::get_opt_recv_timeout( socket );
If an option is read-only it will have no setter -- you will know at compile time or earlier if you are trying to set a read-only option.
These dedicated getters and setters are free-standing functions, rather than members, as there are some options that are only available with certain transports. For example, with the TCP transport the socket has a keep alive option:
bool keep_alive = nng::tcp::get_opt_keep_alive( socket );