Copyright © 2025 Staysail Systems, Inc.
San Marcos, California, United States of America

If you find this book or NNG useful, please consider sponsoring the project.

tip

This book is a work in progress, adapted from earlier sources and updated for the current version of NNG.

note

This document is supplied under the terms of the MIT License, a copy of which should be located in the distribution where this file was obtained (LICENSE.txt), and reproduced below.

Third party contributions to this document were made under the same license terms, and may be covered under one of the following copyrights:

  • Copyright 2018 Capitar IT Group BV.
  • Copyright 2019 Devolutions.
  • Copyright 2020 Dirac Research.

The MIT License

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Dedication

For Jenichka

The sun always shines when you are home.

Preface

important

This is a DRAFT version of this reference manual, and much is still in progress. There may be errors, and large omissions as content is still be moved from the previous ASCIIDOCTOR format and updated for NNG 2.0.

Preface for the First Edition

At the time of this writing, we are wrapping up NNG for its formal 1.0.0 release. It’s a good time for reflection on the road that we took to get here. Like the road on the cover of this book, it was windy (if quite a bit longer), but what we find at the end has made the journey worthwhile.

Originally the NNG project was conceived as a relatively modest effort to rewrite nanomsg based on threads, with a more readily extensible internal architecture so that we could more easily undertake projects like the ZeroTier and TLS transports.

It would not be incorrect to say that the initial NNG effort was started in “anger”, as we were frustrated with nanomsg’s very complex internal state machines. Looking back on it now, those complex state state machines don’t seem nearly as insane as they did just a year ago.

The simple, naïve, approach we would have preferred, and the one we originally started with, involved significant use of threads, inspired by the work we did in mangos, which uses Go’s goroutines heavily. Goroutines are excellent. Threads, it turns out, are not. Scalable, asynchronous, portable I/O is a lot harder than it looks.

Our experience with in-kernel threads on illumos and Solaris spoiled us, and left us utterly unprepared for cesspool that really is large amounts of userspace programming.

Instead, we have created our own, completely asynchronous core, giving us advanced multiprocessing and concurrency capabilities, without either sacrificing portability or settling for some unhappy least common denominator. This core is a robust foundation for NNG and handling the “Scalability Protocols”, but if we’re being completely honest, we think this core has braod applicability for beyond just the Scalability Protocols. It will be interesting to see if others come to the same conclusion.

Builting upon this robust foundation, we have engineered a substantial project, with capabilities far in exceess of the original nanomsg, while still preserving compatibility with the the network protocols that form the backbone of the nanomsg ecosystem, and even a compatible programming interface for nanomsg library users. In addition to compatibility with nanomsg, we find that NNG has greatly increased scalability, reliability, and usability (especially when developing concurrent applications).

NNG also has complete HTTP server and client implementations, support for TLS, and a plethora of other capabilities. Much of this is made possible by a the aforementioned asynchronous I/O framework.

We’ve tried to stay true to the core nanomsg goals about being light-weight, liberally licensed, and implemented in C. (After all, those were the things that drew us to nanomsg in the first place!) In addition we added a couple of new ones. Specifically, reliability, performance, and extensibility (in that order) were added as core goals for the project.

We believe that NNG represents a substantial step forward over other messaging frameworks, and have enjoyed creating it. We hope you find it useful. There is still a lot more we want to do, and future release of NNG will continue to expand it’s capabilities. We’re just getting started.

— Garrett D’Amore, May 30, 2018

Acknowledgements

We would like to thank Janjaap Bos, at Capitar IT Group BV. Without his patronage, neither NNG nor this book would be possible.

We would also like thank Martin Sústrik for creating the original nanomsg project, the foundation upon which all of this work is based.

And certainly not least of all, we would like to thank the various members of the community who have followed and supported the NNG project in so many different ways.

Conventions

Throughout this book there are occasional warnings, notices, and tips. These are visually distinguished as follows:

tip

Tips are things that the reader may find useful, such as suggestions for use or tim saving hints.

note

Notes are things that the reader should be aware of, and provide additional information or context that may aid in the understanding or use of the topic.

important

Warnings are used to denote important cautionary advice, which should be carefully heeded. Ignoring such advice may lead to crashses, unexpected behavior, loss of revenue, or other undesirable conditions.

API Reference

This section is a reference guide for the NNG programming interfaces. It is meant to serve as a reference, rather than as a tutorial.

The material here is organized by major areas of functionality.

Note that unless indicated otherwise, consumers of these interfaces must include the nng/nng.h header file like so:

#include <nng/nng.h>

Sections

Initialization & Finalization

This chapter details the function used to initialize the library before first use, and the funtion used to finalize the library and deallocate any resources used by the library.

Initialization

typedef struct {
    int16_t num_task_threads;
    int16_t max_task_threads;
    int16_t num_expire_threads;
    int16_t max_expire_threads;
    int16_t num_poller_threads;
    int16_t max_poller_threads;
    int16_t num_resolver_threads;
} nng_init_params;

extern nng_err nng_init(nng_init_parms *params);

Before using other interfaces in this library, it is necessary to initialize the library. The nng_init function performs this initialization.

The function is idempotent, although on tear down, every call to nng_init must be paired with a call to nng_fini or no resources will be released. This allows for libraries consuming these interfaces to safely initialize and finalize the library without disrupting other consumers in the same process.

Further, only the first call to this function may have a value of params other than NULL. If params is not NULL, and the library has already been intiazed, then nng_init will return NNG_EBUSY.

In some cases it is desirable to tune certain runtime parameters for the library, which can be done by supplying a non-NULL params argument.

Parameters

The individual fields of the nng_init_params structure can be used to adjust certain runtime tunables for the library. There is no guarantee that these tunables are implemented, and applications should not depend upon them for correct operation.

Any member of nng_init_params that is set to zero will be ignored, and any built in default will be used instead for that value.

note

Applications should make sure that structure is zero initialized before calling nng_init.

The following parameters are present:

  • num_task_threads and max_task_threads
    Configures the number of threads to use for tasks, which are used principally for completion callbacks. The maximum value can be used to provide an upper limit while still allowing for a dynamically calculated value to be used, as long as it does not exceeed the maximum.

  • num_expire_threads and max_expire_threads
    Configures the number of threads used for expiring operations. Using a larger value will reduce contention on some common locks, and may improve performance.

  • num_poller_threads and max_poller_threads
    Configures the number of threads to be used for performing I/O. Not all configurations support changing these values.

  • num_resolver_threads
    Changes the number of threads used for asynchronous DNS look ups.

Finalization

extern void nng_init(nng_init_parms *params);

When the consumer is ready to deallocate any resoures allocated by the library, it should call the nng_fini function. Each call to nng_fini should be paired with an earlier call to nng_init.

After calling nng_fini, the consuming application must not make any other calls to NNG functions, except that it may use nng_init to initialize the application for further use.

Messages

Messages in Scalability Protocols are the fundamental unit of transmission and reception, as these protocols are fundamentally message-oriented.

Messages have a body, containing the application-supplied payload, and a header, containing protocol specific routing and similar related information.

tip

Only applications using raw mode need to access the message header. Very few NNG applications do this.

Message Structure

typedef struct nng_msg nng_msg;

The nng_msg structure represents a single message. It carries a body and a header.

Create a Message

int nng_msg_alloc(nng_msg **msgp, size_t size);

The nng_msg_alloc function allocates a new message. It takes a size argument, and returns a message with a preallocated body of that size in the msgp parameter.

If it succeeds, it returns zero, otherwise this function may return NNG_ENOMEM, indicating that insufficient memory is available to allocate a new message.

Destroy a Message

void nng_msg_free(nng_msg *msg);

The nng_msg_free function deallocates a message.

Duplicate a Message

int nng_msg_dup(nng_msg **dup, nng_msg *msg);

The nng_msg_dup function duplicates the message msg, storing a pointer to the new duplicate in dup. This function also returns zero on succes, or NNG_ENOMEM if memory is exhausted.

Message Size and Capacity

size_t nng_msg_capacity(nng_msg *msg);
int nng_msg_realloc(nng_msg *msg, size_t size);
int nng_msg_reserve(nng_msg *msg, size_t capacity);

Messages have a certain amount of pre-reserved space, which may exceed the total size of the message. This allows for content to be added to the message later, without necessarily performing a reallocation.

The nng_msg_capacity function returns the amount of prereserved space. If a message size change is required, and the new size will fit within the capacity reported by this function, then change will be done without a reallocation, and likely without a data copy as well.

tip

The capacity reported by nng_msg_capacity may not include reserved headroom, which is present to allow a very limited amount of content to be inserted in front of the message without requiring the rest of the message to be copied.

The message size may be changed by use of the nng_msg_realloc function. This function will reallocate the underlying memory for the message msg, preserving contents while doing so. If the new size is smaller than the original message, it will truncate the message, but not perform any allocations. If reallocation fails due to insufficient memory, then the original is left intact.

The nng_msg_reserve function ensures that the total message capacity is at least capacity bytes. Use of this function to ensure the total anticipated capacity is present in the message may help prevent many small allocations.

Both nng_msg_realloc and nng_msg_reserve return zero on success, or may return NNG_ENOMEM if insufficient memory exists to preform allocation.

important

Any pointers to message content obtained before a call to nng_msg_realloc or nng_msg_reserve (or any other function that changes the message size) should be treated as invalid, as the locations pointed to may be deallocated by these functions.

Message Body

void *nng_msg_body(nng_msg *msg);
size_t nng_msg_len(nng_msg *msg);

The body and body length of msg are returned by nng_msg_body and nng_msg_len, respectively.

Clear the Body

void *nng_msg_clear(nng_msg *msg);

The nng_msg_clear simply resets the total message body length to zero, but does not affect the capacity. It does not change the underlying bytes of the message.

Add to Body

int nng_msg_append(nng_msg *msg, const void *val, size_t size);
int nng_msg_append_u16(nng_msg *msg, uint16_t val16);
int nng_msg_append_u32(nng_msg *msg, uint32_t val32);
int nng_msg_append_u64(nng_msg *msg, uint64_t val64);

int nng_msg_insert(nng_msg *msg, const void *val, size_t size);
int nng_msg_insert_u16(nng_msg *msg, uint16_t val16);
int nng_msg_insert_u32(nng_msg *msg, uint32_t val32);
int nng_msg_insert_u64(nng_msg *msg, uint64_t val64);

Appending data to a message body is done by using the nng_msg_append functions. The base nng_msg_append function appends size bytes of untyped data to the end of the message.

Use of the typed versions, ending in suffixes _u16, _u32, and _u64 allows for unsigned integers to be appended directly. The integers are encoded in network byte order, with the most significant byte appearing first. The message body will by two, four, or eight bytes accordingly.

Data may inserted before the rest of the message body by using the nng_msg_insert functions. This will attempt to use “headroom” in the message to avoid a data copy. Otherwise they are like the nng_msg_append functions except that the put the data in front of the messages instead of at the end.

tip

Message headroom is limited, so nng_msg_insert is best used sparingly. It is much more efficient to build the message content from start to end using nng_msg_append.

Consume From Body

int nng_msg_chop(nng_msg *msg, size_t size);
int nng_msg_chop_u16(nng_msg *msg, uint16_t *val16);
int nng_msg_chop_u32(nng_msg *msg, uint32_t *val32);
int nng_msg_chop_u64(nng_msg *msg, uint64_t *val64);

int nng_msg_trim(nng_msg *msg, size_t size);
int nng_msg_trim_u16(nng_msg *msg, uint16_t *val16);
int nng_msg_trim_u32(nng_msg *msg, uint32_t *val32);
int nng_msg_trim_u64(nng_msg *msg, uint64_t *val64);

The nng_msg_chop functions remove data from the end of the body of message msg, reducing the message length by either size, or the appropriate value size.

The nng_msg_trim functions remove data from the beginning of the message body of msg. but are otherwise just like the nng_msg_chop functions.

If the message is not big enough to remove requisite amount of bytes, these functions return NNG_EINVAL. Otherwise they return zero.

Additionally, functions with typed suffixes (_u16, _u32, _u64) decode the data and return it through the appropriate val pointer.

The data is assumed to have been in network byte order in the message, but is returned in the native machine byte order. The appropriate number of bytes is consumed for each of these types, so two bytes for _u16, four bytes for _u32, and eight bytes for _u64.

Message Header

void *nng_msg_header(nng_msg *msg);
size_t nng_msg_header_len(nng_msg *msg);

The header and header length of msg are returned by nng_msg_header and nng_msg_header_len, respectively.

The message headers are generally intended for limited use, to store protocol headers.

important

The message headers are for protocol and transport headers, and not for general application payloads. Misuse of the header may prevent the application from functioning properly.

Clear the Header

void *nng_msg_header_clear(nng_msg *msg);

The nng_msg_header_clear simply resets the total message header length to zero.

Append or Insert Header

int nng_msg_header_append(nng_msg *msg, const void *val, size_t size);
int nng_msg_header_append_u16(nng_msg *msg, uint16_t val16);
int nng_msg_header_append_u32(nng_msg *msg, uint32_t val32);
int nng_msg_header_append_u64(nng_msg *msg, uint64_t val64);

int nng_msg_header_insert(nng_msg *msg, const void *val, size_t size);
int nng_msg_header_insert_u16(nng_msg *msg, uint16_t val16);
int nng_msg_header_insert_u32(nng_msg *msg, uint32_t val32);
int nng_msg_header_insert_u64(nng_msg *msg, uint64_t val64);

Appending data to a message header is done by using the nng_msg_header_append functions, and inserting data in the header is done using the nng_msg_header_insert functions.

These functions act just like the nng_msg_append and nng_msg_insert functions, except that they operate on the message header rather than the message body.

Consume from Header

int nng_msg_header_chop(nng_msg *msg, size_t size);
int nng_msg_header_chop_u16(nng_msg *msg, uint16_t *val16);
int nng_msg_header_chop_u32(nng_msg *msg, uint32_t *val32);
int nng_msg_header_chop_u64(nng_msg *msg, uint64_t *val64);

int nng_msg_header_trim(nng_msg *msg, size_t size);
int nng_msg_header_trim_u16(nng_msg *msg, uint16_t *val16);
int nng_msg_header_trim_u32(nng_msg *msg, uint32_t *val32);
int nng_msg_header_trim_u64(nng_msg *msg, uint64_t *val64);

The nng_msg_header_trim functions remove data from the beginning of the message header, and the nng_msg_header_chop functions remove data from the end of the message header.

These functions act just like the nng_msg_trim and nng_msg_chop functions, except that they operate the message header rather than the message body.

Message Pipe

nng_pipe nng_msg_get_pipe(nng_msg *msg);
void nng_msg_get_pipe(nng_msg *msg, nng_pipe p);

The nng_msg_set_pipe function sets the pipe associated with msg to p. This is most often useful when used with protocols that support directing a message to a specific peer. For example the PAIR version 1 protocol can do this when NNG_OPT_PAIR1_POLY mode is set.

The nng_msg_get_pipe function returns the pipe that was previously set on the message m, either directly by the application, or when the message was received by the protocol.

note

Not all protocols support overriding the destination pipe.

Examples

Example 1: Preparing a message for use

#include <nng/nng.h>

nng_msg *m;
if (nng_msg_alloc(&m, strlen("content") + 1) != 0) {
  // handle error
}
strcpy(nng_msg_body(m), "content");

Example 2: Preallocating message content

if (nng_msg_alloc(&m, 1024) != 0) {
  // handle error
}
while ((val64 = next_datum()) != 0) P
  if (nng_msg_append_u64(m, val64) != 0) {
      // handle error
  }
}

Sockets

Sockets in Scalability Protocols provide the handle for communication between peers. Sockets also encapsulate protocol specific semantics, such as filtering subscriptions, or automatically retrying requests.

Socket Structure

#define NNG_SOCKET_INITIALIZER // opaque value

typedef struct nng_socket_s nng_socket;

The nng_socket structure represents socket. This is a handle, and the members of it are opaque. However, unlike a pointer, it is usually passed by value.

A socket may be initialized statically with the NNG_SOCKET_INITIALIZER macro, to ensure that it cannot be confused with a valid open socket.

Socket Identity

int nng_socket_id(nng_socket s);
int nng_socket_raw(nng_socket s, bool *raw);
int nng_socket_proto_id(nng_socket s, uint16_t *proto);
int nng_socket_peer_id(nng_socket s, uint16_t *proto);
int nng_socket_proto_name(nng_socket s, const char **name);
int nng_socket_peer_name(nng_socket s, const char **name);

These functions are used to provide fundamental information about the socket s. Most applications will not need to use these functions.

The nng_socket_id function returns the numeric id, which will be a non-negative value, associated with the socket. If the socket is uninitialized (has never been opened), then the return value may be -1.

The nng_socket_proto_id and nng_socket_peer_id functions provide the 16-bit protocol identifier for the socket’s protocol, and of the protocol peers will use when communicating with the socket.

The nng_socket_proto_name and nng_socket_peer_name functions provide the ASCII names of the socket’s protocol, and of the protocol peers of the socket use. The value stored in name is a fixed string located in program text, and must not be freed or altered. It is guaranteed to remain valid while this library is present.

The nng_socket_raw function determines whether the socket is in raw mode or not, storing true in raw if it is, or false if it is not.

Opening a Socket

int nng_bus0_open(nng_socket *s);
int nng_pub0_open(nng_socket *s);
int nng_pull0_open(nng_socket *s);
int nng_push0_open(nng_socket *s);
int nng_rep0_open(nng_socket *s);
int nng_req0_open(nng_socket *s);
int nng_respondent0_open(nng_socket *s);
int nng_sub0_open(nng_socket *s);
int nng_surveyor0_open(nng_socket *s);

These functions open a socket, returning it in s. The constructors for sockets are protocol specific so please refer to protocol documentation for more specific information.

The following functions open a socket in normal mode:

  • nng_bus0_open - BUS version 0
  • nng_pair0_open - PAIR version 0
  • nng_pair1_open - PAIR version 1
  • nng_pair1_open_poly - PAIR version 1, polyamorous mode
  • nng_pub0_open - PUB version 0
  • nng_pull0_open - PULL version 0
  • nng_push0_open - PUSH version 0
  • nng_rep0_open - REP version 0
  • nng_req0_open - REQ version 0
  • nng_respondent0_open - RESPONDENT version 0
  • nng_sub0_open - SUB version 0
  • nng_surveyor0_open - SURVEYOR version 0

Raw Mode Sockets

int nng_bus0_open_raw(nng_socket *s);
int nng_pub0_open_raw(nng_socket *s);
int nng_pull0_open_raw(nng_socket *s);
int nng_push0_open_raw(nng_socket *s);
int nng_rep0_open_raw(nng_socket *s);
int nng_req0_open_raw(nng_socket *s);
int nng_respondent0_open_raw(nng_socket *s);
int nng_sub0_open_raw(nng_socket *s);
int nng_surveyor0_open_raw(nng_socket *s);

Raw mode sockets are used in circumstances when the application needs direct access to the message headers to control the protocol details.

Such sockets require greater sophistication on the part of the application to use, as the application must process the protocol headers specifically. The details of the protocol headers, and requirements, are described in the protocol documentation for each protocol.

Raw mode sockets do not have any kind of state machine associated with them, as all of the protocol specific processing must be performed by the application.

tip

Most applications do not need to use raw sockets. The notable exception is when using nng_device, which requires raw sockets. To obtain asynchronous behavior, consider using contexts instead.

The following functions open a socket in raw mode:

  • nng_bus0_open_raw - BUS version 0, raw mode
  • nng_pair0_open_raw - PAIR version 0, raw mode
  • nng_pair1_open_raw - PAIR version 1, raw mode
  • nng_pub0_open_raw - PUB version 0, raw mode
  • nng_pull0_open_raw - PULL version 0, raw mode
  • nng_push0_open_raw - PUSH version 0, raw mode
  • nng_rep0_open_raw - REP version 0, raw mode
  • nng_req0_open_raw - REP version 0, raw mode
  • nng_respondent0_open_raw - RESPONDENT version 0, raw mode
  • nng_sub0_open_raw - SUB version 0, raw mode
  • nng_surveyor0_open_raw - SURVEYOR version 0, raw mode

Closing a Socket

int nng_socket_close(nng_socket s);

The nng_socket_close function closes a socket, releasing all resources associated with it. Any operations that are in progress will be terminated with a result of NNG_ECLOSED.

note

Closing a socket also invalidates any dialers, listeners, pipes, or contexts associated with it.

note

This function will wait for any outstanding operations to be aborted, or to complete, before returning. Consequently it is not safe to call this from contexts that cannot block.

note

Closing the socket may be disruptive to transfers that are still in progress.

Sending Messages

int nng_send(nng_socket s, void *data, size_t size, int flags);
int nng_sendmsg(nng_socket s, nng_msg *msg, int flags);
void nng_socket_send(nng_socket s, nng_aio *aio);

These functions (nng_send, nng_sendmsg, and nng_socket_send) send messages over the socket s. The differences in their behaviors are as follows.

note

The semantics of what sending a message means varies from protocol to protocol, so examination of the protocol documentation is encouraged. Additionally, some protocols may not support sending at all or may require other pre-conditions first. (For example, REP sockets cannot normally send data until they have first received a request, while SUB sockets can only receive data and never send it.)

nng_send

The nng_send function is the simplest to use, but is the least efficient. It sends the content in data, as a message of size bytes. The flags is a bit mask made up of zero or more of the following values:

  • NNG_FLAG_NONBLOCK: If the socket cannot accept more data at this time, it does not block, but returns immediately with a status of NNG_EAGAIN. If this flag is absent, the function will wait until data can be sent.

note

Regardless of the presence or absence of NNG_FLAG_NONBLOCK, there may be queues between the sender and the receiver. Furthermore, there is no guarantee that the message has actually been delivered. Finally, with some protocols, the semantic is implicitly NNG_FLAG_NONBLOCK, such as with PUB sockets, which are best-effort delivery only.

nng_sendmsg

The nng_sendmsg function sends the msg over the socket s.

If this function returns zero, then the socket will dispose of msg when the transmission is complete. If the function returns a non-zero status, then the call retains the responsibility for disposing of msg.

The flags can contain the value NNG_FLAG_NONBLOCK, indicating that the function should not wait if the socket cannot accept more data for sending. In such a case, it will return NNG_EAGAIN.

tip

This function is preferred over nng_send, as it gives access to the message structure and eliminates both a data copy and allocation.

nng_socket_send

The nng_socket_send function sends a message asynchronously, using the nng_aio aio, over the socket s. The message to send must have been set on aio using the nng_aio_set_msg function.

If the operation completes successfully, then the socket will have disposed of the message. However, if it fails, then callback of aio should arrange for a final disposition of the message. (The message can be retrieved from aio with nng_aio_get_msg.)

Note that callback associated with aio may be called before the message is finally delivered to the recipient. For example, the message may be sitting in queue, or located in TCP buffers, or even in flight.

tip

This is the preferred function to use for sending data on a socket. While it does require a few extra steps on the part of the application, the lowest latencies and highest performance will be achieved by using this function instead of nng_send or nng_sendmsg.

Receiving Messages

int nng_recv(nng_socket s, void *data, size_t *sizep, int flags);
int nng_recvmsg(nng_socket s, nng_msg **msgp, int flags);
void nng_socket_recv(nng_socket s, nng_aio *aio);

These functions (nng_recv, nng_recvmsg, and nng_socket_recv) receive messages over the socket s. The differences in their behaviors are as follows.

note

The semantics of what receving a message means varies from protocol to protocol, so examination of the protocol documentation is encouraged. Additionally, some protocols may not support receiving at all or may require other pre-conditions first. (For example, REQ sockets cannot normally receive data until they have first sent a request, while PUB sockets can only send data and never receive it.)

nng_recv

The nng_recv function is the simplest to use, but is the least efficient. It receives the content in data, as a message size (in bytes) of up to the value stored in sizep.

Upon success, the size of the message received will be stored in sizep.

The flags is a bit mask made up of zero or more of the following values:

  • NNG_FLAG_NONBLOCK: If the socket has no messages pending for reception at this time, it does not block, but returns immediately with a status of NNG_EAGAIN. If this flag is absent, the function will wait until data can be received.

nng_recvmsg

The nng_recvmsg function receives a message and stores a pointer to the nng_msg for that message in msgp.

The flags can contain the value NNG_FLAG_NONBLOCK, indicating that the function should not wait if the socket has no messages available to receive. In such a case, it will return NNG_EAGAIN.

tip

This function is preferred over nng_recv, as it gives access to the message structure and eliminates both a data copy and allocation.

nng_socket_recv

The nng_socket_send function receives a message asynchronously, using the nng_aio aio, over the socket s. On success, the received message can be retrieved from the aio using the nng_aio_get_msg function.

note

It is important that the application retrieves the message, and disposes of it accordingly. Failure to do so will leak the memory.

tip

This is the preferred function to use for receiving data on a socket. While it does require a few extra steps on the part of the application, the lowest latencies and highest performance will be achieved by using this function instead of nng_recv or nng_recvmsg.

Socket Options

int nng_socket_get_bool(nng_socket s, const char *opt, bool *valp);
int nng_socket_get_int(nng_socket s, const char *opt, int *valp);
int nng_socket_get_ms(nng_socket s, const char *opt, nng_duration *valp);
int nng_socket_get_size(nng_socket s, const char *opt, size_t *valp);

int nng_socket_set_bool(nng_socket s, const char *opt, int val);
int nng_socket_set_int(nng_socket s, const char *opt, int val);
int nng_socket_set_ms(nng_socket s, const char *opt, nng_duration val);
int nng_socket_set_size(nng_socket s, const char *opt, size_t val);

Protocols usually have protocol specific behaviors that can be adjusted via options.

These functions are used to retrieve or change the value of an option named opt from the context ctx. The nng_socket_get_ functions retrieve the value from the socket s, and store it in the location valp references. The nng_socket_set_ functions change the value for the socket s, taking it from val.

These functions access an option as a specific type. The protocol documentation will have details about which options are available, whether they can be read or written, and the appropriate type to use.

note

Socket options are are used to tune the behavior of the higher level protocol. To change the options for an underlying transport, the option should be set on the dialer or listener instead of the socket.

Common Options

The following options are available for many protocols, and always use the same types and semantics described below.

OptionTypeDescription
NNG_OPT_MAXTTLintMaximum number of traversals across an nng_device device, to prevent forwarding loops. May be 1-255, inclusive. Normally defaults to 8.
NNG_OPT_RECONNMAXTnng_durationMaximum time dialers will delay before trying after failing to connect.
NNG_OPT_RECONNMINTnng_durationMinimum time dialers will delay before trying after failing to connect.
NNG_OPT_RECVBUFintMaximum number of messages (0-8192) to buffer locally when receiving.
NNG_OPT_RECVMAXSZsize_tMaximum message size acceptable for receiving. Zero means unlimited. Intended to prevent remote abuse. Can be tuned independently on dialers and listeners.
NNG_OPT_RECVTIMEOnng_durationDefault timeout (ms) for receiving messages.
NNG_OPT_SENDBUFintMaximum number of messages (0-8192) to buffer when sending messages.
NNG_OPT_SENDTIMEOnng_durationDefault timeout (ms) for sending messages.

 

note

The NNG_OPT_RECONNMAXT, NNG_OPT_RECONNMINT, and NNG_OPT_RECVMAXSZ options are just the initial defaults that dialers (and for NNG_OPT_RECVMAXSZ also listeners) will use. After the dialer or listener is created, changes to the socket’s value will have no affect on that dialer or listener.

Polling Socket Events

int nng_socket_get_recv_poll_fd(nng_socket s, int *fdp);
int nng_socket_get_send_poll_fd(nng_socket s, int *fdp);

Sometimes it is necessary to integrate a socket into a poll or select driven event loop. (Or, on Linux, epoll, or on BSD derived systems like macOS kqueue).

For these occasions, a suitable file descriptor for polling is provided by these two functions.

The nng_socket_get_recv_poll_fd function obtains a file descriptor that will poll as readable when a message is ready for receiving for the socket.

The nng_socket_get_send_poll_fd function obtains a file descriptor that will poll as readable when the socket can accept a message for sending.

These file descriptors should only be polled for readability, and no other operation performed on them. The socket will read from, or write to, these file descriptors to provide a level-signaled behavior automatically.

Additionally the socket will close these file descriptors when the socket itself is closed.

These functions replace the NNG_OPT_SENDFD and NNG_OPT_RECVFD socket options that were available in previous versions of NNG.

note

These functions are not compatible with contexts.

note

The file descriptors supplied by these functions is not used for transporting message data. The only valid use of these file descriptors is for polling for the ability to send or receive messages on the socket.

tip

Using these functions will force the socket to perform extra system calls, and thus have a negative impact on performance and latency. It is preferable to use asynchronous I/O when possible.

Examples

Example 1: Initializing a Socket

nng_socket s = NNG_SOCKET_INITIALIZER;

Example 2: Publishing a Timestamp

This example demonstrates the use of nng_aio, nng_socket_send, and nng_sleep_aio to build a service that publishes a timestamp at one second intervals. Error handling is elided for the sake of clarity.

#include <stdlib.h>
#include <stdio.h>
#include <nng/nng.h>
#include <nng/protocol/pubsub0/pub.h>

struct state {
    nng_socket s;
    bool sleeping;
    nng_aio *aio;
};

static struct state state;

void callback(void *arg) {
    nng_msg *msg;
    nng_time now;
    struct state *state = arg;
    if (nng_aio_result(state->aio) != 0) {
        fprintf(stderr, "Error %s occurred", nng_strerror(nng_aio_result(state->aio)));
        return; // terminate the callback loop
    }
    if (state->sleeping) {
        state->sleeping = false;
        nng_msg_alloc(&msg, sizeof (nng_time));
        now = nng_clock();
        nng_msg_append(msg, &now, sizeof (now)); // note: native endian
        nng_aio_set_msg(state->aio, msg);
        nng_socket_send(state->s, state->aio);
    } else {
        state->sleeping = true;
        nng_sleep_aio(1000, state->aio); // 1000 ms == 1 second
    }
}

int main(int argc, char **argv) {
    const char *url = argv[1]; // should check this

    nng_aio_alloc(&state.aio, NULL, NULL);
    nng_pub0_open(&state.s);
    nng_listen(state.s, url, NULL, 0);
    state.sleeping = 0;
    nng_sleep_aio(1, state.aio); // kick it off right away
    for(;;) {
        nng_msleep(0x7FFFFFFF); // infinite, could use pause or sigsuspend
    }
}

Example 3: Watching a Periodic Timestamp

This example demonstrates the use of nng_aio, nng_socket_recv, to build a client to watch for messages received from the service created in Example 2. Error handling is elided for the sake of clarity.

#include <stdlib.h>
#include <stdio.h>
#include <string.h>>
#include <nng/nng.h>
#include <nng/protocol/pubsub0/sub.h>

struct state {
    nng_socket s;
    nng_aio *aio;
};

static struct state state;

void callback(void *arg) {
    nng_msg *msg;
    nng_time now;
    struct state *state = arg;
    if (nng_aio_result(state->aio) != 0) {
        fprintf(stderr, "Error %s occurred", nng_strerror(nng_aio_result(state->aio)));
        return; // terminate the callback loop
    }
    msg = nng_aio_get_msg(state->aio);
    memcpy(&now, nng_msg_body(msg), sizeof (now)); // should check the length!
    printf("Timestamp is %lu\n", (unsigned long)now);
    nng_msg_free(msg);
    nng_aio_set_msg(state->aio, NULL);
    nng_socket_recv(state->s, state->aio);
}

int main(int argc, char **argv) {
    const char *url = argv[1]; // should check this

    nng_aio_alloc(&state.aio, NULL, NULL);
    nng_sub0_open(&state.s);
    nng_sub0_socket_subscribe(state.s, NULL, 0); // subscribe to everything
    nng_dial(state.s, url, NULL, 0);
    nng_socket_recv(state.s, state.aio); // kick it off right away
    for(;;) {
        nng_msleep(0x7FFFFFFF); // infinite, could use pause or sigsuspend
    }
}

Contexts

Contexts in Scalability Protocols provide for isolation of protocol-specific state machines and associated data, allowing multiple concurrent state machines (or transactions) to coexist on a single socket.

For example, a REP server may wish to allow many requests to be serviced concurrently, even though some jobs may take significant time to process. Contexts provide for this ability.

Not all protocols have contexts, because many protocols simply have no state to manage. The following protocols support contexts:

For these protocols, the socket will also have a single, default, context that is used when performing send or receive operations on the socket directly.

Other protocols are stateless, at least with respect to message processing, and have no use for contexts. For the same reason, raw mode sockets do not support contexts.

tip

Developers with experience with [libnanomsg] may be used to using raw sockets for concurrency. Contexts provide a superior solution, as they are much easier to use, less error prone, and allow for easy control of the amount of concurrency used on any given socket.

One drawback of contexts is that they cannot be used with file descriptor polling using nng_socket_get_recv_poll_fd or nng_socket_get_send_poll_fd.

Context Structure

#define NNG_CTX_INITIALIZER // opaque value

typedef struct nng_ctx_s nng_ctx;

The nng_ctx structure represents context. This is a handle, and the members of it are opaque. However, unlike a pointer, it is passed by value.

A context may be initialized statically with the NNG_CTX_INITIALIZER macro, to ensure that it cannot be confused with a valid open context.

Creating a Context

int nng_ctx_open(nng_ctx *ctxp, nng_socket s);

The nng_ctx_open function creates a separate context to be used with the socket s, and returns it at the location pointed by ctxp.

Context Identity

int nng_ctx_id(nng_ctx c);

The nng_ctx_id function returns a positive identifier for the context c if it is valid. Otherwise it returns -1.

A context is considered valid if it was ever opened with nng_ctx_open function. Contexts that are allocated on the stack or statically should be initialized with the macro NNG_CTX_INITIALIZER to ensure that they cannot be confused with a valid context before they are opened.

Closing a Context

int nng_ctx_close(nng_ctx ctx);

The nng_ctx_close function closes the context ctx. Messages that have been submitted for sending may be flushed or delivered, depending upon the transport.

Further attempts to use the context after this call returns will result in NNG_ECLOSED. Threads waiting for operations on the context when this call is executed may also return with an NNG_ECLOSED result.

note

Closing the socket associated with ctx using nng_socket_close also closes this context.

Sending Messages

int nng_ctx_sendmsg(nng_ctx ctx, nng_msg *msg, int flags);
void nng_ctx_send(nng_ctx ctx, nng_aio *aio);

These functions (nng_ctx_sendmsg and nng_ctx_send) send messages over the socket s. The differences in their behaviors are as follows.

note

The semantics of what sending a message means varies from protocol to protocol, so examination of the protocol documentation is encouraged. Additionally, some protocols may not support sending at all or may require other pre-conditions first. (For example, REP sockets cannot normally send data until they have first received a request, while SUB sockets can only receive data and never send it.)

nng_ctx_sendmsg

The nng_ctx_sendmsg function sends the msg over the context ctx.

If this function returns zero, then the socket will dispose of msg when the transmission is complete. If the function returns a non-zero status, then the call retains the responsibility for disposing of msg.

The flags can contain the value NNG_FLAG_NONBLOCK, indicating that the function should not wait if the socket cannot accept more data for sending. In such a case, it will return NNG_EAGAIN.

nng_ctx_send

The nng_ctx_send function sends a message asynchronously, using the nng_aio aio, over the context ctx. The message to send must have been set on aio using the nng_aio_set_msg function.

If the operation completes successfully, then the context will have disposed of the message. However, if it fails, then callback of aio should arrange for a final disposition of the message. (The message can be retrieved from aio with nng_aio_get_msg.)

Note that callback associated with aio may be called before the message is finally delivered to the recipient. For example, the message may be sitting in queue, or located in TCP buffers, or even in flight.

tip

This is the preferred function to use for sending data on a context. While it does require a few extra steps on the part of the application, the lowest latencies and highest performance will be achieved by using this function instead of nng_ctx_sendmsg.

Receiving Messages

int nng_ctx_recvmsg(nng_ctx ctx, nng_msg **msgp, int flags);
void nng_ctx_recv(nng_ctx ctx, nng_aio *aio);

These functions (, nng_ctx_recvmsg and nng_ctx_recv) receive messages over the context ctx. The differences in their behaviors are as follows.

note

The semantics of what receving a message means varies from protocol to protocol, so examination of the protocol documentation is encouraged. Additionally, some protocols may not support receiving at all or may require other pre-conditions first. (For example, REQ sockets cannot normally receive data until they have first sent a request.)

nng_recvmsg

The nng_ctx_recvmsg function receives a message and stores a pointer to the nng_msg for that message in msgp.

The flags can contain the value NNG_FLAG_NONBLOCK, indicating that the function should not wait if the socket has no messages available to receive. In such a case, it will return NNG_EAGAIN.

nng_socket_recv

The nng_ctx_send function receives a message asynchronously, using the nng_aio aio, over the context ctx. On success, the received message can be retrieved from the aio using the nng_aio_get_msg function.

note

It is important that the application retrieves the message, and disposes of it accordingly. Failure to do so will leak the memory.

tip

This is the preferred function to use for receiving data on a context. While it does require a few extra steps on the part of the application, the lowest latencies and highest performance will be achieved by using this function instead of nng_ctx_recvmsg.

Context Options

int nng_ctx_get_bool(nng_ctx ctx, const char *opt, bool *valp);
int nng_ctx_get_int(nng_ctx ctx, const char *opt, int *valp);
int nng_ctx_get_ms(nng_ctx ctx, const char *opt, nng_duration *valp);
int nng_ctx_get_size(nng_ctx ctx, const char *opt, size_t *valp);

int nng_ctx_set_bool(nng_ctx ctx, const char *opt, int val);
int nng_ctx_set_int(nng_ctx ctx, const char *opt, int val);
int nng_ctx_set_ms(nng_ctx ctx, const char *opt, nng_duration val);
int nng_ctx_set_size(nng_ctx ctx, const char *opt, size_t val);

Some protocols support certain options that affect the behavior of a specific context. For example, most protocols will let you set the defaults timeouts associated with send or receive separately for different contexts.

These functions are used to retrieve or change the value of an option named opt from the context ctx. The nng_ctx_get_ functions retrieve the value from ctx, and store it in the location valp references. The nng_ctx_set_ functions change the value for the ctx, taking it from val.

These functions access an option as a specific type. The protocol documentation will have details about which options are available for contexts, whether they can be read or written, and the appropriate type to use.

Examples

These examples show building blocks for a concurrent service based on contexts.

Example 1: Context Echo Server

The following program fragment demonstrates the use of contexts to implement a concurrent REP service that simply echos messages back to the sender.

struct echo_context {
    nng_ctx ctx;
    nng_aio *aio;
    enum { INIT, RECV, SEND } state;
};

void
echo(void *arg)
{
    struct echo_context *ec = arg;

    switch (ec->state) {
    case INIT:
        ec->state = RECV;
        nng_ctx_recv(ec->ctx, ec->aio);
        return;
    case RECV:
        if (nng_aio_result(ec->aio) != 0) {
            // ... handle error
        }
        // We reuse the message on the ec->aio
        ec->state = SEND;
        nng_ctx_send(ec->ctx, ec->aio);
        return;
    case SEND:
        if (nng_aio_result(ec->aio) != 0) {
            // ... handle error
        }
        ec->state = RECV;
        nng_ctx_recv(ec->ctx, ec->aio);
        return;
    }
}

Example 2: Starting the Echo Service

Given the above fragment, the following example shows setting up the service. It assumes that the socket has already been created and any transports set up as well with functions such as nng_dial or nng_listen.


#define CONCURRENCY 1024

static struct echo_context ecs[CONCURRENCY];

void
start_echo_service(nng_socket rep_socket)
{
    for (int i = 0; i < CONCURRENCY; i++) {
        // error checks elided for clarity
        nng_ctx_open(&ecs[i].ctx, rep_socket);
        nng_aio_alloc(&ecs[i].aio, echo, ecs+i);
        ecs[i].state = INIT;
        echo(ecs+i); // start it running
    }
}

Pipes

typedef struct nng_pipe_s nng_pipe;

An nng_pipe is a handle to a pipe object, which can be thought of as a single connection. (In most cases this is actually the case – the pipe is an abstraction for a single TCP or IPC connection.) Pipes are associated with either the listener or dialer that created them, and therefore are also automatically associated with a single socket.

note

The nng_pipe structure is always passed by value (both for input parameters and return values), and should be treated opaquely. Passing structures this way gives the compiler a chance to perform accurate type checks in functions passing values of this type.

tip

Most applications should never concern themselves with individual pipes. However it is possible to access a pipe when more information about the source of a message is needed, or when more control is required over message delivery.

Pipe objects are created by dialers and and listeners.

Initialization

A pipe may be initialized using the macro NNG_PIPE_INITIALIZER before it is opened, to prevent confusion with valid open pipes.

For example:

nng_pipe p = NNG_PIPE_INITIALIZER;

Pipe Identity

int nng_pipe_id(nng_pipe p);

The nng_pipe_id function returns a positive identifier for the pipe p, if it is valid. Otherwise it returns -1.

note

A pipe is considered valid if it was ever created by the socket. Pipes that are allocated on the stack or statically should be initialized with the macro NNG_PIPE_INITIALIZER to ensure that they cannot be confused with a valid pipe.

Closing a Pipe

nng_err nng_pipe_close(nng_pipe p);

The nng_pipe_close function closes the supplied pipe, p. Messages that have been submitted for sending may be flushed or delivered, depending upon the transport.

Further attempts to use the pipe after this call returns will result in NNG_ECLOSED.

tip

Pipes are automatically closed when their creator closes, or when the remote peer closes the underlying connection.

Pipe Creator

nng_dialer nng_pipe_dialer(nng_pipe p);
nng_listener nng_pipe_listener(nng_pipe p);
nng_socket nng_pipe_socket(nng_pipe p);

These functions return the socket, dialer, or listener that created or owns the pipe.

If the pipe was does not have an associated dialer or listener, then the associated will return [NNG_DIALER_INITIALIZER] or [NNG_LISTENER_INITIALIZER], as appropriate, and either [nng_dialer_id] or [nng_listener_id] for the returned object will return -1.

note

The socket, or the endpoint, returned by these functions may be in the process of closing, and might not be further usable as a result. (Other functions will result in NNG_ECLOSED.)

Pipe Options

nng_err nng_pipe_get_bool(nng_pipe p, const char *opt, bool *valp);
nng_err nng_pipe_get_int(nng_pipe p, const char *opt, int *valp);
nng_err nng_pipe_get_ms(nng_pipe p, const char *opt, nng_duration *valp);
nng_err nng_pipe_get_size(nng_pipe p, const char *opt, size_t *valp);
nng_err nng_pipe_get_addr(nng_pipe p, const char *opt, nng_sockaddr *valp);
nng_err nng_pipe_get_string(nng_pipe p, const char *opt, char **valp);

These functions are used to obtain value of an option named opt from the pipe p, and store it in the location referenced by valp.

These functions access an option as a specific type. The transport layer will have details about which options are available, and which type they may be accessed using.

In the case of nng_pipe_get_string, the string is created as if by nng_strdup, and must be freed by the caller using nng_strfree when no longer needed.

Pipe Notifications

typedef enum {
        NNG_PIPE_EV_ADD_PRE,
        NNG_PIPE_EV_ADD_POST,
        NNG_PIPE_EV_REM_POST,
} nng_pipe_ev;

typedef void (*nng_pipe_cb)(nng_pipe, nng_pipe_ev, void *);

nng_err nng_pipe_notify(nng_socket s, nng_pipe_ev ev, nng_pipe_cb cb, void *arg);

The nng_pipe_notify function registers the callback function cb to be called whenever the pipe event specified by ev occurs on the socket s. The callback cb will be passed arg as its final argument.

A different callback may be supplied for each event. Each event may have at most one callback registered. Registering a callback implicitly unregisters any previously registered.

The following pipe events are supported:

EventDescription
NNG_PIPE_EV_ADD_PREThis event occurs after a connection and negotiation has completed, but before the pipe is added to the socket. If the pipe is closed (using nng_pipe_close) at this point, the socket will never see the pipe, and no further events will occur for the given pipe.
NNG_PIPE_EV_ADD_POSTThis event occurs after the pipe is fully added to the socket. Prior to this time, it is not possible to communicate over the pipe with the socket.
NNG_PIPE_EV_REM_POSTThis event occurs after the pipe has been removed from the socket. The underlying transport may be closed at this point, and it is not possible communicate using this pipe.

warning

The callback cb function must not attempt to perform any accesses to the socket, as it is called with a lock on the socket held! Doing so would thus result in a deadlock.

tip

The callback cb may close a pipe for any reason by simply closing it using nng_pipe_close. For example, this might be done to prevent an unauthorized peer from connecting to the socket, if an authorization check made during NNG_PIPE_EV_ADD_PRE fails.

note

This function ignores invalid values for ev.

Memory

Managing memory and allocations is something that every C program has to deal with. In the case of NNG, it can be more complicated because the underlying platform code can provide different allocators that might not be compatible with the use system allocator used by malloc and free.

Allocate Memory

void *nng_alloc(size_t size);

The nng_alloc function allocates a contiguous memory region of at least size bytes, and returns a pointer to it. The memory will be 64-bit aligned. Note that the memory may have random data in it, just like with malloc.

If memory cannot be allocated for any reason, then NULL will be returned. Applications that experience this should treat this like NNG_ENOMEM.

Memory returned by nng_alloc should be freed when no longer needed using nng_free.

important

Do not use the system free function (or the C++ delete operator) to release this memory. On some configurations this may work, but on others it will lead to a crash or other unpredictable behavior.

Deallocate Memory

void nng_free(void *ptr, size_t size);

The nng_free function deallocates memory previously allocated by nng_alloc.

The size argument must exactly match the size argument that was supplied to nng_alloc when the memory was allocated.

Duplicate String

char *nng_strdup(const char *src);

The nng_strdup duplicates the string src and returns it.

This is logically equivalent to using nng_alloc to allocate a region of memory of strlen(s) + 1 bytes, and then using strcpy to copy the string into the destination before returning it.

The returned string should be deallocated with nng_strfree, or may be deallocated using the nng_free using the length of the returned string plus one (for the NUL terminating byte).

Free String

void nng_strfree(char *str);

The nng_strfree function is a convenience function that can be used to deallocate strings allocated with nng_strdup.

It is effectively the same as nng_free(strlen(str) + 1).

Time

NNG supports has support for time in the form of access to a system clock, and supporting timeouts for certain operations.

Time Type

typedef uint64_t nng_time;

The nng_time type is used to represent a clock offset from a common base time, measured in milliseconds.

The reference, or zero value, is some arbitrary point in time, most often sytem boot, but can be process start time or any other convenient reference.

All threads within a process will use the same reference time, but be aware that different processes may use a different reference time.

Duration Type

typedef int64_t nng_duration;

#define NNG_DURATION_INFINITE (-1)
#define NNG_DURATION_DEFAULT  (-2)
#define NNG_DURATION_ZERO     (0)

The nng_duration time measures a time duration in milliseconds. Normally durations are positive, but some specific negative values are reserved.

  • NNG_DURATION_INFINITE: The duration essentially means forever. This is most often used with a timeout to indicate that there is no timeout, the operation should wait until it is complete, even forever.

  • NNG_DURATION_DEFAULT: This special value is used in some circumstances to prevent overriding a default timeout. Some operations have a default maximum time, and this value means that the previously established default should be used. The precise meaning is context-specific.

  • NNG_DURATION_ZERO: A zero length duration is used to performan an immediate poll.

Get the Current Time

nng_time nng_clock(void);

The nng_clock function returns the number of elapsed milliseconds since some arbitrary time in the past. The resolution of the clock depends on the underlying timing facilities of the system. This function may be used for timing, but applications should not expect very fine-grained values.

Wait for Duration

void nng_msleep(nng_duration msec);

The nng_msleep function blocks the calling thread for at least the specified number of milliseconds.

tip

This function may block for longer than requested. The actual wait time is determined by the capabilities of the underlying system.

Wait Asynchronously

void nng_sleep_aio(nng_duration msec, nng_aio *aio);

It is possible to wait as the action on an nng_aio, which in effect acts like scheduling a callback to run after a specified period of time.

The nng_sleep_aio function provides this capability. After msec milliseconds have passed, then aio’s callback will be executed. If this sleep waits without interruption, and then completes, the result from nng_aio_result will be zero.

note

If a timeout shorter than msec is set on aio using nng_aio_set_timeout, then the sleep will wake up early, with a result code of NNG_ETIMEDOUT.

See Also

Asynchronous Operations, Synchronization

URLs

Universal Resource Locators, or URLs for short, are a standardized way of representing a network resource, defined in RFC 1738, and RFC 3968.

In Scalability Protocols, this concept is extended, although it includes schemes that are not part of the IETF standards.

URL Structure

typedef struct nng_url nng_url;

const char *nng_url_scheme(const nng_url *url);
const char *nng_url_userinfo(const nng_url *url);
const char *nng_url_hostname(const nng_url *url);
uint32_t    nng_url_port(const nng_url *url);
const char *nng_url_path(const nng_url *url);
const char *nng_url_query(const nng_url *url);
const char *nng_url_fragment(const nng_url *url):

URL Fields

The nng_url_scheme function returns the scheme, without any colons or slashes. Values are lower case strings, like “http” or “inproc” or “tls+tcp4”.

The nng_url_userinfo function returns a string corresponding to the user component of a URL (the part before any @ sign) if such a component is present, otherwise it returns NULL.

The nng_url_hostname function returns a hostname (which might actually be an IP address) from the URL, if the URL corresponds to a scheme that uses hostnames (like “http” or “tcp”). If the URL does not (for example “inproc” or “ipc” URLs) then it returns NULL.

The nng_url_port function returns the TCP or UDP port number if the URL corresponds to a protocol based on TCP or UDP. It returns zero otherwise. Note that the port number might not have been explicitly specified in the URL. For example, the port number associated with “http://www.example.com” is 80, which is the standard port number for HTTP.

tip

The port number returned by this is in the native machine byte order. Be careful when using this with other network-oriented APIs.

The nng_url_path function returns the path component of the URL. This will always be non-NULL, but it may be empty.

The nng_url_query and nng_url_fragment functions return the query-information (the part following a ‘?’) and fragment (the part following a ‘#’) if those components are present, or NULL if they are not. The returned string will not include the leading ‘?’ or ‘#’ characters.

Note that any strings returned by these functions are only valid until url is freed with nng_url_free.

Format a URL

int nng_url_sprintf(char *buf, size_t bufsz, const nng_url *url);

The nng_url_sprintf function formats the url to the buf, which must have bufsz bytes of free space associated with it.

This function returns the number of bytes formatted to buf, excludng the terminating zero byte, or if bufsz is too small, then it returns the number of bytes that would have been formatted if there was sufficient space. The semantics are similar to the snprintf function from C99.

tip

If bufsz is 0, then buf can be NULL, and the return value can be used to determine the amount of space to allocate for a dynamically sized buffer.

Parse a URL

int nng_url_parse(nng_url **urlp, const char *str);

The nng_url_parse function parses a URL string (in str), and creates a dynamically allocated nng_url, returning it in urlp.

important

Only nng_url_free should be used to deallocate nng_url objects.

Clone a URL

int nng_url_clone(nng_url **dup, const nng_url *url);

The nng_url_clone function creates a copy of url, and returns it in dup.

Destroy a URL

void nng_url_free(nng_url *url);

The nng_url_free function destroy an nng_url object created with either nng_url_parse or nng_url_free.

This is the only correct way to destroy an nng_url object.

Update a URL Port

void nng_url_resolve_port(nng_url *url, uint32_t port);

In order to use dynamic port allocation, some transports and schemes allow a port number of zero to be specified. When this is done, the system will allocate a free port when the port is bound while starting a listener. Once this is done, the port number may need to be updated so that the URL can be used to configure a client. The nng_url_resolve_port function updates such a URL with a new port. This will have no effect if the URL already has a non-zero port number.

See Also

More information about Universal Resource Locators can be found in RFC 3986.

Asynchronous Operations

In order to obtain significant scalability, with low-latency, and minimal overheads, NNG supports performing operations asynchronously.

One way that applications can perform work asynchronously and concurrently is by using threads, but threads carry significant resource overheads and frequently there are limits on the number that can easily be created.

Additionally, with most network applications, the flow of execution will spend the bulk of its time waiting for traffic from a peer.

For these kinds of applications, it is far more efficient to use asynchronous operations using the mechanisms described in this chapter.

tip

To get the highest performance with the least overhead, applications should use asynchronous operations described in this chapter whenever possible.

Asynchronous I/O Handle

typedef struct nng_aio nng_aio;

An nng_aio is an opaque structure used in conjunction with asynchronous I/O operations. Every asynchronous operation uses one of these structures, each of which can only be used with a single operation at a time.

Asynchronous operations are performed without blocking calling application threads. Instead the application registers a callback function to be executed when the operation is complete (whether successfully or not). This callback will be executed exactly once.

The asynchronous I/O framework also supports cancellation of operations that are already in progress as well setting a maximum timeout for them to complete.

It is also possible to initiate an asynchronous operation, and wait for it to complete, creating a synchronous flow from an asynchronous one.

Create Handle

nng_err nng_aio_alloc(nng_aio **aiop, void (*callb)(void *), void *arg);

The nng_aio_alloc function creates an nng_aio object, with the callback callb taking the argument arg, and returns it in aiop.

If this succeeds, the function returns zero. Otherwise it may return NNG_ENOMEM.

tip

The arg should normally be a structure that contains a pointer to the aiop, or from which it can be located. This allows callb to check the handle for success using nng_aio_result, as well access other properties of aiop.

tip

The callb may be executed on another thread, so it may be necessary to use synchronization methods in callb to avoid data races.

Destroy Handle

void nng_aio_free(nng_aio *aio);
void nng_aio_reap(nng_aio *aio);

The nng_aio_free handle destroys the handle aio, waiting for any operations and associated callbacks to complete before doing so.

The nng_aio_reap handle destroys the handle aio asynchronously, using a reaper thread to do so. It does not wait for the object to be destroyed. Thus this function is safe to call from aio’s own callback.

note

The nng_aio_free function must never be called from an aio callback. Use nng_aio_reap instead if an object must be destroyed from a callback.

Cancellation

void nng_aio_abort(nng_aio *aio, nng_err err);
void nng_aio_cancel(nng_aio *aio);
void nng_aio_stop(nng_aio *aio);

These functions are used to stop a previously submitted asynchronous I/O operation. The operation may be canceled, or may continue to completion. If no operation is in progress (perhaps because it has already completed), then these operations have no effect. If the operation is successfully canceled or aborted, then the callback will still be called.

The nng_aio_abort function aborts the operation associated with aio and returns immediately without waiting. If cancellation was successful, then nng_aio_result will return err.

The nng_aio_cancel function acts like nng_aio_abort, but uses the error code NNG_ECANCELED.

The nng_aio_stop function aborts the aio operation with NNG_ESTOPPED, and then waits the operation and any associated callback to complete. This function also marks aio itself permanently stopped, so that any new operations scheduled by I/O providers using nng_aio_start return false. Thus this function should be used to teardown operations.

tip

When multiple asynchronous I/O handles are in use and need to be deallocated, it is safest to stop all of them using nng_aio_stop, before deallocating any of them with nng_aio_free, particularly if the callbacks might attempt to reschedule further operations.

Set Timeout

void nng_aio_set_timeout(nng_aio *aio, nng_duration timeout);
void nng_aio_set_expire(nng_aio *aio, nng_time expiration);

The nng_aio_set_timeout function sets a timeout for the asynchronous operation associated with aio. This causes a timer to be started when the operation is actually started. If the timer expires before the operation is completed, then it is aborted with an error of NNG_ETIMEDOUT. The timeout duration is specified as a relative number of milliseconds.

If the timeout is NNG_DURATION_INFINITE, then no timeout is used. If the timeout is NNG_DURATION_DEFAULT, then a “default” or socket-specific timeout is used. (This is frequently the same as NNG_DURATION_INFINITE.)

The nng_aio_set_expire function is similar to nng_aio_set_timeout, but sets an expiration time based on the system clock. The expiration time is a clock timestamp, such as would be returned by nng_clock.

Wait for Completion

void nng_aio_wait(nng_aio *aio);

The nng_aio_wait function waits for an asynchronous I/O operation to complete. If the operation has not been started, or has already completed, then it returns immediately.

If a callback was set with aio when it was allocated, then this function will not be called until the callback has completed.

important

The nng_aio_wait function should never be called from a function that itself is a callback of an nng_aio, either this one or any other. Doing so may result in a deadlock.

Test for Completion

bool nng_aio_busy(nng_aio *aio);

The nng_aio_busy function returns true if the aio is currently busy performing an operation or is executing a completion callback. Otherwise it return false. This is the same test used internally by nng_aio_wait.

important

The caller is responsible for coordinating any use of this with any reuse of the aio. Because the aio can be reused use of this function can be racy.

Result of Operation

nng_err nng_aio_result(nng_aio *aio);
size_t nng_aio_count(nng_aio *aio);

The nng_aio_result function returns the result of the operation associated with the handle aio. If the operation was successful, then 0 is returned. Otherwise a non-zero error code, such as NNG_ECANCELED or NNG_ETIMEDOUT, is returned.

For operations that transfer data, nng_aio_count returns the number of bytes transferred by the operation associated with the handle aio. Operations that do not transfer data, or do not keep a count, may return zero for this function.

note

The return value from these functions is undefined if the operation has not completed yet. Either call these from the handle’s completion callback, or after waiting for the operation to complete with nng_aio_wait.

Messages

nng_msg *nng_aio_get_msg(nng_aio *aio);
void nng_aio_set_msg(nng_aio *aio, nng_msg *msg);

The nng_aio_get_msg and nng_aio_set_msg functions retrieve and store a message in aio. For example, if a function to receive data is called, that function can generally be expected to store a message on the asssociated aio, for the application to retrieve with nng_aio_get_msg. Conversely an application desiring to send a message msg will store it in the aio using nng_aio_set_msg. The function implementing the send operation will retrieve the message and arrange for it to be sent.

Message Ownership

For send or transmit operations, the rule of thumb is that implementation of the operation is responsible for taking ownership of the message (and releasing resources when it is complete), if it will return success. If the operation will end in error, then the message will be retained and it is the consuming application’s responsibility to dispose of the message. This allows an application the opportunity to reuse the message to try again, if it so desires.

For receive operations, the implementation of the operation will set the message on the aio on success, and the consuming application hasa responsibility to retrieve and dispose of the message. Failure to do so will leak the message. If the operation does not complete successfully, then no message is stored on the aio.

I/O Vector

typedef struct nng_iov {
    void * iov_buf;
    size_t iov_len;
};

int nng_aio_set_iov(nng_aio *aio, unsigned int niov, nng_iov *iov);

For some operations, the unit of data transferred is not a message, but rather a stream of bytes.

For these operations, an array of niov nng_iov structures can be passed to the nng_aio_set_iov function to provide a scatter/gather array of elements describing the location (iov_buf) and length (iov_len) of data, to transfer.

The iov vector is copied into storage in the aio itself, so that callers may use stack allocated nng_iov structures. The values pointed to by the iov_buf members are not copied by this function though.

A maximum of four (4) nng_iov members may be supplied.

tip

Most functions using nng_iov do not guarantee to transfer all of the data that they are requested to. To be sure that correct amount of data is transferred, as well as to start an attempt to complete any partial transfer, check the amount of data transferred by calling nng_aio_count.

Inputs and Outputs

void nng_aio_set_input(nng_aio *aio, unsigned int index, void *param);
void *nng_aio_get_output(nng_aio *aio, unsigned int index);

Asynchronous operations can take additional input parameters, and provide additional result outputs besides the result code.

The nng_aio_set_input function sets the input parameter at index to param for the operation associated with aio.

The nng_aio_get_output function returns the output result at index for the operation associated with aio.

The type and semantics of input parameters and output results are determined by specific operations. The documentation for the operation should provide details.

The valid values of index range from zero (0) to three (3), as no operation currently defined can accept more than four parameters or return more than four additional results.

note

If the index does not correspond to a defined input for the operation, then nng_aio_set_input will have no effect, and nng_aio_get_output will return NULL.

important

It is an error to call this function while the aio is currently in use by an active asynchronous operation.

Scatter Gather Vectors

typedef struct nng_iov {
	void  *iov_buf;
	size_t iov_len;
} nng_iov;

void nng_aio_set_iov(nng_aio *aio, unsigned nio, const nng_iov *iov);

Some asynchronous operations, such as those dealing with streams, use scatter or gather vectors, where data to be transferred is either gathered from multiple separate regions of memory, or scattered into separate regions of memory. For example a message may have a header located at one location in memory, and a payload located in another.

The nng_aio_set_iov function configures the aio to use nio separate segments, described by the elements in iov. For each of these, the segment of size iov_len located at iov_buf will be used.

The elements of iov will be copied into aio, so the vector may be located on the stack or another temporary location. The locations referenced by it, must remain valid for the duration of the operation, of course.

Note that many of these operations are not guaranteed to perform a full transfer of data, so even a successful operation should check the amount of data actually transferred using nng_aio_count, and if necessary resubmit the operation with a suitably updated vector of nng_iov using this function.

See Also

Synchronization, Threads, Streams, Time

Synchronization Primitives

In order to allow safely accessing shared state, or to allow coordination between different threads, NNG provides synchronization primitives in the form of mutual exclusion locks and condition variables.

Correct use of these primitives will be needed when accessing shared state from threads, or from callback functions associated with asynchronous operations. (The need to do this in callbacks is because the callback may be executed under a task thread other than the submitting thread.)

Mutual Exclusion Lock

typedef struct nng_mtx nng_mtx;

Mutual exclusion locks, or mutex locks, represented by the nng_mtx structure, allow only a single thread to lock “own” the lock, acquired by nng_mtx_lock. Any other thread trying to acquire the same mutex will wait until the owner has released the mutex by calling nng_mtx_unlock.

Creating a Mutex

int nng_mutx_alloc(nng_mt **mtxp);

A mutex can be created by allocating one with nng_mtx_lock. On success, a pointer to the mutex is returned through mtxp. This function can fail due to insufficient memory or resources, in which case it will return NNG_ENOMEM. Otherwise it will succceed and return zero.

Destroying a Mutex

void nng_mtx_free(nng_mtx *mtx);

When no longer needed, a mutex can be deallocated and its resources returned to the caller, by calling nng_mtx_free. The mutex must not be locked by any thread when calling this function.

Acquiring a Mutex

void nng_mtx_lock(nng_mtx *mtx);

The nng_mtx_lock function acquires ownership of a mutex, waiting for it to unowned by any other threads if necessary.

important

A thread must not attempt to reqacuire the same mutex while it already “owns” the mutex. If it does attempt to do so, the result will be a single party deadlock.

Releasing a Mutex

void nng_mtx_unlock(nng_mtx *mtx);

The nng_mtx_unlock function releases a mutex that the calling thread has previously acquired with nng_mtx_lock.

important

A thread must not attempt to release (unlock) a mutex if it was not the thread that acquired the mutex to begin with.

Condition Variable

typedef struct nng_cv nng_cv;

The nng_cv structure implements a condition variable, associated with the the mutex mtx which was supplied when it was created.

Condition variables provide for a way to wait on an arbitrary condition, and to be woken when the condition is signaled. The mutex is dropped while the caller is asleep, and reacquired atomically when the caller is woken.

important

The caller of nng_cv_until, nng_cv_wait, nng_cv_wake, and nng_cv_wake1 must have ownership of the mutex mtx when calling these functions.

Creating a Condition Variable

int nng_cv_alloc(nng_cv **cvp, nng_mtx *mtx);

The nng_cv_alloc function allocates a condition variable, and associated with the mutex mtx, and returns a pointer to it in cvp.

Destroy a Condition Variable

void nng_cv_free(nng_cv *cv);

The nng_cv_free function deallocates the condition variable cv.

Waiting for the Condition

int nng_cv_until(nng_cv *cv, nng_time when);
void nng_cv_wait(nng_cv *cv);

The nng_cv_until and nng_cv_wait functions put the caller to sleep until the condition variable cv is signaled, or (in the case of nng_cv_until), the specified time when (as determined by nng_clock is reached.

While nng_cv_wait never fails and so has no return value, the nng_cv_until function can return NNG_ETIMEDOUT if the time is reached before condition cv is signaled by either nng_cv_wake or nng_cv_wake1.

Signaling the Condition

void nng_cv_wake(nng_cv *cv);
void nng_cv_wake1(nng_cv *cv);

The nng_cv_wake and nng_cv_wake1 functions wake threads waiting in nng_cv_until or nng_cv_wait. The difference between these functions is that nng_cv_wake will wake every thread, whereas nng_cv_wake1 will wake up exactly one thread (which may be chosen randomly).

tip

Use of nng_cv_wake1 may be used to reduce the “thundering herd” syndrom of waking all threads concurrently, but should only be used in circumstances where the application does not depend on which thread will be woken. When in doubt, nng_cv_wake is safer.

Examples

Example 1: Allocating the condition variable

nng_mtx *m;
nng_cv *cv;
nng_mtx_alloc(&m); // error checks elided
nng_cv_alloc(&cv, m);

Example 2: Waiting for the condition

expire = nng_clock() + 1000; // 1 second in the future
nng_mtx_lock(m);  // assume cv was allocated using m
while (!condition_true) {
  if (nng_cv_until(cv, expire) == NNG_ETIMEDOUT) {
    printf("Time out reached!\n");
    break;
  }
}
// condition_true is true
nng_mtx_unlock(m);

Example 3: Signaling the condition

nng_mtx_lock(m);
condition_true = true;
nng_cv_wake(cv);
nng_mtx_unlock(m);

See Also

Threads, Time, Asynchronous I/O

Threads

Threads provide a means of representing multiple parallel execution contexts. NNG makes use of this concept internally, but also provides for user applications to utilize the same thread facilities. This allows one form of concurrency for applications.

note

Threads in NNG are built upon platform support, which may be based upon operating system supplied threads, process, or coroutines. The appearance of concurrency does not guarantee true concurrency, and scheduling between threads may not necessarily be pre-emptive. While this will not adversely impact applications that use this facility for I/O bound concurrency, it may not provide good results for purely CPU-bound operations.

important

Thread objects created by this function may not be real system-level threads capable of performing blocking I/O operations using normal blocking system calls. If use of blocking system calls is required (not including APIs provided by the NNG library itself of course), then real OS-specific threads should be created instead (such as with pthread_create or similar functions.) Blocking NNG library calls can however be made safely from NNG threads.

tip

The system may impose limits on the number of threads that can be created. Typically applications should not create more than a dozen of these. If greater concurrency or scalability is needed, consider instead using an asynchronous model using nng_aio structures.

Thread Structure

typedef struct nng_thread nng_thread;

The nng_thread structure represnts a thread, which is a single execution context. A given thread will have its own stack, and CPU registers. However global state, as well as values allocated on the heap, will be shared and accessible to all threads in the system (See the Synchronization chapter for functions to help with data sharing between different threads.)

Multiple threads can be thought of as running concurrently, even though they might not actually do so.

I/O operations that block (i.e. wait for completion) will block the thread, while allowing other threads to proceed.

Creating a Thread

int nng_thread_create(nng_thread **thrp, void (*func)(void *), void *arg);

The nng_thread_create function creates a thread, which will execute func, with the given argument arg, and returns a pointer to it in thrp.

The thread may begin execution immediately.

The thread will persist until func returns.

This function returns zero on success, but may return NNG_ENOMEM if insufficient resources to create a thread are available.

Destroying a Thread

void nng_thread_destroy(nng_thread *thr);

The nng_thread_destroy function waits for the thread thr to finish execution. This function should be called to reclaim any resources associated with the thread when done. It also has the effect of blocking execution in the caller until thr has completed executing.

Thread Names

void nng_thread_set_name(nng_thread *thr, const char *name);

In order to facilitate debugging, nng_thread_set_name may be called to provide a name for the thread. This may change how the thread is represented in debuggers. Not all platforms support setting the thread name.

See Also

Synchronization, Asynchronous Operations

Logging

This chapter describes the support for message logs. Both applications and NNG itself can emit logs, which can be useful for application field support and debugging. Additionally applications can customize the handling of this logging as needed.

Note that logging is disabled by default unless an application configures a suitable logger with nng_log_set_logger.

Submitting Logs

void nng_log_err(const char *msgid, const char *msg, ...);
void nng_log_warn(const char *msgid, const char *msg, ...);
void nng_log_notice(const char *msgid, const char *msg, ...);
void nng_log_info(const char *msgid, const char *msg, ...);
void nng_log_debug(const char *msgid, const char *msg, ...);

These functions inject a a message into the logging system, where it will be processed and potentially go to system logs, standard output, or procssed further.

The msgid is a short prefix that should uniquely identify the message, possibly also with some kind of category. It is recommended that strings between 8 and 16 charactes be used. As this may, but will not necessarily be displayed to the user, the content of the message should not appear solely in this field. A NULL value is permitted here, but that may make filtering the message or other automatic processing more difficult.

The msg is a printf-style format string, which is used to format the message content. The following arguments are consumed in the same manner as printf.

tip

Applications should take care to limit the use of higher severity levels, as message logs are potentially expensive, increase stress for end users and administrators, and further may mask real problems if incorrectly over used.

Warnings and error messages should be concise and actionable, and notices should only really be those things that are worthy of attention.

Informational and debug messages used during development should be removed when no longer needed, as these messages can overwhelm logging subsystems and can reduce the signal-to-noise value for the message logs, impairing the diagnostic value of the logs.

Auth Logs

void nng_log_auth(nng_log_level level, const char *msgid, const char *msg, ...);

The nng_log_auth function formats and injects a security related log message. (“Auth” can indicate either “authentication” or “authorization”.) The level is a log level. The msgid, msg, and any remaining arguments are processed in a fashion similar to the other logging functions, except that the logs may be are logged using the NNG_LOG_AUTH facility, and thus may be redirected or receive other special treatment.

Log Levels

typedef enum nng_log_level nng_log_level;

void nng_log_set_level(nng_log_level level);
nng_log_level nng_log_get_level(void);

The nng_log_level type represents a severity for logged messages. These levels correspond to those found in the UNIX syslog subsystem, although applications should not depend upon the values being identical.

The nng_log_set_level function sets the log level. Messages with a severity that is numerically greater than this (less-severe) will be discarded.

The nng_log_get_level function returns the log level most recently set by nng_log_set_level or the default if that function has not been called.

The log levels are defined as follows:

typedef enum nng_log_level {
    NNG_LOG_NONE   = 0, // used for filters only, NNG suppresses these
    NNG_LOG_ERR    = 3,
    NNG_LOG_WARN   = 4,
    NNG_LOG_NOTICE = 5,
    NNG_LOG_INFO   = 6,
    NNG_LOG_DEBUG  = 7
} nng_log_level;

The value NNG_LOG_NONE may be useful to suppress message logs altogether.

The default level is typically NNG_LOG_NOTICE, but applications should select a value rather than relying upon the default.

Log Facilities

typedef enum nng_log_facility

void nng_log_set_facility(nng_log_facility facility);

Logging facilities are used to indicate the source of a log message, and may be useful in routing and processing these logs. Traditionally these are used with the UNIX syslog system, and the values here represent some (but not all) of the values found there.

The following values are defined:

typedef enum nng_log_facility {
    NNG_LOG_USER   = 1,
    NNG_LOG_DAEMON = 3,
    NNG_LOG_AUTH   = 10,
    NNG_LOG_LOCAL0 = 16,
    NNG_LOG_LOCAL1 = 17,
    NNG_LOG_LOCAL2 = 18,
    NNG_LOG_LOCAL3 = 19,
    NNG_LOG_LOCAL4 = 20,
    NNG_LOG_LOCAL5 = 21,
    NNG_LOG_LOCAL6 = 22,
    NNG_LOG_LOCAL7 = 23,
} nng_log_facility;

The nng_log_set_facility function can be used to set the facility that the application will use when emitting log messages. This should be called as part of initialization of the application, if logging is to be used.

The default facility is typically NNG_LOG_USER, but applications should select a value rather than relying upon the default.

Log Handlers

typedef void (*nng_logger)(nng_log_level level, nng_log_facility facility,
    const char *msgid, const char *msg);

void nng_null_logger(nng_log_level, nng_log_facility, const char *, const char *);
void nng_stderr_logger(nng_log_level, nng_log_facility, const char *, const char *);
void nng_system_logger(nng_log_level, nng_log_facility, const char *, const char *);

void nng_log_set_logger(nng_logger logger);

Log handlers are responsible for actually processing the logged messages.

The nng_log_set_logger function installs the named logger, of type nng_logger, as the log handler. The function logger will be called when any message is meant to be processed. (Messages are first filtered by severity, then formatted, before calling the logger.)

Any previously installed logger is replaced by logger.

The nng_null_logger function is an implementation of nng_logger that simply discards the content. This is the default logger, so logging is disabled by default.

The nng_stderr_logger function is an implementation that logs messages to the standard error stream. It will attempt to colorize messages by the severity, if the standard error is a terminal device. This can be suppressed by setting either the NO_COLOR or NNG_LOG_NO_COLOR environment variables.

The nng_system_logger attempts to use an appropriate system facility to log messages. For POSIX systems, this means using syslog to process the messages. For other systems the defauilt behavior may be the same as nng_stderr_logger.

See Also

The Syslog Protocol upon which this is based is documented in the following two IETF RFCS,

  • R. Gerhards, RFC 5424, The Syslog Protocol, March 2009
  • C. Lonvick, RFC 3164, The BSD syslog Protocol, August 2001

Statistics

To facilitate debugging and support situations, the NNG library provides for collection and reporting of numerous statistics.

These statistics are organized in a tree, and include both values, and metadata describing the statistics. In order to be efficient and minimize the impact of maintaining statistics, an explicit snapshot of statistics must be taken, and that snapshot can then be processed.

note

Statistics may be disabled by build-time configuration options, in order to reduce program size and run-time overheads.

Statistic Structure

typedef struct nng_stat nng_stat;

The nng_stat structure represents a statistic, which is a single value collected at a specific point in time.

This structure has meta-data describing the value, the value itself, and links to any sibling or child statistics.

note

The presence, name, and semantics of any given statistic are subject to change at any time and without notice.

Collecting a Snapshot

int nng_stats_get(nng_stat **statsp);

The nng_stats_get function takes a snapshot of the statistics for the system and returns it through the pointer statsp. This function may return NNG_ENOMEM if memory is exhausted, or NNG_ENOTSUP if the statistics support is not enabled in the build, but is otherwise expected to return zero.

Freeing a Snapshot

void nng_stats_free(nng_stat *stat);

The nng_stats_free function deallocates the snapshot referenced by stat.

important

The stat must be root of the statistics tree, i.e. the value that was returned through statsp using the function nng_stats_get.

Traversing the Tree

const nng_stat *nng_stat_child(const nng_stat *stat);
const nng_stat *nng_stat_next(const nng_stat *stat);
const nng_stat *nng_stat_parent(const nng_stat *stat);

Traversing the tree of statistics is done using the nng_stat_child and nng_stat_next functions.

The nng_stat_child function returns either the first child of stat, or NULL if the stat has no children.

The nng_stat_next function returns the nearest sibling to the right of stat, or NULL if stat has no more siblings to the right.

The nng_stat_parent function returns the parent of stat, or NULL if stat is the root of the statistics tree.

Finding a Statistic

const nng_stat *nng_stat_find(const nng_stat *stat, const char *name);
const nng_stat *nng_stat_find_dialer(const nng_stat *stat, nng_dialer dialer);
const nng_stat *nng_stat_find_listener(const nng_stat *stat, nng_dialer listener);
const nng_stat *nng_stat_find_socket(const nng_stat *stat, nng_dialer socket);

Sometimes it is easiest to search for a specific statistic, matching by name, or possibly to find the tree of statistics associated iwth a specific socket, dialer, or listener.

The nng_stat_find functions are provided for this purpose.

The nng_stat_find function returns the first statistic within the subtree of statistics stat, with the given name. If no such statistic can be found, NULL is returned.

The nng_stat_find_dialer, nng_stat_find_listener, and nng_stat_find_socket return the statistics subtree for the given dialer, listener, or socket object. If no such statistic can be found, then they return NULL. These functions should be provided the root of the statistic tree, in order to ensure that they can find the desired object.

Statistic Identification

const char *nng_stat_name(const nng_stat *stat);
const char *nng_stat_desc(const nng_stat *stat);

Every statistic has a name, returned by nng_stat_name, and a description, returned by nng_stat_desc. Descriptions are human-readable text, which might be useful for display.

Statistic Type

int nng_stat_type(const nng_stat *stat);

The function nng_stat_type returns the type of the statistic. The type of a statistic determines the nature of the value, and which function can be used to obtain that value.

  • NNG_STAT_SCOPE: The statistic does not carry any real value, but is used for grouping related statistics together. This is a nexus in the statistics tree.

  • NNG_STAT_COUNTER: The statistic is a counter that only increments. Usually the change in the value of the statistic is more interesting (as a rate) than the absolute value at any given time. The value should be obtained using nng_stat_value. The units will be given by the value returned from nng_stat_unit.

  • NNG_STAT_LEVEL: The statistic represnts a measured value which corresponds to a specific value at a specific time. For example, this may represent the number of messages currently queued for some operation, or the link speed of a network interface. Most often the absolute value is more interesting than the change in the value over time. Again the value can be obtained with nng_stat_value, and any appropriate unit of measurement with nng_stat_unit.

  • NNG_STAT_STRING: The statistic is a string, such as a name. The value of the string can be obtained with nng_stat_string. The value of this string will remain valid until the snapshot is deallocated with nng_stats_free.

  • NNG_STAT_BOOLEAN: The value of the statistic is a truth value (either true or false) and can be obtained with nng_stat_bool.

  • NNG_STAT_ID: The value of the statistic is a numeric identifier, such as a socket identifier. The value can be obtained with nng_stat_value, and will be fixed for the life of the statistic.

Statistic Value

uint64_t nng_stat_value(const nng_stat *stat);
const char *nng_stat_string(const nng_stat *stat);
bool nng_stat_bool(const nng_stat *stat);

These functions return the value associated with the statistic.

The nng_stat_value function returns the the numeric value for the statistic stat of type NNG_STAT_COUNTER, NNG_STAT_LEVEL, or NNG_STAT_ID. If stat is not one of these types, then it returns zero.

The nng_stat_bool function returns the Boolean value (either true or false) for the statistic stat of type NNG_STAT_BOOLEAN. If the statistics is not of this type, then it returns false.

The nng_stat_string function returns a pointer to a string value for the statistic stat, of type NNG_STAT_STRING. This string will remain valud until the snapshot that stat was collected with is deallocated with nng_stats_free. If the statistic is not of type NNG_STAT_STRING, then NULL is returned.

Statistic Units

int nng_stat_unit(const nng_stat *stat);

For statistics of type NNG_STAT_COUNTER or NNG_STAT_LEVEL, it is often useful to know what that quantity being reported measures. The following units may be returned from nng_stat_unit for such a statistic:

  • NNG_UNIT_NONE: No unit is known or applies.
  • NNG_UNIT_BYTES: A count of bytes.
  • NNG_UNIT_MESSAGES: A count of messages.
  • NNG_UNIT_MILLIS: A count of milliseconds.
  • NNG_UNIT_EVENTS: A count of events of some type.

Statistic Timestamp

uint64_t nng_stat_timestamp(const nng_stat *stat);

Statistics have a timestamp indicating when the value was sampled, obtained via nng_stat_timestamp. The timestamp is given in in milliseconds since a reference time, and the reference time used here is the same reference time used for nng_clock.

See Also

nng_clock

Errors

typedef enum ... nng_err;

Many NNG functions can fail for a variety of reasons. These functions tend to return either zero on success, or a non-zero error code to indicate failure. 1

All these error codes are nng_err.

note

Many APIs are still using int, but the nng_err enumeration can be used instead, and will help with debugging.

Not every possible error code is defined here, as sometimes an underlying system or library error code is “wrapped”.

Human Readable Error Message

const char *nng_strerror(nng_err err);

The nng_strerror returns the human-readable description of the given error in err.

The error message returned is a fixed NUL-terminated string and may be located in read-only memory.

The returned error message is provided in US English, but in the future locale-specific strings may be presented instead.

note

The specific strings associated with specific error messages are subject to change. Therefore applications must not depend on the message, but may use them verbatim when supplying information to end-users, such as in diagnostic messages or log entries.

List of Errors

ErrorValueDescription
NNG_EINTR1Operation interrupted.
NNG_ENOMEM2Out of memory, or other resource exahusted.
NNG_EINVAL3Invalid argument. The arguments are invalid or malformed somehow.
NNG_EBUSY4Resource busy.
NNG_ETIMEDOUT5Timed out. The operation took longer than the allotted time.
NNG_ECONNREFUSED6Connection refused. Usually indicates the wrong address or a server is running.
NNG_ECLOSED7Object closed. Typically the socket is closed.
NNG_EAGAIN8Try again. Typcally for a non-blocking operation that might succeed later.
NNG_ENOTSUP9Not supported. Perhaps the protocol or transport is not supported, or the operation is not not supported with the transport or protocol.
NNG_EADDRINUSE10Address in use. The network address is already used by another process. Most often this is seen for listeners.
NNG_ESTATE11Incorrect state. The operation cannot be performed in the current state, such as trying to send a response when no request has yet been received.
NNG_ENOENT12Entry not found (no such object.) Can also indicate that a file does not exist.
NNG_EPROTO13Protocol error. Typically this indicates incorrect messages over a network.
NNG_EUNREACHABLE14Destination unreachable.
NNG_EADDRINVAL15Address invalid. Like NNG_EINVAL, but only for network addresses.
NNG_EPERM16Permission denied.
NNG_EMSGSIZE17Message too large.
NNG_ECONNABORTED18Connection aborted. A connection attempt was aborted locally.
NNG_ECONNRESET19Connection reset. The remote peer reset the connection unexpectedly.
NNG_ECANCELED20Operation canceled. Typically as a result of nng_aio_cancel or similar.
NNG_ENOFILES21Out of files. Either the destination file system cannot store files, or all available file handles are used.
NNG_ENOSPC22Out of space. Destination table or filesystem is full.
NNG_EEXIST23Resource already exists.
NNG_EREADONLY24Read only resource. An attempt to modify a read-only file or other object.
NNG_EWRITEONLY25Write only resource. A read operation failed because the object only supports writes.
NNG_ECRYPTO26Cryptographic error. Usually indicates an invalid key was used for TLS.
NNG_EPEERAUTH27Peer could not be authenticated.
NNG_EBADTYPE30Incorrect type. A type-specific function was used for an object of the wrong type.
NNG_ECONNSHUT31Connection shutdown. The connection was shut down and cannot be used.
NNG_ESTOPPED1000Operation stopped. The operation was stopped with nng_aio_stop or [nng_aio_close].
NNG_EINTERNAL1000An unidentifier internal error occurred.
NNG_ESYSERR0x10000000 - 0x1FFFFFFFAn unidentified system error occurred. These are errors reported by the operating system.
NNG_ETRANERR0x20000000 - 0x2FFFFFFFAn unidentified transport error occurred.


1: This convention goes back to UNIX system calls, which behave the same way, but NNG does not use a separate errno variable.

Streams

NNG provides a common streams API for working with byte-oriented streams. In NNG, streams are bidirectional connections for exchanging a stream of bytes.

Some common examples of streams are TCP connections, UNIX domain sockets, Windows named pipes, and WebSockets.

The API documented here is to facilitate applications that wish to work with these at a lower-level than Scalability Protocols, in a way that is both portable and agnostic about the specific underlying transport mechanism.

tip

When working with Scalability Protocols directly, it is unlikely that there will be any need for using these Streams APIs.

Stream Type

typedef struct nng_stream nng_stream

The base nng_stream type represents a bidirectional, byte-oriented, reliable connection.

note

The nng_stream object is used for raw byte stream connections, and should not be confused with a pipe object created on a socket using the nng_listen, nng_dial or related functions.

Sending and Receiving Data

void nng_stream_send(nng_stream *s, nng_aio *aio);
void nng_stream_recv(nng_stream *s, nng_aio *aio);

The nng_stream_send function starts sending data asynchronously over the stream s. The data is sent from the scatter/gather vector located in the nng_aio aio, which must have been previously set using nng_aio_set_iov.

The nng_stream_recv function starts receiving data [asynchronously over the stream s into the scatter/gather vector located in the nng_aio aio, which must have been previously set using nng_aio_set_iov.

These functions return immediately, with no return value. Completion of the operation is signaled via the aio, and the final result may be obtained via nng_aio_result.

The I/O operation may complete as soon as at least one byte has been transferred, or an error has occurred. Therefore, the number of bytes transferred may be less than requested. The actual number of bytes transferred can be determined with nng_aio_count.

Closing a Stream

void nng_stream_close(nng_stream *s);
void nng_stream_stop(nng_stream *s);
void nng_stream_free(nng_stream *s);

The nng_stream_close function closes a stream, but does not destroy it. This function returns immediately. Operations that are pending against the stream, such as nng_stream_send or nng_stream_recv operations will be canceled asynchronously, if possible. Those operations will result in NNG_ECLOSED.

The nng_stream_stop function not only closes the stream, but waits for any operations pending against it to complete, and for any underlying asynchronous registered I/O to be fully deregistered. As some systems use separate threads for asynchronous I/O, stopping the stream is necessary before those resources can be freed. Until the stream is stopped, there could still be I/O operations in flight, making it unsafe to deallocate memory.

The nng_stream_free function stops the stream like nng_stream_stop, but also deallocates the stream itself.

note

Because nng_stream_stop and nng_stream_free both may block waiting for outstanding I/O to complete or be aborted, these functions are unsafe to call from functions that may not block, such as the completion function registered with an nng_aio when it is created.

Getting Stream Options

int nng_stream_get_bool(nng_stream *s, const char *opt, bool *valp);
int nng_stream_get_int(nng_stream *s, const char *opt, int *valp);
int nng_stream_get_ms(nng_stream *s, const char *opt, nng_duration *valp);
int nng_stream_get_size(nng_stream *s, const char *opt, size_t *valp);
int nng_stream_get_addr(nng_stream *s, const char *opt, nng_sockaddr *valp);
int nng_stream_get_string(nng_stream *s, const char *opt, char **valp);

These functions are used to obtain value of an option named opt from the stream s, and store it in the location referenced by valp.

These functions access an option as a specific type. The transport layer will have details about which options are available, and which type they may be accessed using.

In the case of nng_stream_get_string, the string is created as if by nng_strdup, and must be freed by the caller using nng_strfree when no longer needed.

Stream Factories

typedef struct nng_stream_dialer nng_stream_dialer;
typedef struct nng_stream_listener nng_stream_listener;

The nng_stream_listener object and nng_stream_listener objects can be thought of as factories that create nng_stream streams.

The nng_stream_listener object a handle to a listener, which creates streams by accepting incoming connection requests. In a BSD socket implementation, this is the entity responsible for doing bind, listen and accept. Normally a listener may be used to accept multiple, possibly many, concurrent connections.

The nng_stream_dialer object is a handle to a dialer, which creates streams by making outgoing connection requests. While there isn’t a specific BSD socket analogue, this can be thought of as a factory for TCP sockets created by opening them with socket and then calling connect on them.

Creating a Stream Factory

int nng_stream_dialer_alloc(nng_stream_dialer **dialerp, const char *url);
int nng_stream_dialer_alloc_url(nng_stream_dialer **dialerp, const nng_url *url);
int nng_stream_listener_alloc(nng_stream_listener **lstenerp, const char *url);
int nng_stream_listener_alloc_url(nng_stream_listener **listenerp, const nng_url *url);

The nng_stream_dialer_alloc and nng_stream_dialer_alloc_url functions create a stream dialer, associated the URL specified by url represented as a string, or as an nng_url object, respectively. The dialer is returned in the location dialerp references.

The nng_stream_listener_alloc and nng_stream_listener_alloc_url functions create a stream listener, associated the URL specified by url represented as a string, or as an nng_url object, respectively. The listener is returned in the location listenerp references.

Example 1: Creating a TCP Listener

This shows creating a TCP listener that listens on INADDR_ANY, port 444.

nng_listener listener;
int rv = nng_stream_listener_alloc(&listener, "tcp://:444");

Closing a Stream Factory

void nng_stream_dialer_close(nng_stream_listener *dialer);
void nng_stream_dialer_stop(nng_stream_listener *dialer);
void nng_stream_dialer_free(nng_stream_listener *dialer);
void nng_stream_listener_close(nng_stream_listener *listener);
void nng_stream_listener_stop(nng_stream_listener *listener);
void nng_stream_listener_free(nng_stream_listener *listener);

The nng_stream_dialer_close and nng_stream_listener_close functions close the stream dialer or listener, preventing it from creating new connections. This will generally include closing any underlying file used for creating such connections. However, some requests may still be pending when this function returns, as it does not wait for the shutdown to complete.

The nng_stream_dialer_stop and nng_stream_listener_stop functions performs the same action, but also wait until all outstanding requests are serviced, and the dialer or listener is completely stopped. Because they blocks, these functions must not be called in contexts where blocking is not allowed.

The nng_stream_dialer_free and nng_stream_listener_free function performs the same action as nng_stream_dialer_stop or nng_stream_listener_stop, but also deallocates the dialer or listener, and any associated resources.

tip

A best practice for shutting down an application safely is to stop everything before deallocating. This ensures that no callbacks are running that could reference an object after it is deallocated.

Making Outgoing Connections

void nng_stream_dialer_dial(nng_stream_dialer *dialer, nng_aio *aio);

The nng_stream_dialer_dial initiates an outgoing connection asynchronously, using the nng_aio aio. If it successfully establishes a connection, it creates an nng_stream, which can be obtained as the first output result on aio using the nng_aio_get_output function with index zero.

tip

An nng_stream_dialer can be used multiple times to make multiple concurrent connection requests, but they all must reference the same URL.

Example 3: Connecting to Google

This demonstrates making an outbound connection to “google.com” on TCP port 80. Error handling is elided for clarity.

nng_aio *aio;
nng_stream_dialer *dialer;
nng_stream *stream;

nng_stream_dialer_alloc(&dialer, "tcp://google.com:80");

nng_aio_alloc(&aio, NULL, NULL);

// make a single outbound connection
nng_stream_dialer_dial(dialer, aio);
nng_aio_wait(aio); // wait for the asynch operation to complete
if (nng_aio_result(aio) != 0) {
    // ... handle the error
}
stream = nng_aio_get_output(aio, 0);

Accepting Incoming Connections

int nng_stream_listener_listen(nng_stream_listener *listener);
void nng_stream_listener_accept(nng_stream_listener *listener, nng_aio *aio);

Accepting incoming connections is performed in two steps. The first step, nng_stream_listener_listen is to setup for listening. For a TCP implementation of this, for example, this would perform the bind and the listen steps. This will bind to the address represented by the URL that was specific when the listener was created with nng_stream_listener_alloc.

In the second step, nng_stream_listener_accept accepts an incoming connection on listener asynchronously, using the nng_aio aio. If an incoming connection is accepted, it will be represented as an nng_stream, which can be obtained from the aio as the first output result using the nng_aio_get_output function with index zero.

Example 3: Accepting an Inbound Stream

For clarity this example uses a synchronous approach using nng_aio_wait, but a typical server application would most likely use a callback to accept the incoming stream, and start another instance of nng_stream_listener_accept.

nng_aio *aio;
nng_listener *listener;
nng_stream *stream;

nng_stream_listener_alloc(&listener, "tcp://:8181");
nng_aio_alloc(&aio, NULL, NULL); // normally would use a callback

// listen (binding to the URL in the process)
if (nng_stream_listener_listen(listener)) {
    // ... handle the error
}

// now accept a single incoming connection as a stream object
nng_stream_listener_accept(l, aio);
nng_aio_wait(aio); // wait for the asynch operation to complete
if (nng_aio_result(aio) != 0) {
    // ... handle the error
}
stream = nng_aio_get_output(aio, 0);

Stream Factory Options

int nng_stream_dialer_get_addr(nng_stream_dialer *dialer, const char *opt, nng_sockaddr *valp);
int nng_stream_dialer_get_bool(nng_stream_dialer *dialer, const char *opt, bool *valp);
int nng_stream_dialer_get_int(nng_stream_dialer *dialer, const char *opt, int *valp);
int nng_stream_dialer_get_ms(nng_stream_dialer *dialer, const char *opt, nng_duration *valp);
int nng_stream_dialer_get_size(nng_stream_dialer *dialer, const char *opt, size_t *valp);
int nng_stream_dialer_get_string(nng_stream_dialer *dialer, const char *opt, char **valp);

int nng_stream_listener_get_addr(nng_stream_listener *listener, const char *opt, nng_sockaddr *valp);
int nng_stream_listener_get_bool(nng_stream_listener *listener, const char *opt, bool *valp);
int nng_stream_listener_get_int(nng_stream_listener *listener, const char *opt, int *valp);
int nng_stream_listener_get_ms(nng_stream_listener *listener, const char *opt, nng_duration *valp);
int nng_stream_listener_get_size(nng_stream_listener *listener, const char *opt, size_t *valp);
int nng_stream_listener_get_string(nng_stream_listener *listener, const char *opt, char **valp);

int nng_stream_dialer_set_addr(nng_stream_dialer *dialer, const char *opt, const nng_sockaddr *val);
int nng_stream_dialer_set_bool(nng_stream_dialer *dialer, const char *opt, bool val);
int nng_stream_dialer_set_int(nng_stream_dialer *dialer, const char *opt, int val);
int nng_stream_dialer_set_ms(nng_stream_dialer *dialer, const char *opt, nng_duration val);
int nng_stream_dialer_set_size(nng_stream_dialer *dialer, const char *opt, size_t val);
int nng_stream_dialer_set_string(nng_stream_dialer *dialer, const char *opt, const char *val);

int nng_stream_listener_set_addr(nng_stream_listener *listener, const char *opt, const nng_sockaddr *val);
int nng_stream_listener_set_bool(nng_stream_listener *listener, const char *opt, bool val);
int nng_stream_listener_set_int(nng_stream_listener *listener, const char *opt, int val);
int nng_stream_listener_set_ms(nng_stream_listener *listener, const char *opt, nng_duration val);
int nng_stream_listener_set_size(nng_stream_listener *listener, const char *opt, size_t val);
int nng_stream_listener_set_string(nng_stream_listener *listener, const char *opt, const char *val);

These functions are used to retrieve or change the value of an option named opt from the stream dialer or listener. The nng_stream_dialer_get_ and nng_stream_listener_get_ function families retrieve the value, and store it in the location valp references. The nng_stream_dialer_set_ and nng_stream_listener_set_ function families change the value for the dialer or listener, taking it from val.

These functions access an option as a specific type. The transport layer will have details about which options are available, and which type they may be accessed using.

In the case of nng_stream_dialer_get_string and nng_stream_listener_get_string, the string is created as if by nng_strdup, and must be freed by the caller using nng_strfree when no longer needed.

In the case of nng_stream_dialer_set_string and nng_stream_listener_set_string, the string contents are copied if necessary, so that the caller need not retain the value referenced once the function returns.

In the case of nng_stream_dialer_set_addr and nng_stream_listener_set_addr, the contents of addr are copied if necessary, so that the caller need not retain the value referenced once the function returns.

Example 4: Socket Activation

Some nng_stream_listener objects, depending on the underlying transport and platform, can support a technique known as “socket activation”, where the file descriptor used for listening and accepting is supplied externally, such as by a system service manager. In this case, the application supplies the file descriptor or SOCKET object using the NNG_OPT_LISTEN_FD option, instead of calling nng_stream_listener_listen.

tip

Scalability Protocols transports based upon stream implementations that support socket activation can also benefit from this approach.

nng_stream_listener *listener;
int fd;

// This is a systemd API, not part of NNG.
// See systemd documentation for an explanation.
// fd at this point has already had bind() and listen() called.
fd = SD_LISTEN_FDS_START + 0;

nng_stream_listener_alloc(&listener, "tcp://");
nng_stream_listener_set_int(listener, NNG_OPT_LISTEN_FD, fd);

// can now start doing nng_stream_listener_accept...

TLS Configuration

int nng_stream_dialer_get_tls(nng_stream_listener *dialer, nng_tls_config **tlsp);
int nng_stream_dialer_set_tls(nng_stream_listener *dialer, nng_tls_config *tls);
int nng_stream_listener_get_tls(nng_stream_listener *listener, nng_tls_config **tlsp);
int nng_stream_listener_set_tls(nng_stream_listener *listener, nng_tls_config *tls);

Both nng_stream_dialer and nng_stream_listener objects may support configuration of TLS parameters. The nng_stream_dialer_set_tls and nng_stream_listener_set_tls functions support setting the configuration of a nng_tls_config object supplied by tls on dialer or listener. This must be performed before the listener starts listening with nng_stream_listener_listen, or the dialer starts an outgoing connection as a result of nng_stream_dialer_dial.

The configuration object that was previously established (which may be a default if one was not explicitly configured) can be obtained with the nng_stream_dialer_get_tls and nng_stream_listener_get_tls. They will return a pointer to the nng_tls_config object in question at the location referenced by tlsp.

note

TLS configuration cannot be changed once it has started being used by a listener or dialer. This applies to both configuring a different TLS configuration object, as well as mutating the existing nng_tls_config object.

HTTP Support

NNG offers support for creation of HTTP clients, and servers. NNG supports HTTP/1.1 at present, and supports a subset of functionality, but the support should be sufficient for simple clients, REST API servers, static content servers, and gateways between HTTP and and other protocols. It also provides support for WebSocket based connections.

HTTP follows a request/reply model, where a client issues a request, and the server is expected to reply. Every request is answered with a single reply.

Header File

#include <nng/http.h>

Unlike the rest of NNG, the HTTP API in NNG requires including nng/http.h. It is not necessary to include the main nng/nng.h header, it will be included transitively by nng/http.h.

Connection Object

typedef struct nng_http nng_http;

The nng_http object represents a single logical HTTP connection to the server. For HTTP/1.1 and earlier, this will correspond to a single TCP connection, but the object also contains state relating to the transaction, such as the hostname used, HTTP method used, request headers, response status, response headers, and so forth.

An nng_http object can be reused, unless closed, so that additional transactions can be performed after the first transaction is complete.

At any given point in time, an nng_http object can only refer to a single HTTP transaction. In NNG, these nng_http objects are used in both the client and server APIs.

The nng_http object is created by either nng_http_client_connect or by an HTTP server object which then passes it to an [nng_http_handler] callback function.

HTTP Method

void nng_http_set_method(nng_http *conn, const char *method);
const char *nng_http_get_method(nng_http *conn);

Each HTTP transaction has a single verb, or method, that is used. The most common methods are “GET”, “HEAD”, and “POST”, but a number of others are possible.

The nng_http_set_method function specifies the HTTP method to use for the transaction. The default is “GET”. HTTP methods are case sensitive, and generally upper-case, such as “GET”, “POST”, “HEAD”, and so forth. This function silently truncates any method to 32-characters. (There are no defined methods longer than this.)

The nng_http_get_method function is used, typically on a server, to retrieve the method the client set when issuing the transaction.

HTTP URI

nng_err nng_http_set_uri(nng_http *conn, const char *uri, const char *query);
const char *nng_http_get_uri(nng_http *conn);

The nng_http_set_uri function sets the URI, which normally appears like a path such as “/docs/index.html”, for the next transaction on conn. It sets the URI to uri, and, if query is not NULL, also appends the contents of query, separated by either the ‘?’ or ‘&’ character, depending on whether uri already contains a query string. It may return NNG_ENOMEM, or NNG_EMSGSIZE if the the result is too long, or NNG_EINVAL if there is some other problem with the URI.

note

The uri and query must be already percent-encoded if necessary.

The nni_http_get_uri function is used to obtain the URI that was previously set by nng_http_set_uri. If the URI is unset (such as for a freshly created connection), then it returns NULL. The returned value will have any query concentated, for example “/api/get_user.cgi?name=garrett”.

HTTP Version

nng_err nng_http_set_version(nng_http *conn, const char *version);
const char *nng_http_get_version(nng_http *conn);

The nng_http_set_version function is used to select the HTTP protocol version to use for the exchange. At present, only the values NNG_HTTP_VERSION_1_0 and NNG_HTTP_VERSION_1_1 (corresponding to “HTTP/1.0” and “HTTP/1.1”) are supported. NNG will default to using “HTTP/1.1” if this function is not called. If an unsupported version is supplied, NNG_ENOTSUP will be returned, otherwise zero.

The nng_http_get_version function is used to determine the version the client selected. Normally there is little need to use this, but there are some subtle semantic differences between HTTP/1.0 and HTTP/1.1.

tip

There are few, if any, remaining HTTP/1.0 implementations that are not also capable of HTTP/1.1. It might be easiest to just fail any request coming in that is not HTTP/1.1.

note

NNG does not support HTTP/2 or HTTP/3 at this time.

HTTP Status

typedef enum ... nng_http_status;
nng_http_status nng_http_get_status(nng_http *conn);
const char *nng_http_get_reason(nng_http_conn *conn);
void nng_http_set_status(nng_http *conn, nng_http_status status, const char *reason);

The nng_http_get_status function obtains the numeric code (typipcally numbered from 100 through 599) returned by the server in the last exchange on conn. (If no exchange has been performed yet, the result is undefined.) The value is returned as an nng_http_status.

A descriptive message matching the status code is returned by nng_http_get_reason.

The nng_http_set_status function is used on a server in a handler callback to set the status code that will be reported to the client to status, and the associated text (reason) to reason. If reason is NULL, then a built in reason based on the status will be used instead.

tip

Callbacks used on the server may wish to use nng_http_server_set_error or nng_http_server_set_redirect instead of nng_http_set_status, because those functions will also set the response body to a suitable HTML document for display to users.

Status codes are defined by the IETF. Here are defininitions that NNG provides for convenience:

NameCodeReason TextNotes
NNG_HTTP_STATUS_CONTINUE100ContinuePartial transfer, client may send body.
NNG_HTTP_STATUS_SWITCHING101Switching ProtocolsUsed when upgrading or hijacking a connection.
NNG_HTTP_STATUS_PROCESSING102Processing
NNG_HTTP_STATUS_OK200OKSuccessful result.
NNG_HTTP_STATUS_CREATED201CreatedResource created successfully.
NNG_HTTP_STATUS_ACCEPTED202CreatedRequest accepted for future processing.
NNG_HTTP_STATUS_NOT_AUTHORITATIVE203Not AuthoritativeRequest successful, but modified by proxy.
NNG_HTTP_STATUS_NO_CONTENT204No ContentRequest successful, no content returned.
NNG_HTTP_STATUS_RESET_CONTENT205Reset ContentRequest successful, client should reload content.
NNG_HTTP_STATUS_PARTIAL_CONTENT206Partial ContentResponse to a range request.
NNG_HTTP_STATUS_MULTI_STATUS207Multi-StatusUsed with WebDAV.
NNG_HTTP_STATUS_ALREADY_REPORTED208Already ReportedUsed with WebDAV.
NNG_HTTP_STATUS_IM_USED226IM UsedUsed with delta encodings, rarely supported.
NNG_HTTP_STATUS_MULTIPLE_CHOICES300Multiple ChoicesMultiple responses possible, client should choose.
NNG_HTTP_STATUS_MOVED_PERMANENTLY301Moved PermanentlyPermanent redirection, may be saved by client.
NNG_HTTP_STATUS_FOUND302FoundTemporary redirection, client may switch to GET.
NNG_HTTP_STATUS_SEE_OTHER303See OtherRedirect, perhaps after a success POST or PUT.
NNG_HTTP_STATUS_NOT_MODIFIED304Not ModifiedResource not modified, client may use cached version.
NNG_HTTP_STATUS_USE_PROXY305Use Proxy
NNG_HTTP_STATUS_TEMPORARY_REDIRECT307Temporary RedirectTemporary redirect, preserves method.
NNG_HTTP_STATUS_PERMANENT_REDIRECT308Permanent RedirectPermanent redirect.
NNG_HTTP_STATUS_BAD_REQUEST400Bad RequestGeneric problem with the request.
NNG_HTTP_STATUS_UNAUTHORIZED401UnauthorizedIndicates a problem with authentication.
NNG_HTTP_STATUS_PAYMENT_REQUIRED402Payment Required
NNG_HTTP_STATUS_FORBIDDEN403ForbiddenNo permission to access resource.
NNG_HTTP_STATUS_NOT_FOUND404Not FoundResource does not exist.
NNG_HTTP_STATUS_METHOD_NOT_ALLOWED405Method Not AllowedResource does not support the method.
NNG_HTTP_STATUS_METHOD_NOT_ACCEPTABLE406Not AcceptableCould not satisfy accept requirements.
NNG_HTTP_STATUS_PROXY_AUTH_REQUIRED407Proxy Authentication RequiredProxy requires authentication.
NNG_HTTP_STATUS_REQUEST_TIMEOUT408Request TimeoutTimed out waiting for request.
NNG_HTTP_STATUS_CONFLICT409ConflictConflicting request.
NNG_HTTP_STATUS_GONE410GoneResource no longer exists.
NNG_HTTP_STATUS_LENGTH_REQUIRED411Length RequiredMissing Content-Length.
NNG_HTTP_STATUS_PRECONDITION_FAILED412Precondition Failed
NNG_HTTP_STATUS_CONTENT_TOO_LARGE413Content Too Large
NNG_HTTP_STATUS_URI_TOO_LONG414URI Too Long
NNG_HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE415Unsupported Media Type
NNG_HTTP_STATUS_RANGE_NOT_SATISFIABLE416Range Not Satisfiable
NNG_HTTP_STATUS_EXPECTATION_FAILED417Expectation Failed
NNG_HTTP_STATUS_TEAPOT418I Am A TeapotRFC 2324.
NNG_HTTP_STATUS_UNPROCESSABLE_ENTITY422Unprocessable Entity
NNG_HTTP_STATUS_LOCKED423Locked
NNG_HTTP_STATUS_FAILED_DEPENDENCY424Failed Dependency
NNG_HTTP_STATUS_TOO_EARLY425Too Early
NNG_HTTP_STATUS_UPGRADE_REQUIRED426Upgrade Required
NNG_HTTP_STATUS_PRECONDITION_REQUIRED428Precondition Required
NNG_HTTP_STATUS_TOO_MANY_REQUESTS429Too Many Requests
NNG_HTTP_STATUS_HEADERS_TOO_LARGE431Headers Too Large
NNG_HTTP_STATUS_UNAVAIL_LEGAL_REASONS451Unavailabe For Legal Reasons
NNG_HTTP_STATUS_INTERNAL_SERVER_ERROR500Internal Server Error
NNG_HTTP_STATUS_NOT_IMPLEMENTED501Not ImplementedServer does not implement method.
NNG_HTTP_STATUS_BAD_GATEWAY502Bad Gateway
NNG_HTTP_STATUS_SERVICE_UNAVAILALE503Service Unavailable
NNG_HTTP_STATUS_GATEWAY_TIMEOUT504Gateway TImeout
NNG_HTTP_STATUS_HTTP_VERSION_NOT_SUPP505HTTP Version Not Supported
NNG_HTTP_STATUS_VARIANT_ALSO_NEGOTIATES506Variant Also Negotiates
NNG_HTTP_STATUS_INSUFFICIENT_STORAGE507Variant Also Negotiates
NNG_HTTP_STATUS_LOOP_DETECTED508Loop Detected
NNG_HTTP_STATUS_NOT_EXTENDED510Not Extended
NNG_HTTP_STATUS_NETWORK_AUTH_REQUIRED511Network Authentication Required

Retrieving Headers

const char *nng_http_get_header(nng_http *conn, const char *key);
bool nng_next_header(nng_http *conn, const char **keyp, const char **valuep, void **next);

The nng_http_get_header returns the header value matching key that was received over conn, or NULL if no such header exists.

Thus, if conn is a client connection, then this function returns the the header value sent by the server as part of a response, whereas if it is a server connection, it returns the header value sent by the client as part of the request.

If multiple headers are present with the same key, they may be returned as a combined value, with individual values separated by commas, but this behavior is not guaranteed.

The nng_http_next_header function iterates over all the headers, using the same list that nng_http_get_header uses. To start, it is called with next initialized to NULL. If a header was found, then it returns true, and sets keyp and valuep to values containing the header name and value. It also updates next, which should be used for the next iteration.

Once nng_http_next_header returns false, further calls with the same parameters will continue to do so. The scan can be rest by setting next to NULL.

Modifying Headers

nng_err nng_http_add_header(nng_http *conn, const char *key, const char *val);
nng_err nng_http_set_header(nng_http *conn, const char *key, const char *val);
void nng_http_del_header(nng_http *conn, const char *key);

The nng_http_add_header, nng_http_set_header, and nng_http_del_header functions are used to add a modify either the request or response headers for conn prior to sending to the connected peer on conn.

Thus, if the conn is a client connection created by nng_http_client_connect, then the request headers are modified. Conversely, if it is a connection created by an HTTP server and used in a callback function, then the response headers are modified.

The nng_http_add_header function adds a header with the name key, and the value val, to the list of headers. In so doing, it may bring collapse multiple headers with the same name into a comma separated list, following the syntax specified in RFC 9110. The function may return NNG_ENOMEM, NNG_EMSGSIZE, or NNG_EINVAL.

The nng_http_set_header function adds the header if it does not already exist, but replaces any and all previously existing headers with the same name key, if they exist. In all other respects it behaves similarly to nng_http_add_header.

The nng_http_del_header removes all headers with name key.

note

Some HTTP headers have special semantics, such as the “Host”, “Content-Length”, and “Content-Type” headers. This implementation may apply those semantics, in order to conform to the specifications for HTTP, such as by guaranting that only a single instance of one of these headers is present.

Retrieving Body Content

void nng_http_get_body(nng_http_conn *conn, void **datap, size_t *sizep);

The nng_http_get_data obtains the most recently received request or response body. This will be NULL if the content has not been retrieved properly yet, or if the peer did not any content. (Some requests are defined to never have body content, such as “HEAD”.)

Storing Body Content

void nng_http_set_body(nng_http_conn *conn, void *data, size_t size);
void nng_http_copy_body(nng_http_conn *conn, const void *data, size_t size);

The nng_http_set_data function sets the outgoing body content to data, which must be size bytes long. The caller must ensure that data remains valid for the duration of the transaction.

The nng_http_copy_data function makes a copy of data, which will be freed automatically when the transaction is finished, but otherwise behaves like nng_http_set_data.

On client conn objects, these functions update the request object, but on server conn objects, they update the response object.

These functions also update the relevant “Content-Length” header.

note

The current framework does not support sending data via chunked transfer-encoding.

tip

It is a good idea to also set the Content-Type header.

Closing the Connection

void nng_http_close(nng_http *conn);

The nng_http_close function closes the supplied HTTP connection conn, including any disposing of any underlying file descriptors or related resources.

Once this function, no further access to the conn structure may be made.

Reset Connection State

void nng_http_reset(nng_http *conn);

The nng_http_reset function resets the request and response state of the the connection conn, so that it is just as if it had been freshly created with nng_http_client_connect or passed into a handler function for a server callback.

The intended purpose of this function is to clear the object state before reusing the conn for subsequent transactions.

Direct Read and Write

void nng_http_read(nng_http *conn, nng_aio *aio);
void nng_http_write(nng_http *conn, nng_aio *aio);
void nng_http_read_all(nng_http *conn, nng_aio *aio);
void nng_http_write_all(nng_http *conn, nng_aio *aio);

The nng_http_read and nng_http_write functions read or write data asynchronously from or to the connection conn, using the nng_iov that is set in aio with nng_aio_set_iov. These functions will complete as soon as any data is transferred. Use nng_aio_count to determine how much data was actually transferred.

The nng_http_read_all and nng_http_write_all functions perform the same task, but will keep resubmitting operations until the the entire amount of data requested by the nng_iov is transferred.

note

These functions perform no special handling for chunked transfers.

These functions are most likely to be useful after hijacking the connection with nng_http_hijack. They can be used to transfer request or response body data as well.

Hijacking Connections

nng_err nng_http_hijack(nng_http *conn);

TODO: This API will change to convert the conn into a stream object.

The nng_http_hijack function hijacks the connection conn, causing it to be disassociated from the HTTP server where it was created.

The purpose of this function is the creation of HTTP upgraders (such as WebSocket), where the underlying HTTP connection will be taken over for some other purpose, and should not be used any further by the server.

This function is most useful when called from a handler function. (See [nng_http_handler_alloc].)

note

It is the responsibility of the caller to dispose of the underlying connection when it is no longer needed. Furthermore, the HTTP server will no longer send any responses to the hijacked connection, so the caller should do that as well if appropriate. (See [nng_http_write_response].)

tip

This function is intended to facilitate uses cases that involve changing the protocol from HTTP, such as WebSocket. Most applications will never need to use this function.

Client API

The NNG client API consists of an API for creating connections, and an API for performing transactions on those connections.

Client Object

typedef struct nng_http_client nng_http_client;

The nng_http_client object is the client side creator for nng_http objects. It is analogous to a dialer object used elsewhere in NNG, but it specifically is only for HTTP.

Create a Client

void nng_http_client_alloc(nng_http_client *clientp, const nng_url *url);

The nng_http_client_alloc allocates an HTTP client suitable for connecting to the server identified by url and stores a pointer to it in the location referenced by clientp.

Destroy a Client

void nng_http_client_free(nng_http_client *client);

The nng_http_client_free connection destroys the client object and any of its resources.

note

Any connections created by nng_http_client_connect are not affected by this function, and must be closed explicitly as needed.

Client TLS

nng_err nng_http_client_get_tls(nng_http_client *client, nng_tls_config **tlsp);
nng_err nng_http_client_set_tls(nng_http_client *client, nng_tls_config *tls);

The nng_http_client_get_tls and nng_http_client_set_tls functions are used to retrieve or change the TLS configuration used when making outbound connections, enabling TLS as a result.

If TLS has not been previously configured on client, then nng_http_client_get_tls will return NNG_EINVAL. Both functions will return NNG_ENOTSUP if either HTTP or TLS is not supported.

Calling nng_http_client_set_tls invalidates any client previously obtained with nng_http_client_get_tls, unless a separate hold on the object was obtained.

Once TLS is enabled for an nng_http_client, it is not possible to disable TLS.

note

The TLS configuration itself cannnot be changed once it has been used to create a connection, such as by calling nng_http_client_connect, but a new one can be installed in the client. Existing connections will use the TLS configuration that there were created with.

Creating Connections

#include <nng/http.h>

void nng_http_client_connect(nng_http_client *client, nng_aio *aio);

The nng_http_client_connect function makes an outgoing connection to the server configured for client, and creates an nng_http object for the connection.

This is done asynchronously, and when the operation succeseds the connection may be retried from the aio using nng_aio_get_output with index 0.

Example 1: Connecting to Google

nng_aio *aio;
nng_url *url;
nng_http_client *client;
nng_http *conn;
nng_err rv;

// Error checks elided for clarity.
nng_url_parse(&url, "http://www.google.com");
nng_aio_alloc(&aio, NULL, NULL);
nng_http_client_alloc(&client, url);

nng_http_client_connect(client, aio);

// Wait for connection to establish (or attempt to fail).
nng_aio_wait(aio);

if ((rv = nng_aio_result(aio)) != 0) {
    printf("Connection failed: %s\n", nng_strerror(rv));
} else {
    // Connection established, get it.
    conn = nng_aio_get_output(aio, 0);

    // ... do something with it here

    // Close the connection when done to avoid leaking it.
    nng_http_close(conn);
}

Preparing a Transaction

Sending the Request

void nng_http_write_request(nng_http *conn, nng_aio *aio);

The nng_http_write_request function starts an asynchronous write of the HTTP request associated with conn. The entire request is sent, including headers, and if present, the request body data. (The request body can be set with [nng_http_set_data] or [nng_http_copy_data].)

This function returns immediately, with no return value. Completion of the operation is signaled via the aio, and the final result may be obtained via nng_aio_result.

tip

Consider using the [nng_http_transact] function, which provides a simpler interface for performing a complete HTTP client transaction.

Obtaining the Response

void nng_http_read_response(nng_http *conn, nng_aio *aio);

The nng_http_read_response function starts an asynchronous read from the HTTP connection conn, reading an HTTP response into the response associated with conn, including all of the related headers.

It does not transfer any response body. To do that, use nng_http_read_all or nng_http_read.

note

At this time we have no API support for reading chunked transfers directly. Applications that need to do so may use the direct read functions.

tip

An easier one-shot method for many use cases might be [nng_http_transact].

Submitting the Transaction

void nng_http_transact(nng_http *conn, nng_aio *aio);

The HTTP request is issued, and the response processed, asynchronously by the nng_http_transact function. When the function is complete, the aio will be notified.

The nng_http_transact function is used to perform a complete HTTP exchange over the connection conn, sending the request and attached body data to the remote server, and reading the response.

The entire response is read, including any associated body, which can subsequently be obtained using [nng_http_get_data].

This function is intended to make creation of client applications easier, by performing multiple asynchronous operations required to complete an entire HTTP transaction.

If an error occurs, the caller should close conn with nng_http_close, as it may not necessarily be usable with other transactions.

warning

If the remote server tries to send an extremely large buffer, then a corresponding allocation will be made, which can lead to denial of service attacks. Client applications should take care to use this only with reasonably trust-worthy servers.

note

A given connection conn should be used with only one operation or transaction at a time as HTTP/1.1 has no support for request interleaving.

This function returns immediately, with no return value. Completion of the operation is signaled via the aio, and the final result may be obtained via [nng_aio_result()].

Response Body

Server API

Handlers

typedef struct nng_http_handler nng_http_handler;

An nng_http_handler encapsulates a function used used to handle incoming requests on an HTTP server, routed based on method and URI, and the parameters used with that function.

Every handler has a Request-URI to which it refers, which is determined by the path argument. Only the path component of the Request URI is considered when determining whether the handler should be called.

This implementation limits the path length to 1024 bytes, including the zero termination byte. This does not prevent requests with much longer URIs from being supported, but doing so will require setting the handler to match a parent path in the tree using [nng_http_handler_set_tree].

tip

The NNG HTTP framework is optimized for URLs shorter than 200 characters.

Additionally each handler has a method it is registered to handle (the default is “GET” andc can be changed with [nng_http_handler_set_method]), and optionally a “Host” header it can be matched against (see [nng_http_handler_set_host]).

In some cases, a handler may reference a logical tree rather (directory) rather than just a single element. (See [nng_http_handler_set_tree]).

Implementing a Handler

typedef void (*nng_http_hander_func)(nng_http_conn *conn, void *arg, nng_aio *aio);

nng_err nng_http_handler_alloc(nng_http_handler **hp, const char *path, nng_http_handler_func cb);

The nng_http_handler_alloc function allocates a generic handler which will be used to process requests coming into an HTTP server. On success, a pointer to the handler is stored at the located pointed to by hp.

The handler function is specified by cb. This function uses the asynchronous I/O framework.

The function receives the connection on conn, and an optional data pointer that was set previously with [nng_http_handler_set_data] as the second argument. The final argument is the nng_aio aio, which must be “finished” to complete the operation.

The handler may call [nng_http_write_response] to send the response, or it may simply let the framework do so on its behalf. The server will perform this step if the callback has not already done so.

Response headers may be set using nng_http_set_header, and request headers may be accessed by using nng_http_get_header.

Likewise the request body may be accessed, using nng_http_get_body, and the response body may be set using either nng_http_set_body or nng_http_copy_body.

note

The request body is only collected for the handler if the [nng_http_handler_collect_body] function has been called for the handler.

The HTTP status should be set for the transaction using nng_http_set_status.

Finally, the handler should finish the operation by calling the nng_aio_finish function after having set the status to [NNG_OK]. If any other status is set on the aio, then a generic 500 response will be created and sent, if possible, and the connection will be closed.

The aio may be scheduled for deferred completion using the nng_aio_start.

Serving Directories and Files

nng_err nng_http_handler_alloc_directory(nng_http_handler **hp, const char *path, const char *dirname);
nng_err nng_http_handler_alloc_file(nng_http_handler **hp, const char *path, const char *filename);

The nng_http_handler_alloc_directory and nng_http_handler_alloc_file create handlers pre-configured to act as static content servers for either a full directory at dirname, or the single file at filename. These support the “GET” and “HEAD” methods, and the directory variant will dynamically generate index.html content based on the directory contents. These will also set the “Content-Type” if the file extension matches one of the built-in values already known. If the no suitable MIME type can be determined, the content type is set to “application/octet-stream”.

Static Handler

nng_err nng_http_handler_alloc_static(nng_http_handler **hp, const char *path,
        const void *data, size_t size, const char *content_type);

The nng_http_handler_alloc_static function creates a handler that serves the content located in data (consisting of size bytes) at the URI path. The content_type determines the “Content-Type” header. If NULL is specified then a value of application/octet-stream is assumed.

Redirect Handler

nng_err nng_http_handler_alloc_redirect(nng_http_handler **hp, const char *path,
        nng_http_status status, const char *location);

The nng_http_handler_alloc_redirect function creates a handler with a function that simply directions from the URI at path to the given location.

The HTTP reply it creates will be with [status code][nng_http_status] status, which should be a 3XX code such as 301, and a Location: header will contain the URL referenced by location, with any residual suffix from the request URI appended.

tip

Use [nng_http_handler_set_tree] to redirect an entire tree. For example, it is possible to redirect an entire HTTP site to another HTTPS site by specifying / as the path and then using the base of the new site, such as https://newsite.example.com as the new location.

tip

Be sure to use the appropriate value for status. Permanent redirection should use [NNG_HTTP_STATUS_STATUS_MOVED_PERMANENTLY] (301) and temporary redirections should use [NNG_HTTP_STATUS_TEMPORARY_REDIRECT] (307). In REST APIs, using a redirection to supply the new location of an object created with POST should use [NNG_HTTP_STATUS_SEE_OTHER] (303).

Collecting Request Body

void nng_http_handler_collect_body(nng_http_handler *handler, bool want, size_t maxsz);

The nng_http_handler_collect_body function requests that HTTP server framework collect any reuqest body for the request and attach it to the connection before calling the callback for the handler.

Subsequently the data can be retrieved by the handler from the request with the nng_http_get_body function.

The collection is enabled if want is true. Furthermore, the data that the client may sent is limited by the value of maxsz. If the client attempts to send more data than maxsz, then the request will be terminated with [NNG_HTTP_STATUS_CONTENT_TOO_LARGE] (413).

tip

Limiting the size of incoming request data can provide protection against denial of service attacks, as a buffer of the client-supplied size must be allocated to receive the data.

In order to provide an unlimited size, use (size_t)-1 for maxsz. The value 0 for maxsz can be used to prevent any data from being passed by the client.

The built-in handlers for files, directories, and static data limit the maxsz to zero by default. Otherwise the default setting is to enable this capability with a default value of maxsz of 1 megabyte.

note

NNG specifically does not support the Chunked transfer-encoding. This is considered a bug, and is a deficiency for full HTTP/1.1 compliance. However, few clients send data in this format, so in practice this should create few limitations.

Setting Callback Argument

void nng_http_handler_set_data(nng_http_handler *handler, void *data,
    void (*dtor)(void *));

The nng_http_handler_set_data function is used to set the data argument that will be passed to the callback.

Additionally, when the handler is deallocated, if dtor is not NULL, then it will be called with data as its argument. The intended use of this function is deallocate any resources associated with data.

Setting the Method

void nng_http_handler_set_method(nng_http_handler *handler, const char *method);

The nng_http_handler_set_method function sets the method that the handler will be called for, such as “GET” or “POST”. (By default the “GET” method is handled.)

If method is NULL the handler will be executed for all methods. The handler may determine the actual method used with the nng_http_get_method function.

The server will automatically call “GET” handlers if the client sends a “HEAD” request, and will suppress HTTP body data in the responses sent for such requests.

note

If method is longer than 32-bytes, it may be truncated silently.

Filtering by Host

void nng_http_handler_set_host(nng_http_handler *handler, const char *host);

The nng_http_handler_set_host function is used to limit the scope of the handler so that it will only be called when the specified host matches the value of the Host: HTTP header.

This can be used to create servers with different content for different virtual hosts.

The value of the host can include a colon and port, and should match exactly the value of the Host header sent by the client. (Canonicalization of the host name is performed.)

note

The port number may be ignored; at present the HTTP server framework does not support a single server listening on different ports concurrently.

Handling an Entire Tree

void nng_http_handler_set_tree(nng_http_handler *handler);

The nng_http_handler_set_tree function causes the handler to be matched if the request URI sent by the client is a logical child of the path for handler, and no more specific handler has been registered.

This is useful in cases when the handler would like to examine the entire path and possibly behave differently; for example a REST API that uses the rest of the path to pass additional parameters.

tip

This function is useful when constructing API handlers where a single service address (path) supports dynamically generated children. It can also provide a logical fallback instead of relying on a 404 error code.

Sending the Response Explicitly

void nng_http_write_response(nng_http *conn, nng_aio *aio);

Normally the server will send any attached response, but there are circumstances where a response must be sent manually, such as when hijacking a connection.

In such a case, nng_http_write_response can be called, which will send the response and any attached data, asynchronously using the nng_aio aio.

By default, for HTTP/1.1 connections, the connection is kept open, and will be reused to receive new requests. For HTTP/1.0, or if the client has requested explicitly by setting the “Connection: close” header, the connection will be closed after the response is fully sent.

Miscellaneous

This chapter discusses some interfaces that don’t really fit anywhere else.

Get Random Number

uint32_t nng_random(void);

The nng_random returns a random number. The value returned is suitable for use with cryptographic functions such as key generation, and is obtained using platform-specific cryptographically strong random number facilities when available.

Create Socket Pair

nng_err nng_socket_pair(int fds[2]);

The nng_socket_pair function creates a pair of connected file descriptors. These file descriptors, which are returned in the fds array, are suitable for use with the Socket transport.

On POSIX platforms, this is a thin wrapper around the standard socketpair function, using the AF_UNIX family and the SOCK_STREAM socket type. 1

This will return zero on success, or an error number. On platforms that lack this capability, such as Windows, it will return NNG_ENOTSUP.

tip

This function may be useful for creating a shared connection between a parent process and a child process on UNIX platforms, without requiring the processes use a shared filesystem or TCP connection.

Report Library Version

const char * nng_version(void);

The nng_version function returns a human readable version number for NNG, formatted as a NUL-terminated string.

Additionally, compile time version information is available via some predefined macros:

  • NNG_MAJOR_VERSION: Major version number.
  • NNG_MINOR_VERSION: Minor version number.
  • NNG_PATCH_VERSION: Patch version number.

NNG is developed and released using Semantic Versioning 2.0, and the version numbers reported refer to both the API and the library itself. (The ABI – application binary interface – between the library and the application is controlled in a similar, but different manner depending upon the link options and how the library is built.)


1: At present only POSIX platforms implementing socketpair support this function.

ID Map

Internally, NNG uses a map of numeric identifiers to data structures. This feature is also exposed for application use, as a “supplemental” feature.

When using these functions, it is necessary to add the #include <nng/supplemental/util/idhash.h> include file to list of includes.

ID Map Structure

typedef struct nng_id_map_s nng_id_map;

The ID map structure, nng_id_map provides a table of identifiers mapping to user-supplied pointers (which must not be NULL). The identifiers can be thought of as indices into the table, with the pointers providing the reference for the user supplied data.

The values of identifiers can be supplied by the user, or can be allocated automatically by nng_id_map from a predefined range. The starting point for allocations can also be randomly within the range.

The identifiers are 64-bit unsigned integers and can be sparse; the structure will use space efficiently even if identifiers are very far apart. 1

important

The function available for nng_id_map are not thread-safe. Callers should use a mutex or similar approach when thread-safety is needed.

Create ID Map

#define NNG_MAP_RANDOM 1

int nng_id_map_alloc(nng_id_map **map_p, uint64_t lo, uint64_t hi, int flags);

The nng_id_map_alloc function allocates a map without any data in it, and returns a pointer to it in map_p. When allocating identifiers dynamically, the values will be chosen from the range defined by lo and hi, inclusive.

The flags argument is a bit mask of flags that can adjust behavior of the map. The only flag defined at present is NNG_MAP_RANDOM, which causes the first identifier allocation to start at a random point within the range. This is useful to reduce the odds of different instances of an application using the same identifiers at the same time.

If both lo and hi are zero, then the values 0 and 0xffffffff are substituted in their place, giving a full range of 32-bit identifiers.

This function can return NNG_ENOMEM if it is unable to allocate resources, otherwise it returns zero on success.

Destroy Map

void nng_id_map_free(nng_id_map *map);

The nng_id_map_free function destroys map, releasing any resources associated with it.

note

The nng_id_map_free frees the map itself, but will not free memory associated with any strctures contained within it.

Store a Value

int nng_id_set(nng_id_map *map, uint64_t id, void *value);

The nng_id_map_set function is used to store the value in the map at index id.

If another value is already stored at that same location, then it is overwritten with value.

note

The value must not be NULL.

If the table has to grow to accommodate this value, it may fail if insufficient memory is available, returning NNG_ENOMEM. Otherwise it returns zero.

Lookup a Value

void *nng_id_get(nng_id_map *map, uint64_t id);

The nng_id_get function looks up the entry for id in map, returning the associated value if present, or NULL if no such entry exists.

Allocate an ID

int nng_id_alloc(nng_id_map *map, uint64_t *id_p, void *value);

The nng_id_alloc stores the value in the map, at a newly allocated index, and returns the index in id_p.

Identifiers are allocated in increasing order, without reusing old identifiers until the largest possible identifier is allocated. After wrapping, only identifiers that are no longer in use will be considered. No effort is made to order the availability of identifiers based on when they were freed.2

As with nng_id_set, this may need to allocate memory and can thus fail with NNG_ENOMEM.

Additionally, if there are no more free identifiers within the range specified when map was created, then it will return NNG_ENOSPC.

Otherwise it returns zero, indicating success.

Remove an ID

int nng_id_remove(nng_id_map *map, uint64_t id);

The nng_id_remove removes the entry at index id from map.

If no such entry exist, it will return NNG_ENOENT. Otherwise it returns zero.

Iterating IDs

bool nng_id_visit(nng_id_map *map, uint64_t *id_p, void **value_p, uint32_t *cursor);

The nng_id_visit function is used to iterate over all items in the table. The caller starts the iteration by setting the cursor to 0 before calling it. For each call, the associated key and value of the next item will be returned in id_p, and value_p and the cursor will be updated. When all items have been iterated, the function returns false. The order of items returned is not guaranteed to be sequential. The caller must not attempt to derive any value of the cursor as it refers to internal table indices.

Entries may be safely removed from map while iterating.

However, if new entries are added to the table while iterating, the result of iteration is undefined; entries may be repeated or omitted during such an iteration.

The caller must not attempt to derive any value of the cursor as it refers to internal table indices.


1: The ID map is capable of storing at most 232 identifiers, even though the identifers may themselves be much larger than this.

2: The concern about possibly reusing a recently released identifier comes into consideration after the range has wrapped. Given a sufficiently large range, this is unlikely to be a concern.

Command Arguments

Some NNG utilities need to parse command line options, and for this purpose a header library is supplied.

To make use of this, the header <nng/args.h> must be included.

tip

The functionality described here is entirely contained in the nng/args.h header file, and may be used without previously initializing the library with nng_init, and may even be used in programs that are not linked against the NNG library.

Parse Command Line Arguments

typedef struct nng_arg_spec {
    const char *a_name;  // Long style name (may be NULL for short only)
    int         a_short; // Short option (no clustering!)
    int         a_val;   // Value stored on a good parse (>0)
    bool        a_arg;   // Option takes an argument if true
} nng_optspec;

#define NNG_ARG_END     (-1)
#define NNG_ARG_INVAL   (-2)
#define NNG_ARG_AMBIG   (-3)
#define NNG_ARG_MISSING (-4)

int nng_args_parse(int argc, char *const *argv,
                   const nng_optspec *spec, int *val, char **arg, int *idx);

The nng_args_parse function is intended to facilitate parsing command-line arguments. This function exists largely to stand in for getopt from POSIX systems, but it is available on all platforms, and it includes some capabilities missing from getopt.

The function parses arguments from main1 (using argc and argv), starting at the index referenced by idx. (New invocations typically set the value pointed to by idx to 1.)

Options are parsed as specified by spec (see Argument Specification.) The value of the parsed option will be stored at the address indicated by val, and the value of idx will be incremented to reflect the next option to parse.

tip

For using this to parse command-line like strings that do not include the command name itself, set the value referenced by idx to zero instead of one.

If the option had an argument, a pointer to that is returned at the address referenced by arg.

This function should be called repeatedly, until it returns either -1 (indicating the end of options is reached) or a non-zero error code is returned.

This function may return the following errors:

  • [NNG_ARG_AMBIGU]: Parsed option matches more than one specification.
  • [NNG_ARG_MISSING]: Option requires an argument, but one is not present.
  • [NNG_ARG_INVAL]: An invalid (unknown) argument is present in argv.

Option Specification

The calling program must first create an array of nng_arg_spec structures describing the options to be supported. This structure has the following members:

  • a_name:

    The long style name for the option, such as “verbose”. This will be parsed as a long option on the command line when it is prefixed with two dashes. It may be NULL if only a short option is to be supported.

  • a_short:

    This is a single letter (at present only ASCII letters are supported). These options appear as just a single letter, and are prefixed with a single dash on the command line. The use of a slash in lieu of the dash is not supported, in order to avoid confusion with path name arguments. This value may be set to 0 if no short option is needed.

  • o_val:

    This is a numeric value that is unique to this option. This value is assigned by the application program, and must be non-zero for a valid option. If this is zero, then it indicates the end of the specifications, and the rest of this structure is ignored. The value will be returned to the caller in val by nng_args_parse when this option is parsed from the command line.

  • a_arg:

    This value should be set to true if the option should take an argument.

Long Options

Long options are parsed from the argv array, and are indicated when the element being scanned starts with two dashes. For example, the “verbose” option would be specified as --verbose on the command line. If a long option takes an argument, it can either immediately follow the option as the next element in argv, or it can be appended to the option, separated from the option by an equals sign (=) or a colon (:).

Short Options

Short options appear by themselves in an argv element, prefixed by a dash (-). If the short option takes an argument, it can either be appended in the same element of argv, or may appear in the next argv element.

note

Option clustering, where multiple options can be crammed together in a single argv element, is not supported by this function (yet).

Prefix Matching

When using long options, the parser will match if it is equal to a prefix of the a_name member of a option specification, provided that it do so unambiguously (meaning it must not match any other option specification.)

Example

The following program fragment demonstrates this function.

    enum { OPT_LOGFILE, OPT_VERBOSE };
    char *logfile; // options to be set
    bool verbose;

    static nng_arg_spec specs[] = {
        {
            .a_name = "logfile",
            .a_short = 'D',
            .a_val = OPT_LOGFILE,
            .a_arg = true,
        }, {
            .a_name = "verbose",
            .a_short = 'V',
            .a_val = OPT_VERBOSE,
            .a_arg = false,
        }, {
            .a_val = 0; // Terminate array
        }
    };

    for (int idx = 1;;) {
        int rv, opt;
        char *arg;
        rv = nng_args_parse(argc, argv, specs, &opt, &arg, &idx);
        if (rv != 0) {
            break;
        }
        switch (opt) {
        case OPT_LOGFILE:
            logfile = arg;
            break;
        case OPT_VERBOSE:
            verbose = true;
            break;
        }
    }
    if (rv != NNG_ARG_END) {
        switch (rv) {
        case NNG_ARG_AMBIG:
            printf("Options error: ambiguous option\n");
            break;
        case NNG_ARG_MISSING:
            printf("Options error: required option argument missing\n");
            break;
        case NNG_ARG_INVAL:
            printf("Options error: unknown option present\n");
            break;
        }
        exit(1);
    }


1: Parsing argument strings from other sources can be done as well, although usually then idx will be initialized to zero.

Protocols

The Scalability Protocols are a principally a collection of common networking patterns found in applications.

The following patterns are included:

Request - Reply

The request/reply pattern is made up of the REQ and REP protocols. This most often used when implementing RPC-like services, where a given request is matched by a single reply.

Pipeline

The pipeline pattern is made up of the PUSH and PULL protocols.

In this pattern communication is half-duplex, in that one side sends data and another side receives.

This pattern is also characterized by its ability to solve distribution problems, and the fact that it has back-pressure, providing a measure of flow control to data production and consumption.

Publish - Subscribe

Bus

Pair

BUS Protocol

The BUS protocol provides for building mesh networks where every peer is connected to every other peer. In this protocol, each message sent by a node is sent to every one of its directly connected peers.

tip

Messages are only sent to directly connected peers. This means that in the event that a peer is connected indirectly, it will not receive messages. When using this protocol to build mesh networks, it is therefore important that a fully-connected mesh network be constructed.

All message delivery in this pattern is best-effort, which means that peers may not receive messages. Furthermore, delivery may occur to some, all, or none of the directly connected peers. (Messages are not delivered when peer nodes are unable to receive.) Hence, send operations will never block; instead if the message cannot be delivered for any reason it is discarded.

tip

In order to minimize the likelihood of message loss, this protocol should not be used for high throughput communications. Furthermore, the more traffic in aggregate that occurs across the topology, the more likely that message loss is to occur.

Socket Operations

The nng_bus0_open functions create a bus socket. This socket may be used to send and receive messages. Sending messages will attempt to deliver to each directly connected peer.

Protocol Versions

Only version 0 of this protocol is supported. (At the time of writing, no other versions of this protocol have been defined.)

Protocol Options

The BUS protocol has no protocol-specific options.

Protocol Headers

When using a BUS socket in raw mode, received messages will contain the incoming pipe ID as the sole element in the header. If a message containing such a header is sent using a raw BUS socket, then, the message will be delivered to all connected pipes except the one identified in the header. This behavior is intended for use with device configurations consisting of just a single socket. Such configurations are useful in the creation of rebroadcasters, and this capability prevents a message from being routed back to its source. If no header is present, then a message is sent to all connected pipes.

When using normal (cooked mode) BUS sockets, no message headers are present.

PAIR protocol

The PAIR protocol implements a peer-to-peer pattern, where relationships between peers are one-to-one.

Socket Operations

The nng_pair_open functions create a PAIR socket.

Normally, this pattern will block when attempting to send a message if no peer is able to receive the message.

note

Even though this mode may appear to be reliable, because back-pressure prevents discarding messages most of the time, there are topologies involving where messages may be discarded. Applications that require reliable delivery semantics should consider using REQ sockets, or implement their own acknowledgment layer on top of PAIR sockets.

Protocol Versions

Version 0 is the legacy version of this protocol. It lacks any header information, and is suitable when building simple one-to-one topologies.

tip

Use version 0 if you need to communicate with other implementations, including the legacy libnanomsg library or mangos.

Version 1 of the protocol offers improved protection against loops when used with devices.

Polyamorous Mode

note

Polyamorous mode is deprecated, and support for it will likely be removed in a future release, when a suitable mesh protocol is available. In the meantime, applications are encouraged to look to other patterns.

Normally pair sockets are for one-to-one communication, and a given peer will reject new connections if it already has an active connection to another peer.

Polyamorous changes this, to allow a socket to communicate with multiple directly-connected peers. This mode is enabled by opening a socket using nng_pair1_open_poly.

tip

Polyamorous mode is only available when using pair version 1.

In polyamorous mode a socket can support many one-to-one connections. In this mode, the application must choose the remote peer to receive an outgoing message by setting the nng_pipe to use for the outgoing message using nng_msg_set_pipe.

If no remote peer is specified by the sender, then the protocol will select any available connected peer.

Most often the value of the outgoing pipe will be obtained from an incoming message using nng_msg_get_pipe, such as when replying to an incoming message.

note

Directed send only works with directly connected peers. It will not function across device proxies.

In order to prevent head-of-line blocking, if the peer on the given pipe is not able to receive (or the pipe is no longer available, such as if the peer has disconnected), then the message will be discarded with no notification to the sender.

Protocol Options

The following protocol-specific options are available.

  • NNG_OPT_MAXTTL: (int, version 1 only). Maximum time-to-live.

  • NNG_OPT_PAIR1_POLY: (bool, version 1 only) This option is no longer supported. Formerly it was used to configure polyamorous mode, but that mode is now established by using the nng_pair1_open_poly function.

Protocol Headers

Version 0 of the pair protocol has no protocol-specific headers.

Version 1 of the pair protocol uses a single 32-bit unsigned value. The low-order (big-endian) byte of this value contains a “hop” count, and is used in conjunction with the NNG_OPT_MAXTTL option to guard against device forwarding loops. This value is initialized to 1, and incremented each time the message is received by a new node.

PUB Protocol

The PUB protocol is one half of a publisher/subscriber pattern. In this pattern, a publisher sends data, which is broadcast to all subscribers. The subscribing applications only see the data to which they have subscribed.

The PUB protocol is the publisher side, and the SUB protocol is the subscriber side.

note

In this implementation, the publisher delivers all messages to all subscribers. The subscribers maintain their own subscriptions, and filter them locally. Thus, this pattern should not be used in an attempt to reduce bandwidth consumption.

The topics that subscribers subscribe to is just the first part of the message body. Applications should construct their messages accordingly.

Socket Operations

The nng_pub0_open functions create a publisher socket. This socket may be used to send messages, but is unable to receive them. Attempts to receive messages will result in NNG_ENOTSUP.

Protocol Versions

Only version 0 of this protocol is supported. (At the time of writing, no other versions of this protocol have been defined.)

Protocol Options

The PUB protocol has no protocol-specific options.

Protocol Headers

The PUB protocol has no protocol-specific headers.

PULL protocol

The PULL protocol is one half of a pipeline pattern. The other half is the PUSH protocol.

In the pipeline pattern, pushers distribute messages to pullers. Each message sent by a pusher will be sent to one of its peer pullers, chosen in a round-robin fashion from the set of connected peers available for receiving. This property makes this pattern useful in load-balancing scenarios.

Socket Operations

The nng_pull0_open functions create a PULL socket. This socket may be used to receive messages, but is unable to send them. Attempts to send messages will result in NNG_ENOTSUP.

When receiving messages, the PULL protocol accepts messages as they arrive from peers. If two peers both have a message ready, the order in which messages are handled is undefined.

Protocol Versions

Only version 0 of this protocol is supported. (At the time of writing, no other versions of this protocol have been defined.)

Protocol Options

The PULL protocol has no protocol-specific options.

Protocol Headers

The PULL protocol has no protocol-specific headers.

PUSH protocol

DESCRIPTION

The PUSH protocol is one half of a pipeline pattern. The other side is the PULL protocol.

In the pipeline pattern, pushers distribute messages to pullers. Each message sent by a pusher will be sent to one of its peer pullers, chosen in a round-robin fashion from the set of connected peers available for receiving. This property makes this pattern useful in load-balancing scenarios.

Socket Operations

The nng_push0_open call creates a PUSH socket. This socket may be used to send messages, but is unable to receive them. Attempts to receive messages will result in NNG_ENOTSUP.

Send operations will observe flow control (back-pressure), so that only peers capable of accepting a message will be considered. If no peer is available to receive a message, then the send operation will wait until one is available, or the operation times out.

note

Although the pipeline protocol honors flow control, and attempts to avoid dropping messages, no guarantee of delivery is made. Furthermore, as there is no capability for message acknowledgment, applications that need reliable delivery are encouraged to consider the REQ protocol instead.

Protocol Versions

Only version 0 of this protocol is supported. (At the time of writing, no other versions of this protocol have been defined.)

Protocol Options

  • NNG_OPT_SENDBUF: (int, 0 - 8192) Normally this is set to zero, indicating that send operations are unbuffered. In unbuffered operation, send operations will wait until a suitable peer is available to receive the message. If this is set to a positive value (up to 8192), then an intermediate buffer is provided for the socket with the specified depth (in messages).

note

Transport layer buffering may occur in addition to any socket buffer determined by this option.

Protocol Headers

The PUSH protocol has no protocol-specific headers.

REP Protocol

The REP protocol is one half of a request/reply pattern. In this pattern, a requester sends a message to one replier, who is expected to reply. The request is resent if no reply arrives, until a reply is received or the request times out.

tip

This protocol is useful in setting up RPC-like services. It is also reliable, in that a requester will keep retrying until a reply is received.

The REP protocol is the replier side, and the REP protocol is the requester side.

Socket Operations

The nng_rep0_open functions create a replier socket. This socket may be used to receive messages (requests), and then to send replies.

Generally a reply can only be sent after receiving a request.

Send operations will result in NNG_ESTATE if no corresponding request was previously received.

Likewise, only one receive operation may be pending at a time. Any additional concurrent receive operations will result in NNG_ESTATE.

Raw mode sockets ignore all these restrictions.

Context Operations

This protocol supports the creation of contexts for concurrent use cases using nng_ctx_open.

Each context may have at most one outstanding request, and operates independently of the others. The restrictions for order of operations with sockets apply equally well for contexts, except that each context will be treated as if it were a separate socket.

Protocol Versions

Only version 0 of this protocol is supported. (At the time of writing, no other versions of this protocol have been defined.)

Protocol Options

The REP protocol has no protocol-specific options.

Protocol Headers

The REP protocol uses a backtrace in the header. This is more fully documented in the REQ chapter.

REQ protocol

The REQ protocol is one half of a request/reply pattern. In this pattern, a requester sends a message to one replier, who is expected to reply. The request is resent if no reply arrives, until a reply is received or the request times out.

tip

This protocol is useful in setting up RPC-like services. It is also “reliable”, in that a the requester will keep retrying until a reply is received.

note

Because requests are resent, it is important that they be idempotent to ensure predictable and repeatable behavior even in the face of duplicated requests, which can occur (for example if a reply message is lost for some reason.)

The requester generally only has one outstanding request at a time unless in raw mode, and it will generally attempt to spread work requests to different peer repliers.

tip

This property, when combined with a device can help provide a degree of load-balancing.

The REQ protocol is the requester side, and the REP protocol is the replier side.

Socket Operations

The nng_req0_open functions create a REQ socket. This socket may be used to send messages (requests), and then to receive replies.

Generally a reply can only be received after sending a request. (Attempts to receive a message will result in NNG_ESTATE if there is no outstanding request.)

Furthermore, only a single receive operation may be pending at a time. Attempts to post more receive operations concurrently will result in NNG_ESTATE.

Requests may be canceled by sending a different request. This will cause the requester to discard any reply from the earlier request, but it will not stop a replier from processing a request it has already received or terminate a request that has already been placed on the wire.

Raw mode sockets ignore all these restrictions.

Context Operations

This protocol supports the creation of contexts for concurrent use cases using nng_ctx_open.

The NNG_OPT_REQ_RESENDTIME value may be configured differently on contexts created this way.

Each context may have at most one outstanding request, and operates independently from the others.

The restrictions for order of operations with sockets apply equally well for contexts, except that each context will be treated as if it were a separate socket.

Protocol Versions

Only version 0 of this protocol is supported. (At the time of writing, no other versions of this protocol have been defined.)

Protocol Options

The following protocol-specific option is available.

  • NNG_OPT_REQ_RESENDTIME:
    (nng_duration)
    When a new request is started, a timer of this duration is also started. If no reply is received before this timer expires, then the request will be resent.

    Requests are also automatically resent if the peer to whom the original request was sent disconnects.

    Resending may be deferred up to the value of the NNG_OPT_RESENDTICK parameter.

    If the value is set to NNG_DURATION_INFINITE, then resends are disabled altogether. This should be used when the request is not idemptoent.

  • NNG_OPT_REQ_RESENDTICK:
    (nng_duration)
    This is the granularity of the clock that is used to check for resending. The default is a second. Setting this to a higher rate will allow for more timely resending to occur, but may incur significant additional overhead when the socket has many outstanding requests (contexts).

    When there are no requests outstanding that have a resend set, then the clock does not tick at all.

    This option is shared for all contexts on a socket, and is only available for the socket itself.

Protocol Headers

This protocol uses a backtrace in the header. This form uses a stack of 32-bit big-endian identifiers. There must be at least one identifier, the request ID, which will be the last element in the array, and must have the most significant bit set.

There may be additional peer IDs preceding the request ID. These will be distinguishable from the request ID by having their most significant bit clear.

When a request message is received by a forwarding node (such as a device), the forwarding node prepends a 32-bit peer ID (which must have the most significant bit clear), which is the forwarder’s way of identifying the directly connected peer from which it received the message. (This peer ID, except for the most significant bit, has meaning only to the forwarding node itself.)

It may help to think of prepending a peer ID as pushing a peer ID onto the front of the stack of headers for the message. (It will use the peer ID it popped from the front to determine the next intermediate destination for the reply.)

When a reply message is created, it is created using the same headers that the request contained.

A forwarding node can pop the peer ID it originally pushed on the message, stripping it from the front of the message as it does so.

When the reply finally arrives back at the initiating requester, it should have only a single element in the message, which will be the request ID it originally used for the request.

RESPONDENT protocol

The RESPONDENT protocol is one half of a survey pattern. In this pattern, a surveyor sends a survey, which is broadcast to all peer respondents. The respondents then have a chance to reply (but are not obliged to reply). The survey itself is a timed event, so that responses received after the survey has finished are discarded.

tip

This protocol is useful in solving voting problems, such as leader election in cluster configurations, as well as certain kinds of service discovery problems.

The RESPONDENT protocol is the respondent side, and the SURVEYOR protocol is the surveyor side.

Socket Operations

The nng_respondent0_open functions create a respondent socket. This socket may be used to receive messages, and then to send replies. A reply can only be sent after receiving a survey, and generally the reply will be sent to surveyor from whom the last survey was received.

Respondents may discard a survey by simply not replying to it.

Raw mode sockets ignore all these restrictions.

Context Operations

This protocol supports the creation of contexts for concurrent use cases using nng_ctx_open.

Incoming surveys will be routed to and received by only one context. Additional surveys may be received by other contexts in parallel. Replies made using a context will be returned to the the surveyor that issued the survey most recently received by that context. The restrictions for order of operations with sockets apply equally well for contexts, except that each context will be treated as if it were a separate socket.

Protocol Versions

Only version 0 of this protocol is supported. At the time of writing, no other versions of this protocol have been defined. 1

Protocol Options

The respondent protocol has no protocol-specific options.

Protocol Headers

The RESPONDENT protocol uses a backtrace in the header. This is more fully documented in the SURVEYOR manual.


1: An earlier and incompatible version of the protocol was used in older pre-releases of nanomsg, but was not released in any production version.

SUB protocol

The SUB protocol is one half of a publisher/subscriber pattern. In this pattern, a publisher sends data, which is broadcast to all subscribers. The subscribing applications only see the data to which they have subscribed.

The SUB protocol is the subscriber side, and the PUB protocol is the publisher side.

note

The publisher delivers all messages to all subscribers. The subscribers maintain their own subscriptions, and filter them locally. Thus, this pattern should not be used in an attempt to reduce bandwidth consumption.

The topics that subscribers subscribe to is compared to the leading bytes of the message body. Applications should construct their messages accordingly.

Socket Operations

The nng_sub0_open functions create a SUB socket. This socket may be used to receive messages, but is unable to send them. Attempts to send messages will result in NNG_ENOTSUP.

Protocol Versions

Only version 0 of this protocol is supported. (At the time of writing, no other versions of this protocol have been defined.)

Protocol Options

The following protocol-specific option is available.

  • NNG_OPT_SUB_PREFNEW:
    (bool)

    This read/write option specifies the behavior of the subscriber when the queue is full. When true (the default), the subscriber will make room in the queue by removing the oldest message. When false, the subscriber will reject messages if the message queue does not have room.

Protocol Headers

The SUB protocol has no protocol-specific headers.

SURVEYOR protocol

The SURVEYOR protocol is one half of a survey pattern. In this pattern, a surveyor sends a survey, which is broadcast to all peer respondents. The respondents then have a chance, but are not obliged, to reply. The survey itself is a timed event, so that responses received after the survey has finished are discarded.

tip

This protocol is useful in solving voting problems, such as leader election in cluster configurations, as well as certain kinds of service discovery problems.

The SURVEYOR protocol is the surveyor side, and the RESPONDENT protocol is the respondent side.

Socket Operations

The nng_surveyor0_open functions create a surveyor socket. This socket may be used to send messages (surveys), and then to receive replies. A reply can only be received after sending a survey. A surveyor can normally expect to receive at most one reply from each responder. (Messages can be duplicated in some topologies, so there is no guarantee of this.)

Attempts to receive on a socket with no outstanding survey will result in NNG_ESTATE. If the survey times out while the surveyor is waiting for replies, then the result will be NNG_ETIMEDOUT.

Only one survey can be outstanding at a time; sending another survey will cancel the prior one, and any responses from respondents from the prior survey that arrive after this will be discarded.

Raw mode sockets ignore all these restrictions.

Context Operations

This protocol supports the creation of contexts for concurrent use cases using nng_ctx_open.

Each context can initiate its own surveys, and it will receive only responses to its own outstanding surveys. Other contexts on the same socket may have overlapping surveys operating at the same time.

Each of these may have their own timeouts established with NNG_OPT_SURVEYOR_SURVEYTIME.

Additionally, sending a survey on a context will only cancel an outstanding survey on the same context.

note

Due to the best-effort nature of this protocol, if too may contexts are attempting to perform surveys simultaneously, it is possible for either individual outgoing surveys or incoming responses to be lost.

Protocol Versions

Only version 0 of this protocol is supported. At the time of writing, no other versions of this protocol have been defined. 1

Protocol Options

The following protocol-specific option is available.

  • NNG_OPT_SURVEYOR_SURVEYTIME:
    (nng_duration)

    When a new survey is started, a timer of this duration is started. Any responses arriving this time will be discarded. Attempts to receive after the timer expires with no other surveys started will result in NNG_ESTATE.

    If a receive is pending when this timer expires, it will result in NNG_ETIMEDOUT.

Protocol Headers

This form uses a stack of 32-bit big-endian identifiers. There must be at least one identifier, the survey ID, which will be the last element in the array, and must have the most significant bit set.

There may be additional peer IDs preceding the survey ID. These will be distinguishable from the survey ID by having their most significant bit clear.

When a survey message is received by a forwarding node (such as a device), the forwarding node prepends a 32-bit peer ID (which must have the most significant bit clear), which is the forwarder’s way of identifying the directly connected peer from which it received the message. (This peer ID, except for the most significant bit, has meaning only to the forwarding node itself.)

It may help to think of prepending a peer ID as pushing a peer ID onto the front of the stack of headers for the message. (It will use the peer ID it popped from the front to determine the next intermediate destination for the response.)

When a response message is created, it is created using the same headers that the survey contained.

A forwarding node can pop the peer ID it originally pushed on the message, stripping it from the front of the message as it does so.

When the response finally arrives back at the initiating surveyor, it should have only a single element in the message, which will be the survey ID it originally used for the request.

More detail can be found in the sp-surveyor-01 RFC document.


1: An earlier and incompatible version of the protocol was used in older pre-releases of nanomsg, but was not released in any production version.

Transports

This section documents transports for Scalabity Protocols implemented by NNG.

INPROC Transport

The inproc transportintra-process provides communication support between sockets within the same process. This may be used as an alternative to slower transports when data must be moved within the same process.

This transport tries hard to avoid copying data, and thus is very light-weight.

URL Format

This transport uses URLs using the scheme inproc://, followed by an arbitrary string of text, terminated by a NUL byte.

Multiple URLs can be used within the same application, and they will not interfere with one another.

Two applications may also use the same URL without interfering with each other. They will however be unable to communicate with each other using that URL.

Socket Address

When using an nng_sockaddr structure, the actual structure is of type nng_sockaddr_inproc.

Transport Options

The inproc transport has no special options.

note

While inproc accepts the option NNG_OPT_RECVMAXSZ for compatibility, the value of the option is ignored with no enforcement. As inproc peers are in the same address space, they are implicitly trusted, so the protection afforded by NNG_OPT_RECVMAXSZ is unnecessary.

Mixing Implementations

When mixing the NNG library with other implementations of these protocols in the same process (such as the mangos or libnanomsg implementations), it will not be possible to utilize the inproc transport to communicate across this boundary.

This limitation also extends to using different instances of the NNG library within the same process.

IPC Transport

DESCRIPTION

The ipc transport provides communication support between sockets within different processes on the same host. For POSIX platforms, this is implemented using UNIX domain sockets. For Windows, this is implemented using Windows named pipes. Other platforms may have different implementation strategies.

URI Formats

Traditional Names

This transport uses URIs using the scheme ipc://, followed by a path name in the file system where the socket or named pipe should be created.

tip

On Windows, all names are prefixed by \\.\pipe\ and do not reside in the normal file system. On POSIX platforms, the path is taken literally, and is relative to the current directory, unless it begins with /, in which case it is relative to the root directory.

note

When using relative paths on POSIX systems, the address used and returned in properties like NNG_OPT_LOCADDR will also be relative. Consequently, they will only be interpreted the same by processes that have the same working directory. To ensure maximum portability and safety, absolute paths are recommended whenever possible.

note

If compatibility with legacy nanomsg applications is required, then path names must not be longer than 122 bytes, including the final NUL byte. This is because legacy versions of nanomsg cannot express URLs longer than 128 bytes, including the ipc:// prefix.

UNIX Aliases

The unix:// scheme is an alias for ipc:// and can be used inter-changeably, but only on POSIX systems. 1

Abstract Names

On Linux, this transport also can support abstract sockets. Abstract sockets use a URI-encoded name after the abstract:// scheme, which allows arbitrary values to be conveyed in the path, including embedded NUL bytes. For example, the name "a\0b" would be represented as abstract://a%00b.

tip

An empty name may be used with a listener to request “auto bind” be used to select a name. In this case the system will allocate a free name. The name assigned may be retrieved using NNG_OPT_LOCADDR.

Abstract names do not include the leading NUL byte used in the low-level socket address.

Abstract sockets do not have any representation in the file system, and are automatically freed by the system when no longer in use.

Abstract sockets ignore socket permissions, but it is still possible to determine the credentials of the peer with NNG_OPT_PEER_UID, and similar options. 2

Socket Address

When using an nng_sockaddr structure, the actual structure is of type nng_sockaddr_ipc, except for abstract sockets, which use nng_sockaddr_abstract.

Transport Options

The following transport options are supported by this transport, where supported by the underlying platform.

OptionTypeDescription
NNG_OPT_IPC_PERMISSIONSintSettable on listeners before they start, this is the UNIX file mode used when creating the socket.
NNG_OPT_LOCADDRnng_sockaddrLocal socket address, either nng_sockaddr_ipc or nng_sockaddr_abstract.
NNG_OPT_REMADDRnng_sockaddrRemote socket address, either nng_sockaddr_ipc or nng_sockaddr_abstract.
NNG_OPT_PEER_GIDintRead only option, returns the group ID of the process at the other end of the socket, if platform supports it.
NNG_OPT_PEER_PIDintRead only option, returns the processed ID of the process at the other end of the socket, if platform supports it.
NNG_OPT_PEER_UIDintRead only option, returns the user ID of the process at the other end of the socket, if platform supports it.
NNG_OPT_PEER_ZONEIDintRead only option, returns the zone ID of the process at the other end of the socket, if platform supports it.
NNG_OPT_LISTEN_FDintWrite only for listeners before they start, use the named socket for accepting (for use with socket activation).

Other Configuration Parameters

On Windows systems, the security descriptor for the listener, which can be used to control access, can be set using the function nng_listener_set_security_descriptor.


1: The purpose of this scheme is to support a future transport making use of AF_UNIX on Windows systems, at which time it will be necessary to discriminate between the Named Pipes and the AF_UNIX based transports.

2: This property makes it important that names be chosen randomly to prevent unauthorized access, or that checks against the peer credentials are made, or ideally, both.

Socket Transport (Experimental)

Description

The socket transport supports communication between peers across arbitrary BSD sockets, such as those that are created with nng_socket_pair.

This transport only supports listeners, using nng_listener_create.

note

Attempts to create dialers using this transport will result in NNG_ENOTSUP.

The socket file descriptor is passed to the listener using the NNG_OPT_SOCKET_FD option (as an integer). Setting this option will cause the listener to create a pipe backed by the file descriptor.

The protocol between peers using this transport is compatible with the protocol used for the tcp transport, but this is an implementation detail and subject to change without notice. 1

note

This transport is experimental, and at present is only supported on POSIX platforms. 2

URL Format

This transport uses the URL socket://, without further qualification.

Socket Address

The socket address will be of family NNG_AF_UNSPEC. There are no further socket details available.

Transport Options

The following transport options are supported by this transport.

OptionTypeDescription
NNG_OPT_SOCKET_FDintWrite-only option, that may be set multiple times on a listener. Each time this is set, the listener will create a pipe backed by the given file descriptor passed as an argument.
NNG_OPT_PEER_GIDintRead only option, returns the group ID of the process at the other end of the socket, if platform supports it.
NNG_OPT_PEER_PIDintRead only option, returns the processed ID of the process at the other end of the socket, if platform supports it.
NNG_OPT_PEER_UIDintRead only option, returns the user ID of the process at the other end of the socket, if platform supports it.
NNG_OPT_PEER_ZONEIDintRead only option, returns the zone ID of the process at the other end of the socket, if platform supports it.

note

The NNG_OPT_PEER_GID, NNG_OPT_PEER_PID, NNG_OPT_PEER_UID, and NNG_OPT_PEER_ZONEID options depend on platform support. These behave in exactly the same fashion as for the ipc transport.


1: Specifically it is not compatible with the ipc transport.

2: Windows lacks a suitable socketpair equivalent function we could use.

UDP Transport (Experimental)

Description

The udp transport supports communication between peers using UDP.

UDP is a very light-weight connection-less, unreliable, unordered delivery mechanism.

Both IPv4 and IPv6 are supported when the underlying platform also supports it.

This transport adds an ordering guarantee, so that messages will always be received in the correct order. Messages that arrive out of order, or are duplicated, will be dropped. There may be gaps in the messages received, so applications should not assume that all messages sent will arrive.

note

This transport is experimental.

URL Format

This transport uses URIs using the scheme udp://, followed by an IP address or hostname, followed by a colon and finally a UDP port number. For example, to contact port 8001 on the localhost either of the following URIs could be used: udp://127.0.0.1:8001 or udp://localhost:8001.

A URI may be restricted to IPv6 using the scheme udp6://, and may be restricted to IPv4 using the scheme udp4://.

note

Specifying udp6:// may not prevent IPv4 hosts from being used with IPv4-in-IPv6 addresses, particularly when using a wildcard hostname with listeners. The details of this varies across operating systems.

tip

We recommend using either numeric IP addresses, or names that are specific to either IPv4 or IPv6 to prevent confusion and surprises.

When specifying IPv6 addresses, the address must be enclosed in square brackets ([]) to avoid confusion with the final colon separating the port.

For example, the same port 8001 on the IPv6 loopback address (::1) would be specified as udp://[::1]:8001.

The special value of 0 (INADDR_ANY) can be used for a listener to indicate that it should listen on all interfaces on the host. A short-hand for this form is to omit the IP address entirely. For example, the following two URIs are equivalent, and could be used to listen to port 9999 on the host:

  1. udp://0.0.0.0:9999
  2. udp://:9999

Socket Address

When using an nng_sockaddr structure, the actual structure is either of type nng_sockaddr_in (for IPv4) or nng_sockaddr_in6 (for IPv6).

Transport Options

The following transport options are supported by this transport, where supported by the underlying platform.

OptionTypeDescription
NNG_OPT_LOCADDRnng_sockaddrThe locally bound address, will be either nng_sockaddr_in or nng_sockaddr_in6.
NNG_OPT_REMADDRnng_sockaddrThe remote peer address, will be either nng_sockaddr_in or nng_sockaddr_in6. Only valid for pipe and dialer objects.
NNG_OPT_RECVMAXSZsize_tMaximum size of incoming messages, will be limited to at most 65000.
NNG_OPT_UDP_COPY_MAXsize_tThreshold above which received messages are “loaned” up, rather than a new message being allocated and copied into.
NNG_OPT_UDP_BOUND_PORTintThe locally bound UDP port number (1-65535), read-only for listener objects only.

Maximum Message Size

This transport maps each SP message to a single UDP packet. In order to allow room for network headers, we thus limit the maximum message size to 65000 bytes, minus the overhead for any SP protocol headers.

However, applications are strongly encouraged to only use this transport for very much smaller messages, ideally those that will fit within a single network packet without requiring fragmentation and reassembly.

For Ethernet without jumbo frames, this typically means an MTU of a little less than 1500 bytes. (Specifically, 1452, which allows 28 bytes for IPv4 and UDP, and 20 bytes for the this transport. Reduce by an additional 20 bytes for IPv6.)

Other link layers may have different MTUs, however IPv6 requires a minimum MTU of 1280, which after deducting 48 bytes for IPv6 and UDP headers, and 20 bytes for our transport header, leaves 1212 bytes for user data. If additional allowances are made for SP protocol headers with a default TTL of 8 (resulting in 72 additional bytes for route information), the final user accessible payload will be 1140 bytes. Thus this can be likely be viewed as a safe maximum to employ for SP payload data across all transports.

The maximum message size is negotiated as part of establishing a peering relationship, and oversize messages will be dropped by the sender before going to the network.

The maximum message size to receive can be configured with the NNG_OPT_RECVMAXSZ option.

Keep Alive

This transports maintains a logical “connection” with each peer, to provide a rough facsimile of a connection based semantic. This requires some resource on each peer. In order to ensure that resources are reclaimed when a peer vanishes unexpectedly, a keep-alive mechanism is implemented.

TODO: Document the tunables for this.

Migration Guides

This section provides some guides to aid in migrating software from earlier versions NNG or libnanomsg.

Migrating from NNG 1.x

There are some incompatibities from NNG 1.x, and applications must make certain changes for NNG 2.0. This guide should help with this migration. While we have made reasonable efforts to highlight all of the things that applications could run into, this list is not necessarily exhaustive, and undocumented interfaces may have changed without notice here.

Detecting NNG v2

For applications that need to detect NNG v2 versus older code, the NNG_MAJOR_VERSION macro can be used. This will have numeric value 2 for version 2, and 1 for earlier versions.

note

NNG version 2.0 is not stabilized yet, and while it is in development there is no compatibility guarantee between releases or builds of NNG 2.

Nanomsg Compatibility

Applications using the legacy libnanomsg API will have to be updated to native NNG interfaces. See the Migrating From libnanomsg chapter for details.

Library Initialization

It is now required for applications to initialize the library explicitly before using it. This is done using the nng_init function.

Removed Headers

The following header files are removed, and the declarations they provided are now provided by including <nng/nng.h>. Simply remove any references to them.

  • nng/protocol/bus0/bus.h
  • nng/protocol/pair0/pair.h
  • nng/protocol/pair1/pair.h
  • nng/protocol/pipeline0/pull.h
  • nng/protocol/pipeline0/push.h
  • nng/protocol/pubsub0/pub.h
  • nng/protocol/pubsub0/sub.h
  • nng/protocol/reqrep0/rep.h
  • nng/protocol/reqrep0/req.h
  • nng/protocol/survey0/respond.h
  • nng/protocol/survey0/survey.h
  • nng/supplemental/tls/tls.h
  • nng/supplemental/util/idhash.h
  • nng/supplemental/util/platform.h
  • nng/transport/inproc/inproc.h
  • nng/transport/ipc/ipc.h
  • nng/transport/tcp/tcp.h
  • nng/transport/tls/tls.h
  • nng/transport/ws/websocket.h
  • nng/transport/zerotier/zerotier.h

Renamed Functions

The following functions have been renamed as described by the following table. The old names are available by defining the macro NNG1_TRANSITION in your compilation environment.

Old NameNew Name
nng_closenng_socket_close
nng_recv_aionng_socket_recv
nng_send_aionng_socket_send

Removed Protocol Aliases

The following macro aliases are removed, unless NNG1_TRANSITION is defined in your compilation environment.

  • nng_bus_open
  • nng_pair_open
  • nng_pub_open
  • nng_pull_open
  • nng_push_open
  • nng_rep_open
  • nng_req_open
  • nng_respondent_open
  • nng_sub_open
  • nng_surveyor_open

Just add either 0 or 1 (in the case of PAIRv1) to get the protocol desired. (Forcing the version number to be supplied should avoid surprises later as new versions of protocols are added.)

NNG_FLAG_ALLOC Removed

The NNG_FLAG_ALLOC flag that allowed a zero copy semantic with nng_send and nng_recv is removed. This was implemented mostly to aid legacy nanomsg applications, and it was both error prone and still a bit suboptimal in terms of performance.

Modern code should use one of nng_sendmsg, nng_recvmsg, nng_socket_send, or nng_socket_recv to get the maximum performance benefit. Working directly with nng_msg structures gives more control, reduces copies, and reduces allocation activity.

Error Code Changes

When an operation fails with NNG_ESTOPPED, it means that the associated [nni_aio] object has been permanently stopped and must not be reused. Applications must watch for this error code, and not resubmit an operation that returns it. This is particularly important for callbacks that automatically resubmit operations. Failure to observe this rule will lead to an infinite loop as any further operations on the object will fail immediately with NNG_ESTOPPED.

The error codes NNG_EAMBIGUOUS and NNG_ENOARG have been removed.

AIO Provider API changes

The API used for providers for asynchronous I/O operations has changed slightly.

  • The nng_aio_begin function is removed. However a new nng_aio_reset function should be called instead, before performing any other operations on an aio object. (This simply clears certain fields.)
  • The nng_aio_defer function is replaced, with a very nng_aio_start function. However, this function has slightly different semantics. It will automatically call the callback if the operation cannot be scheduled.
  • Be aware of the new NNG_ESTOPPED error code, for operations on a handle that is being torn down by the consumer.

Transport Specific Functions

Transports have not needed to be registered for a long time now, and the functions for doing so have been removed. These functions can be simply removed from your application:

  • nng_inproc_register
  • nng_ipc_register
  • nng_tls_register
  • nng_tcp_register
  • nng_ws_register
  • nng_wss_register
  • nng_zt_register

Additionally, the header files containing these functions have been removed, such as nng/transport/ipc/ipc.h. Simply remove #include references to those files.

TLS Configuration

The support for configuring TLS via NNG_OPT_TLS_CONFIG, NNG_TLS_AUTH_MODE, NNG_OPT_TLS_CA_FILE, NNG_OPT_TLS_SERVER_NAME, and similar has been removed.

Instead configuration must be performed by allocating a nng_tls_config object, and then setting fields on it using the appropriate functions, after which it may be configured on a listener or dialer using the nng_listener_set_tls or nng_dialer_set_tls functions.

Likewise, when using the streams API, use the nng_stream_listener_set_tls or nng_stream_dialer_set_tls functions.

Note that the declarations needed for TLS configuration are now available in <nng/nng.h>, rather than the supplemental header.

Old TLS Versions Removed

Support for very old TLS versions 1.0 and 1.1 is removed. Further, the NNG_TLS_1_0 and NNG_TLS_1_1 constants are also removed. Applications should use NNG_TLS_1_2 or even NNG_TLS_1_3 instead.

Only One TLS Key/Cert Per Configuration

The ability to configure multiple keys and certificates for a given TLS configuration object is removed. (The nng_tls_config_own_cert will return NNG_EBUSY if it has already been called for the configuration.) The intended purpose was to support alternative cryptographic algorithms, but this is not necessary, was never used, and was error prone.

Support for Local Addresses in Dial URLs Removed

NNG 1.x had an undocumented ability to specify the local address to bind to when dialing, by using the local address in front of the destination address separated by a semicolon. This was provided for legacy libnanomsg compatilibility, and is no longer offered. The correct way to specify a local address is by setting NNG_OPT_LOCADDR on the dialer.

IPC Option Type Changes

The types of NNG_OPT_PEER_GID, NNG_OPT_PEER_PID, NNG_OPT_PEER_UID, and NNG_OPT_PEER_ZONEID have changed from uint64_t to int. The underlying platforms all use 32-bit quantities for these.

Option Functions

The previously deprecated nng_pipe_getopt_xxx family of functions is removed. Applications should use nng_pipe_get and related functions instead.

The socket option function families for nng_getopt and nng_setopt have been removed as well. In this case, use the nng_socket_get and nng_socket_set functions as appropriate.

The _getopt and _setopt functions for contexts, listeners, and dialers are no longer present. Simply changing _getopt to _get or _setopt to _set in the function name should be sufficient in most cases.

The following functions served no useful purpose (after other changes described in this document), and are thus removed:

  • nng_ctx_get_string
  • nng_ctx_set_string
  • nng_ctx_get_uint64
  • nng_dialer_get_ptr
  • nng_dialer_set_ptr
  • nng_dialer_get_uint64
  • nng_dialer_set_uint64
  • nng_listener_get_ptr
  • nng_listener_set_ptr
  • nng_listener_get_uint64
  • nng_listener_set_uint64
  • nng_socket_get_ptr
  • nng_socket_set_ptr
  • nng_socket_get_string
  • nng_socket_set_string
  • nng_socket_get_uint64
  • nng_socket_set_uint64
  • nng_stream_get_ptr
  • nng_stream_set_ptr
  • nng_stream_get_uint64
  • nng_stream_dialer_get_ptr
  • nng_stream_dialer_set_ptr
  • nng_stream_dialer_get_uint64
  • nng_stream_dialer_set_uint64
  • nng_stream_listener_get_ptr
  • nng_stream_listener_set_ptr
  • nng_stream_listener_get_uint64
  • nng_stream_listener_set_uint64
  • nng_ctx_get_ptr (not documented)
  • nng_ctx_set_ptr (not documented)

Untyped Option Functions Removed

The following functions are removed. To access options, use a proper typed access function, such as one ending in a suffix like _bool (to access a bool typed option).

  • nng_ctx_get
  • nng_ctx_set
  • nng_dialer_get
  • nng_dialer_set
  • nng_listener_get
  • nng_listener_set
  • nng_pipe_get
  • nng_socket_get
  • nng_socket_set
  • nng_stream_get
  • nng_stream_set
  • nng_stream_dialer_get
  • nng_stream_dialer_set
  • nng_stream_listener_get
  • nng_stream_listener_set

Stream Options

The ability to set options on streams after they have been created is no longer present. (It turns out that this was not very useful.) All functions nng_stream_set_xxx are removed. For tuning the NNG_OPT_TCP_NODELAY or similar properties, set the option on the listener or dialer that creates the stream instead.

Transport Options

A number of transport options can no longer be set on the socket. Instead these options must be set on the endpoint (dialer or listener) using the appropriate nng_dialer_set or nng_listener_set option. This likely means that it is necessary to allocate and configure the endpoint before attaching it to the socket. This will also afford a much more fine-grained level of control over transport options.

The following options are copied from the socket when creating a dialer or listener, but afterwards will not be changed on the dialer or listener if the socket changes. It is recommended to set them properly on the socket before creating dialers or listeners, or set them explicitly on the dialer or listener directly:

The latter option is a hint for transports and intended to facilitate early detection (and possibly avoidance of extra allocations) of oversize messages, before bringing them into the socket itself.

Socket Options

The NNG_OPT_PROTO, NNG_OPT_PROTONAME, NNG_OPT_PEER, and NNG_OPT_PEERNAME options have been replaced by functions instead of options. Use nng_socket_proto_id, nng_socket_peer_id, nng_socket_proto_name, and nng_socket_peer_name instead. Note that the new functions provide a reference to a static string, and thus do not require allocation, and the returned strings should not be freed. Also the IDs are provided as uint16_t, matching the actual wire protocol values, instead of int.

The NNG_OPT_RAW option has aso been replaced by a function, nng_socket_raw.

The NNG_OPT_SENDFD and NNG_OPT_RECVFD options have been replaced by nng_socket_get_send_poll_fd and nng_socket_get_recv_poll_fd respectively.

The NNG_OPT_SOCKNAME function is removed. This was provided for application use, and never used internally by NNG. Applications should keep track of this information separately.

Subscriptions

The NNG_OPT_SUB_SUBSCRIBE and NNG_OPT_SUB_UNSUBCRIBE options have been replaced by the following functions: nng_sub0_socket_subscribe, nng_sub0_socket_unsubscribe, nng_sub0_ctx_subscribe and nng_sub0_ctx_unsubscribe. These functions, like the options they replace, are only applicable to SUB sockets.

Statistics Use Constified Pointers

A number of the statistics functions take, or return, const nng_stat * instead of plain nng_stat *. The ABI has not changed, but it may be necessary to declare certain methods variables const to avoid warnings about misuse of const.

Wildcards Not Valid in URLs

The use of * to act as a wild card meaning all local interface addresses is removed. The empty string already performs this function, and unlike * is RFC compliant.

URL Option Removed

The NNG_OPT_URL option has been removed. It is replaced by the type safe nng_dialer_get_url and nng_listener_get_url functions, which return an nng_url structure instead of a string.

URL Structure Changes

The details of nng_url have changed significantly, and direct access of the structure is no longer permitted. Intead new accessors functions are provided:

HTTP API

The entire HTTP API has been refactored and should be much simpler to use and more efficient. Applications directly using the HTTP API will need to be fully modified.

A few limits on string lengths of certain values are now applied, which allows us to preallocate values and eliminate certain unreasonable error paths. If values longer than these are supplied in certain APIs they may be silently truncated to the limit:

  • Hostnames are limited per RFC 1035 to 253 characters (not including terminating “.” or zero byte.)
  • HTTP Method names are limited to 32 bytes (the longest IANA registered method is currently 18 bytes, used for WebDAV.)
  • The fixed part of URI pathnames used with HTTP handlers is limited to 1024 bytes. (Longer URIs may be accepted by using [nng_http_handler_set_tree] and matching a parent of the directory component.)

The following API calls have changed so that they are void returns, and cannot fail. They may silently truncate data.

  • [nng_http_req_set_method]
  • [nng_http_res_set_status]
  • [nng_http_handler_collect_body]
  • [nng_http_handler_set_data]
  • [nng_http_handler_set_host]
  • [nng_http_handler_set_method]
  • [nng_http_handler_set_tree]

The HTTP handler objects may not be modified once in use. Previously this would fail with NNG_EBUSY. These checks are removed now, but debug builds will assert if an application tries to do so.

WebSocket API

The NNG_OPT_WSS_REQUEST_HEADERS, NNG_OPT_WSS_RESPONSE_HEADERS and NNG_OPT_WS_OPT_WS_REQUEST_HEADERS, NNG_OPT_WS_RESPONSE_HEADERS have been removed.

The NNG_OPT_WS_REQUEST_HEADER and NNG_OPT_WS_RESPONSE_HEADER option prefixes have been collapsed into just NNG_OPT_WS_HEADER, with slightly different semantics. It still is a prefix (append the name of the header of interest), but setting it can only affect outbound headers (request header for dialers, response header for listeners), and when reading it on a pipe, the value returned is the header sent by the remote peer.

The undocumented hook function signature has changed to reflect changes in the HTTP API.

Security Descriptors (Windows Only)

The NNG_OPT_IPC_SECURITY_DESCRIPTOR option is removed, and replaced with the functions [nng_listener_get_security_descriptor] and [nng_stream_listener_get_security_descriptor].

Security descriptor support is only relevant to Windows, and is presently only supported for IPC when Named Pipes are used. Planned future changes to switch to UNIX domain sockets may eliminate support for security descriptors altogether in NNG.

Command Line Argument Parser Changes

The supplemental function nng_opts_parse and supporting definitions have moved. This functionality is now supplied by a header only library, available in nng/args.h. See nng_args_parse for more information.

ZeroTier Support Removed

The Layer 2 special ZeroTier transport has been removed. It is possible to use NNG with ZeroTier using TCP/IP, and a future update is planned to provided coexistence between ZeroTier & the native stack’s TCP/IP using lwIP.

Migrating from libnanomsg

Previous version of NNG offered direct API compatibility with libnanomsg, but that support is no longer offered in this version.

If your application is still using legacy libnanomsg APIs, you will need to update it for this version of NNG.

Header Files

Most applications can replace all #include <nn/*.h> statements with #include <nng/nng.h>.

Replace -lnanomsg with -lnng. It may be necessary to include additional system libraries, such as -lpthread, depending on your system.

Initialization

It is necessary to explicitly initialize the library, using the nng_init function. This must be called before any other function.

Types

Sockets, dialers, and listeners in libnanomsg are simple integers. In NNG, these are struct types.

Messages are quite different in NNG, with the absence of the POSIX message control headers.

The struct nn_msghdr structure has no equivalent. See nng_msg for the NNG approach to messages. Likewise there is no struct nn_cmsghdr equivalent.

API Conversions

Nanomsg APINNG EquivalentNotes
nn_strerrornng_strerror
nn_errnoNoneErrors are returned directly rather than through errno.
nn_socketVariousUse the appropriate protocol constructor, such as nng_req0_open.
nn_closenng_socket_close
nn_bindnng_listen, nng_listener_createAllocating a listener with nng_lister_create and configuring it offers more capabilities.
nn_connectnng_dial, nng_dialer_createAllocating a dialer with nng_dialer_create and configuring it offers more capabilities.
nn_shutdownnng_listener_close, nng_dialer_close
nn_allocmsgnng_msg_allocThere are significant semantic differences.
nn_freemsgnng_msg_free
nn_reallocmsgnng_msg_realloc
nn_sendnng_send
nn_recvnng_recv
nn_sendmsgnng_sendmsg
nn_getsockoptnng_socket_getNNG has typed accessors for options, and also separate functions for dialers and listeners.
nn_setsockoptnng_socket_set
nn_devicenng_device
nn_pollNoneCan be constructed using nng_aio. Few if any applications ever used this API.
nn_termnng_finiThe nng_fini API can do this, but is not recommended except when debugging memory leaks.
nn_get_statisticnng_stats_getThe statistics in NNG are completely different, with different semantics and no stability guarantees.
NN_POLLINNoneUsed only with nn_poll.
NN_POLLOUTNoneUsed only with nn_poll.
NN_MSGnng_sendmsg, nng_recvmsgThere are differences as a separate nng_msg structure is involved.
NN_CMSG_ALIGNNone
NN_CMSG_FIRSTHDRNone
NN_CMSG_NXTHDRNone
NN_CMSG_DATANone
NN_CMSG_LENNone
NN_CMSG_SPACENone
struct nn_iovecnng_iov
struct nn_msghdrnng_msg
struct nn_cmsghdrnng_msg and nng_msg_header

Options

The following options are changed.

Nanomsg OptionNNG EqvaivalentNotes
NN_LINGERNoneNNG does not support tuning this.
NN_SNDBUFNNG_OPT_SENDBUFNNG value is given in messages, not bytes.
NN_RCVBUFNNG_OPT_RECVBUFNNG value is given in messages, not bytes.
NN_SNDTIMEONNG_OPT_SENDTIMEO
NN_RCVTIMEONNG_OPT_RECVTIMEO
NN_RECONNECT_IVLNNG_OPT_RECONNMINT
NN_RECONNECT_IVL_MAXNNG_OPT_RECONNMAXT
NN_SNDPRIONoneNot supported in NNG yet.
NN_RCVPRIONoneNot supported in NNG yet.
NN_RCVFDnng_socket_get_recv_poll_fdNo longer an option, use a function call.
NN_SNDFDnng_socket_get_send_poll_fdNo longer an option, use a function call.
NN_DOMAINNoneNNG options are not divided by domain or protocol.
NN_PROTOCOLnng_socket_proto_idNo longer an option. See also nng_socket_proto_name.
NN_IPV4ONLYNoneUse URL such as tcp4:// to obtain this functionality.
NN_SOCKET_NAMENoneRemoved from NNG.
NN_MAXTTLNNG_OPT_MAXTTL
NN_SUB_SUBSCRIBEnng_sub0_socket_subscribeNo longer an option, use a function call.
NN_SUB_UNSUBSCRIBEnng_sub0_socket_unsubscribeNo longer an option, use a function call.

Error Codes

Most of the error codes have similar names in NNG, just prefixed with NNG_. There are some exceptions. Be aware that the numeric values are not the same.

Nanomsg ErrorNNG ErrorNotes
EINTRNNG_EINTR
ENOMEMNNG_ENOMEM
EINVALNNG_EINVAL, NNG_EADDRINVAL, NNG_EBADTYPENNG discrimates between different types of errors.
EBUSYNNG_EBUSY
ETIMEDOUTNNG_ETIMEDOUT
ECONNREFUSEDNNG_ECONNREFUSED
EBADFNNG_ECLOSED, NNG_ECANCELEDCanceling an operation returns differently than using an invalid or closed object.
EAGAINNNG_EAGAIN
ENOTSUPNNG_ENOTSUP
EADDRINUSENNG_EADDRINUSE
EFSMNNG_ESTATENot a legal POSIX errno value.
ENOENTNNG_ENOENT
EPROTONNG_EPROTO
EHOSTUNREACHNNG_EUNREACHABLE
EACCCESNNG_EPERM, NNG_EWRITEONLY, NNG_EREADONLY, NNG_ECRYPTO, NNG_EPEERAUTHNNG has more fine grained reasons for access failures.
EMSGSIZENNG_EMSGSIZE
ECONNABORTEDNNG_ECONNABORTED
ECONNRESETNNG_ECONNRESET
EEXISTNNG_EEXIST
EMFILENNG_ENOFILES
ENOSPCNNG_ENOSPC

Local Addresses for Dialing

The ability to specify the source address in the URL,to use when using nn_dial inside the URL is not present in NNG. The correct way to specify the local address is using the NNG_OPT_LOCADDR option on the dialer before starting to dial.

Index

ABI, 1
abstract sockets, 1
abstract://, 1
accept, 1
AF_UNIX, 1
aio, 1
allocations, 1
application binary interface, 1
asynchronous I/O, 1
back-pressure, 1
backtrace, 1, 2, 3, 4
best-effort, 1
bind, 1
body, 1
BUS, 1
BUS protocol, 1
callback, 1
clock, 1
command-line arguments, 1
concurrency, 1
concurrent, 1
condition variable, 1
connect, 1
connection, 1
context, 1
CPU-bound, 1
duration, 1
error message, 1
event loop, 1
flow control, 1
gather, 1
getopt, 1
half-duplex, 1
header, 1
idempotent, 1
INADDR_ANY, 1
inproc, 1
inproc transport, 1
inproc://, 1
intra-process, 1
ipc, 1
ipc transport, 1
ipc://, 1
IPv4, 1
IPv6, 1
leader election, 1
listen, 1
load-balancing, 1, 2, 3
Log handlers, 1
memory, 1
messages, 1
MTU, 1
mutex, 1
named pipes, 1
NNG_AF_UNSPEC, 1
nng_aio, 1
nng_aio_abort, 1
nng_aio_alloc, 1
nng_aio_busy, 1
nng_aio_cancel, 1
nng_aio_count, 1
nng_aio_free, 1
nng_aio_get_msg, 1
nng_aio_reap, 1
nng_aio_result, 1
nng_aio_set_expire, 1
nng_aio_set_iov, 1, 2
nng_aio_set_msg, 1
nng_aio_stop, 1
nng_aio_wait, 1
nng_alloc, 1
nng_arg_spec, 1
nng_args_parse, 1
nng_bus0_open, 1
nng_bus0_open_raw, 1
nng_clock, 1
nng_ctx, 1
nng_ctx_close, 1
nng_ctx_id, 1
NNG_CTX_INITIALIZER, 1
nng_ctx_open, 1
nng_ctx_recv, 1
nng_ctx_recvmsg, 1
nng_ctx_send, 1
nng_ctx_sendmsg, 1
nng_cv, 1
nng_cv_alloc, 1
nng_cv_free, 1
nng_cv_until, 1
nng_cv_wait, 1
nng_cv_wake, 1
nng_cv_wake1, 1
nng_duration, 1
NNG_DURATION_DEFAULT, 1
NNG_DURATION_INFINITE, 1
NNG_DURATION_ZERO, 1
NNG_ECANCELED, 1
nng_fini, 1
NNG_FLAG_NONBLOCK, 1, 2
nng_free, 1
nng_http, 1
nng_http_add_header, 1
nng_http_client, 1
nng_http_client_alloc, 1
nng_http_client_connect, 1
nng_http_client_free, 1
nng_http_client_get_tls, 1
nng_http_client_set_tls, 1
nng_http_close, 1
nng_http_copy_data, 1
nng_http_del_header, 1
nng_http_get_data, 1
nng_http_get_header, 1
nng_http_get_method, 1
nng_http_get_reason, 1
nng_http_get_status, 1
nng_http_get_version, 1
nng_http_handler, 1
nng_http_handler_alloc, 1
nng_http_handler_alloc_directory, 1
nng_http_handler_alloc_file, 1
nng_http_handler_alloc_redirect, 1
nng_http_handler_alloc_static, 1
nng_http_handler_collect_body, 1
nng_http_handler_set_data, 1
nng_http_handler_set_host, 1
nng_http_handler_set_method, 1
nng_http_handler_set_tree, 1
nng_http_hijack, 1
nng_http_next_header, 1
nng_http_read, 1
nng_http_read_all, 1
nng_http_read_response, 1
nng_http_reset, 1
nng_http_set_data, 1
nng_http_set_header, 1
nng_http_set_method, 1
nng_http_set_status, 1
nng_http_set_uri, 1
nng_http_set_version, 1
nng_http_status, 1
nng_http_transact, 1, 2
nng_http_write, 1
nng_http_write_all, 1
nng_http_write_request, 1
nng_http_write_response, 1
nng_id_alloc, 1
nng_id_get, 1
nng_id_map, 1
nng_id_map_alloc, 1
nng_id_map_free, 1
nng_id_map_set, 1
nng_id_remove, 1
nng_id_visit, 1
nng_init, 1
nng_iov, 1, 2
nng_log, 1
nng_log_auth, 1
nng_log_get_level, 1
nng_log_level, 1
nng_log_set_facility, 1
nng_log_set_level, 1
nng_log_set_logger, 1
nng_logger, 1
NNG_MAJOR_VERSION, 1
NNG_MINOR_VERSION, 1
nng_msg, 1
nng_msg_alloc, 1
nng_msg_append, 1
nng_msg_body, 1
nng_msg_capacity, 1
nng_msg_chop, 1
nng_msg_clear, 1
nng_msg_dup, 1
nng_msg_free, 1
nng_msg_get_pipe, 1
nng_msg_header, 1
nng_msg_header_append, 1
nng_msg_header_chop, 1
nng_msg_header_clear, 1
nng_msg_header_insert, 1
nng_msg_header_len, 1
nng_msg_header_trim, 1
nng_msg_insert, 1
nng_msg_len, 1
nng_msg_realloc, 1
nng_msg_reserve, 1
nng_msg_set_pipe, 1
nng_msg_trim, 1
nng_msleep, 1
nng_mtx, 1
nng_mtx_free, 1
nng_mtx_lock, 1, 2
nng_mtx_unlock, 1
nng_null_logger, 1
NNG_OPT_LISTEN_FD, 1
NNG_OPT_REQ_RESENDTICK, 1
NNG_OPT_REQ_RESENDTIME, 1
NNG_OPT_SOCKET_FD, 1
NNG_OPT_SUB_PREFNEW, 1
NNG_OPT_SURVEYOR_SURVEYTIME, 1
nng_pair0_open, 1
nng_pair0_open_raw, 1
nng_pair1_open, 1
nng_pair1_open_poly, 1
nng_pair1_open_raw, 1
NNG_PATCH_VERSION, 1
nng_pipe, 1
nng_pipe_close, 1
nng_pipe_dialer, 1
NNG_PIPE_EV_ADD_POST, 1
NNG_PIPE_EV_ADD_PRE, 1
NNG_PIPE_EV_REM_POST, 1
nng_pipe_get_addr, 1
nng_pipe_get_bool, 1
nng_pipe_get_int, 1
nng_pipe_get_ms, 1
nng_pipe_get_size, 1
nng_pipe_get_string, 1
nng_pipe_id, 1
NNG_PIPE_INITIALIZER, 1
nng_pipe_listener, 1
nng_pipe_notify, 1
nng_pipe_socket, 1
nng_pub0_open, 1
nng_pub0_open_raw, 1
nng_pull0_open, 1
nng_pull0_open_raw, 1
nng_push0_open, 1
nng_push0_open_raw, 1
nng_random, 1
nng_recv, 1
nng_recvmsg, 1
nng_rep0_open, 1
nng_rep0_open_raw, 1
nng_req0_open, 1
nng_req0_open_raw, 1
nng_respondent0_open, 1
nng_respondent0_open_raw, 1
nng_send, 1
nng_sendmsg, 1
nng_sleep_aio, 1
nng_socket, 1
nng_socket_close, 1
nng_socket_get_recv_poll_fd, 1
nng_socket_get_send_poll_fd, 1
nng_socket_id, 1
nng_socket_pair, 1
nng_socket_peer_id, 1
nng_socket_peer_name, 1
nng_socket_proto_id, 1
nng_socket_proto_name, 1
nng_socket_raw, 1
nng_socket_recv, 1
nng_socket_send, 1
nng_stat, 1
nng_stat_bool, 1
NNG_STAT_BOOLEAN, 1
nng_stat_child, 1
NNG_STAT_COUNTER, 1
nng_stat_desc, 1
nng_stat_find, 1
nng_stat_find_dialer, 1
nng_stat_find_listener, 1
nng_stat_find_socket, 1
NNG_STAT_ID, 1
NNG_STAT_LEVEL, 1
nng_stat_name, 1
nng_stat_next, 1
nng_stat_parent, 1
NNG_STAT_SCOPE, 1
NNG_STAT_STRING, 1
nng_stat_string, 1
nng_stat_timestamp, 1
nng_stat_type, 1
nng_stat_unit, 1
nng_stat_value, 1
nng_stats_free, 1
nng_stats_get, 1
nng_stderr_logger, 1
nng_strdup, 1
nng_stream, 1
nng_stream_close, 1
nng_stream_dialer_alloc, 1
nng_stream_dialer_alloc_url, 1
nng_stream_dialer_close, 1
nng_stream_dialer_dial, 1
nng_stream_dialer_free, 1
nng_stream_dialer_get_addr, 1
nng_stream_dialer_get_bool, 1
nng_stream_dialer_get_int, 1
nng_stream_dialer_get_ms, 1
nng_stream_dialer_get_size, 1
nng_stream_dialer_get_string, 1
nng_stream_dialer_get_tls, 1
nng_stream_dialer_set_addr, 1
nng_stream_dialer_set_bool, 1
nng_stream_dialer_set_int, 1
nng_stream_dialer_set_ms, 1
nng_stream_dialer_set_size, 1
nng_stream_dialer_set_string, 1
nng_stream_dialer_set_tls, 1
nng_stream_dialer_stop, 1
nng_stream_free, 1
nng_stream_get_addr, 1
nng_stream_get_bool, 1
nng_stream_get_int, 1
nng_stream_get_ms, 1
nng_stream_get_size, 1
nng_stream_get_string, 1
nng_stream_listener, 1, 2
nng_stream_listener_accept, 1
nng_stream_listener_alloc, 1
nng_stream_listener_alloc_url, 1
nng_stream_listener_close, 1
nng_stream_listener_free, 1
nng_stream_listener_get_addr, 1
nng_stream_listener_get_bool, 1
nng_stream_listener_get_int, 1
nng_stream_listener_get_ms, 1
nng_stream_listener_get_size, 1
nng_stream_listener_get_string, 1
nng_stream_listener_get_tls, 1
nng_stream_listener_listen, 1
nng_stream_listener_set_addr, 1
nng_stream_listener_set_bool, 1
nng_stream_listener_set_int, 1
nng_stream_listener_set_ms, 1
nng_stream_listener_set_size, 1
nng_stream_listener_set_string, 1
nng_stream_listener_set_tls, 1
nng_stream_listener_stop, 1
nng_stream_recv, 1
nng_stream_send, 1
nng_stream_stop, 1
nng_strerror, 1
nng_strfree, 1
nng_sub0_open, 1
nng_sub0_open_raw, 1
nng_surveyor0_open, 1
nng_surveyor0_open_raw, 1
nng_system_logger, 1
nng_thread, 1
nng_thread_create, 1
nng_thread_destroy, 1
nng_thread_set_name, 1
nng_time, 1
NNG_UNIT_BYTES, 1
NNG_UNIT_EVENTS, 1
NNG_UNIT_MESSAGES, 1
NNG_UNIT_MILLIS, 1
NNG_UNIT_NONE, 1
nng_url_clone, 1
nng_url_fragment, 1
nng_url_free, 1
nng_url_hostname, 1
nng_url_parse, 1
nng_url_path, 1
nng_url_port, 1
nng_url_query, 1
nng_url_resolve_port, 1
nng_url_scheme, 1
nng_url_sprintf, 1
nng_url_userinfo, 1
nng_version, 1
nni_http_get_uri, 1
PAIR, 1
PAIR protocol, 1
pipe, 1
pipeline pattern, 1, 2, 3
polyamorous mode, 1
port number, 1
protocol, 1
PUB, 1
PUB protocol, 1
publisher, 1
PULL, 1
PULL protocol, 1
PUSH, 1
PUSH protocol, 1
random number, 1
raw mode, 1
REP, 1
REP protocol, 1
REQ, 1
REQ protocol, 1
request/reply pattern, 1, 2, 3
RESPONDENT, 1
RESPONDENT protocol, 1
scatter, 1
scheduling, 1
service discovery, 1
socket, 1
socket, 1
socket activation, 1
socket transport, 1
socket://, 1
state machines, 1
statistics, 1
stream factory, 1
streams, 1
SUB, 1
SUB protocol, 1
subscriber, 1
survey pattern, 1, 2
SURVEYOR, 1
SURVEYOR protocol, 1
synchronization primitives, 1
threads, 1
thundering herd, 1
timeout, 1
TLS, 1, 2
UDP, 1
udp transport, 1
udp://, 1
Universal Resource Locator, 1
UNIX domain sockets, 1
unix://, 1
URI, 1
URL, 1, 2
version number, 1
voting, 1