fixes #759 Add some demo programs
diff --git a/demo/README b/demo/README
new file mode 100644
index 0000000..4ee4cf8
--- /dev/null
+++ b/demo/README
@@ -0,0 +1,5 @@
+The programs here are intended to demonstrate how to use the nanomsg API.  
+The intention is that you can use these programs (or parts thereof) in
+your own code.
+
+We welcome further contributions here.
diff --git a/demo/async_demo.c b/demo/async_demo.c
new file mode 100644
index 0000000..b82a2aa
--- /dev/null
+++ b/demo/async_demo.c
@@ -0,0 +1,284 @@
+/*
+    Copyright 2016 Garrett D'Amore <garrett@damore.org>
+
+    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.
+
+    "nanomsg" is a trademark of Martin Sustrik
+*/
+
+/*  This program serves as an example for how to write an async RPC service,
+    using the RAW request/reply pattern and nn_poll.  The server receives
+    messages and keeps them on a list, replying to them.
+
+    Our demonstration application layer protocol is simple.  The client sends
+    a number of milliseconds to wait before responding.  The server just gives
+    back an empty reply after waiting that long.
+
+    To run this program, start the server as async_demo <url> -s
+    Then connect to it with the client as async_client <url> <msec>.
+
+    For example:
+
+    % ./async_demo tcp://127.0.0.1:5555 -s &
+    % ./async_demo tcp://127.0.0.1:5555 323
+    Request took 324 milliseconds.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <sys/time.h>
+
+#include <nanomsg/nn.h>
+#include <nanomsg/reqrep.h>
+
+/*  MAXJOBS is a limit on the on the number of outstanding requests we
+    can queue.  We will not accept new inbound jobs if we have more than
+    this queued.  The reason for this limit is to prevent a bad client
+    from consuming all server resources with new job requests. */
+
+#define MAXJOBS 100
+
+/*  The server keeps a list of work items, sorted by expiration time,
+    so that we can use this to set the timeout to the correct value for
+    use in poll.  */
+struct work {
+    struct work *next;
+    struct nn_msghdr request;
+    uint64_t expire;
+    void *control;
+};
+
+/*  Return the UNIX time in milliseconds.  You'll need a working
+    gettimeofday(), so this won't work on Windows.  */
+uint64_t milliseconds (void)
+{
+    struct timeval tv;
+    gettimeofday (&tv, NULL);
+    return (((uint64_t)tv.tv_sec * 1000) + ((uint64_t)tv.tv_usec / 1000));
+}
+    
+/*  The server runs forever. */
+int server(const char *url)
+{
+    int fd; 
+    struct work *worklist = NULL;
+    int npending = 0;
+
+    /*  Create the socket. */
+    fd = nn_socket(AF_SP_RAW, NN_REP);
+    if (fd < 0) {
+        fprintf (stderr, "nn_socket: %s\n", nn_strerror (nn_errno ()));
+        return (-1);
+    }
+
+    /*  Bind to the URL.  This will bind to the address and listen
+        synchronously; new clients will be accepted asynchronously
+        without further action from the calling program. */
+
+    if (nn_bind (fd, url) < 0) {
+        fprintf (stderr, "nn_bind: %s\n", nn_strerror (nn_errno ()));
+        nn_close (fd);
+        return (-1);
+    }
+
+    /*  Main processing loop. */
+
+    for (;;) {
+        uint32_t timer;
+        int rc;
+        int timeout;
+        uint64_t now;
+        struct work *work, **srch;
+        uint8_t *body;
+        void *control;
+        struct nn_iovec iov;
+        struct nn_msghdr hdr;
+        struct nn_pollfd pfd[1];
+
+        /*  Figure out if any work requests are finished, and can be
+            responded to. */
+
+        timeout = -1;
+        while ((work = worklist) != NULL) {
+
+            now = milliseconds ();
+            if (work->expire > now) {
+                timeout = (work->expire - now);
+                break;
+            }
+            worklist = work->next;
+            npending--;
+            rc = nn_sendmsg (fd, &work->request, NN_DONTWAIT);
+            if (rc < 0) {
+                fprintf (stderr, "nn_sendmsg: %s\n",
+                    nn_strerror (nn_errno ()));
+                nn_freemsg (work->request.msg_control);
+            }
+            free (work);
+        }
+
+        /*  This check ensures that we don't allow more than a set limit
+            of concurrent jobs to be queued.  This protects us from resource
+            exhaustion by malicious or defective clients. */
+
+        if (npending >= MAXJOBS) {
+            nn_poll (pfd, 0, timeout);
+            continue;
+        }
+
+        pfd[0].fd = fd;
+        pfd[0].events = NN_POLLIN;
+        pfd[0].revents = 0;
+        nn_poll (pfd, 1, timeout);
+
+        if ((pfd[0].revents & NN_POLLIN) == 0) {
+            continue;
+        }
+
+        /*  So there should be a message waiting for us to receive.
+            We handle it by parsing it, creating a work request for it,
+            and adding the work request to the worklist. */
+
+        memset (&hdr, 0, sizeof (hdr));
+        control = NULL;
+        iov.iov_base = &body;
+        iov.iov_len = NN_MSG;
+        hdr.msg_iov = &iov;
+        hdr.msg_iovlen = 1;
+        hdr.msg_control = &control;
+        hdr.msg_controllen = NN_MSG;
+
+        rc = nn_recvmsg (fd, &hdr, 0);
+        if (rc < 0) {
+            /*  Any error here is unexpected. */
+            fprintf (stderr, "nn_recv: %s\n", nn_strerror (nn_errno ()));
+            break;
+        }
+        if (rc != sizeof (uint32_t)) {
+            fprintf (stderr, "nn_recv: wanted %d, but got %d\n",
+                (int) sizeof (uint32_t), rc);
+            nn_freemsg (body);
+            nn_freemsg (control);
+            continue;
+        }
+
+        memcpy (&timer, body, sizeof (timer));
+        nn_freemsg (body);
+
+        work = malloc (sizeof (*work));
+        if (work == NULL) {
+            fprintf (stderr, "malloc: %s\n", strerror (errno));
+            /*  Fatal error -- other programs can try to handle it better. */
+            break;
+        }
+        memset (work, 0, sizeof (*work));
+        work->expire = milliseconds () + ntohl (timer);
+        work->control = control;
+        work->request.msg_iovlen = 0;  /*  No payload data to send. */
+        work->request.msg_iov = NULL;
+        work->request.msg_control = &work->control;
+        work->request.msg_controllen = NN_MSG;
+
+        /*  Insert the work request into the list in order. */
+        srch = &worklist;
+        for (;;) {
+            if ((*srch == NULL) || ((*srch)->expire > work->expire)) {
+                work->next = *srch;
+                *srch = work;
+                npending++;
+                break;
+            }
+            srch = &((*srch)->next);
+        }
+    }
+
+    /*  This may wind up orphaning requests in the queue.   We are going
+        to exit with an error anyway, so don't worry about it. */
+
+    nn_close (fd);
+    return (-1);
+}
+
+/*  The client runs just once, and then returns. */
+int client (const char *url, const char *msecstr)
+{
+    int fd;
+    int rc;
+    char *greeting;
+    uint8_t msg[sizeof (uint32_t)];
+    uint64_t start;
+    uint64_t end;
+    unsigned msec;
+
+    msec = atoi(msecstr);
+
+    fd = nn_socket (AF_SP, NN_REQ);
+    if (fd < 0) {
+        fprintf (stderr, "nn_socket: %s\n", nn_strerror (nn_errno ()));
+        return (-1);
+    }
+
+    if (nn_connect (fd, url) < 0) {
+        fprintf (stderr, "nn_socket: %s\n", nn_strerror (nn_errno ()));
+        nn_close (fd);
+        return (-1);        
+    }
+
+    msec = htonl(msec);
+    memcpy (msg, &msec, sizeof (msec));
+
+    start = milliseconds ();
+
+    if (nn_send (fd, msg, sizeof (msg), 0) < 0) {
+        fprintf (stderr, "nn_send: %s\n", nn_strerror (nn_errno ()));
+        nn_close (fd);
+        return (-1);
+    }
+
+    rc = nn_recv (fd, &msg, sizeof (msg), 0);
+    if (rc < 0) {
+        fprintf (stderr, "nn_recv: %s\n", nn_strerror (nn_errno ()));
+        nn_close (fd);
+        return (-1);
+    }
+
+    nn_close (fd);
+
+    end = milliseconds ();
+
+    printf ("Request took %u milliseconds.\n", (uint32_t)(end - start));
+    return (0);
+}
+
+int main (int argc, char **argv)
+{
+    int rc;
+    if (argc < 3) {
+        fprintf (stderr, "Usage: %s <url> [-s|name]\n", argv[0]);
+        exit (EXIT_FAILURE);
+    }
+    if (strcmp (argv[2], "-s") == 0) {
+        rc = server (argv[1]);
+    } else {
+        rc = client (argv[1], argv[2]);
+    }
+    exit (rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
+}
diff --git a/demo/pthread_demo.c b/demo/pthread_demo.c
new file mode 100644
index 0000000..b377682
--- /dev/null
+++ b/demo/pthread_demo.c
@@ -0,0 +1,244 @@
+/*
+    Copyright 2016 Garrett D'Amore <garrett@damore.org>
+
+    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.
+
+    "nanomsg" is a trademark of Martin Sustrik
+*/
+
+/*  This program serves as an example for how to write a threaded RPC service,
+    using the RAW request/reply pattern and pthreads.  Multiple worker threads
+    are spawned on a single socket, and each worker processes jobs in order.
+
+    Our demonstration application layer protocol is simple.  The client sends
+    a number of milliseconds to wait before responding.  The server just gives
+    back an empty reply after waiting that long.
+
+    To run this program, start the server as pthread_demo <url> -s
+    Then connect to it with the client as pthread_client <url> <msec>.
+
+    For example:
+
+    % ./pthread_demo tcp://127.0.0.1:5555 -s &
+    % ./pthread_demo tcp://127.0.0.1:5555 323
+    Request took 324 milliseconds.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <poll.h>
+#include <sys/time.h>
+#include <pthread.h>
+
+#include <nanomsg/nn.h>
+#include <nanomsg/reqrep.h>
+
+/*  MAXWORKERS is a limit on the on the number of workers we will fire
+    off.  Since each worker processes jobs sequentially, this is a limit
+    on the concurrency of the server.  New inbound messages will queue up
+    waiting for a worker to receive them. */
+
+#define MAXWORKERS 100
+
+/*  Return the UNIX time in milliseconds.  You'll need a working
+    gettimeofday(), so this won't work on Windows.  */
+uint64_t milliseconds (void)
+{
+    struct timeval tv;
+    gettimeofday (&tv, NULL);
+    return (((uint64_t)tv.tv_sec * 1000) + ((uint64_t)tv.tv_usec / 1000));
+}
+
+void *worker (void *arg)
+{
+    int fd = (intptr_t)arg; 
+
+    /*  Main processing loop. */
+
+    for (;;) {
+        uint32_t timer;
+        int rc;
+        int timeout;
+        uint8_t *body;
+        void *control;
+        struct nn_iovec iov;
+        struct nn_msghdr hdr;
+
+        memset (&hdr, 0, sizeof (hdr));
+        control = NULL;
+        iov.iov_base = &body;
+        iov.iov_len = NN_MSG;
+        hdr.msg_iov = &iov;
+        hdr.msg_iovlen = 1;
+        hdr.msg_control = &control;
+        hdr.msg_controllen = NN_MSG;
+
+        rc = nn_recvmsg (fd, &hdr, 0);
+        if (rc < 0) {
+            if (nn_errno () == EBADF) {
+                return (NULL);   /* Socket closed by another thread. */
+            }
+            /*  Any error here is unexpected. */
+            fprintf (stderr, "nn_recv: %s\n", nn_strerror (nn_errno ()));
+            break;
+        }
+
+        if (rc != sizeof (uint32_t)) {
+            fprintf (stderr, "nn_recv: wanted %d, but got %d\n",
+                (int) sizeof (uint32_t), rc);
+            nn_freemsg (body);
+            nn_freemsg (control);
+            continue;
+        }
+
+        memcpy (&timer, body, sizeof (timer));
+        nn_freemsg (body);
+
+        /*  Poor mans usleep but in msec. */
+        poll (NULL, 0, ntohl (timer));
+
+        hdr.msg_iov = NULL;
+        hdr.msg_iovlen = 0;
+
+        rc = nn_sendmsg (fd, &hdr, 0);
+        if (rc < 0) {
+            fprintf (stderr, "nn_send: %s\n", nn_strerror (nn_errno ()));
+            nn_freemsg (control);
+        }
+    }
+
+    /*  We got here, so close the file.  That will cause the other threads
+        to shut down too. */
+
+    nn_close (fd);
+    return (NULL);
+}
+/*  The server runs forever. */
+int server(const char *url)
+{
+    int fd; 
+    int i;
+    pthread_t tids [MAXWORKERS];
+    int rc;
+
+    /*  Create the socket. */
+    fd = nn_socket(AF_SP_RAW, NN_REP);
+    if (fd < 0) {
+        fprintf (stderr, "nn_socket: %s\n", nn_strerror (nn_errno ()));
+        return (-1);
+    }
+
+    /*  Bind to the URL.  This will bind to the address and listen
+        synchronously; new clients will be accepted asynchronously
+        without further action from the calling program. */
+
+    if (nn_bind (fd, url) < 0) {
+        fprintf (stderr, "nn_bind: %s\n", nn_strerror (nn_errno ()));
+        nn_close (fd);
+        return (-1);
+    }
+
+    memset (tids, 0, sizeof (tids));
+
+    /*  Start up the threads. */
+    for (i = 0; i < MAXWORKERS; i++) {
+        rc = pthread_create (&tids[i], NULL, worker, (void *)(intptr_t)fd);
+        if (rc < 0) {
+            fprintf (stderr, "pthread_create: %s\n", strerror (rc));
+            nn_close (fd);
+            break;
+        }
+    }
+
+    /*  Now wait on them to finish. */
+    for (i = 0; i < MAXWORKERS; i++) {
+        if (tids[i] != NULL) {
+            pthread_join (tids[i], NULL);
+        }
+    }
+    return (-1);
+}
+
+/*  The client runs just once, and then returns. */
+int client (const char *url, const char *msecstr)
+{
+    int fd;
+    int rc;
+    char *greeting;
+    uint8_t msg[sizeof (uint32_t)];
+    uint64_t start;
+    uint64_t end;
+    unsigned msec;
+
+    msec = atoi(msecstr);
+
+    fd = nn_socket (AF_SP, NN_REQ);
+    if (fd < 0) {
+        fprintf (stderr, "nn_socket: %s\n", nn_strerror (nn_errno ()));
+        return (-1);
+    }
+
+    if (nn_connect (fd, url) < 0) {
+        fprintf (stderr, "nn_socket: %s\n", nn_strerror (nn_errno ()));
+        nn_close (fd);
+        return (-1);        
+    }
+
+    msec = htonl(msec);
+    memcpy (msg, &msec, sizeof (msec));
+
+    start = milliseconds ();
+
+    if (nn_send (fd, msg, sizeof (msg), 0) < 0) {
+        fprintf (stderr, "nn_send: %s\n", nn_strerror (nn_errno ()));
+        nn_close (fd);
+        return (-1);
+    }
+
+    rc = nn_recv (fd, &msg, sizeof (msg), 0);
+    if (rc < 0) {
+        fprintf (stderr, "nn_recv: %s\n", nn_strerror (nn_errno ()));
+        nn_close (fd);
+        return (-1);
+    }
+
+    nn_close (fd);
+
+    end = milliseconds ();
+
+    printf ("Request took %u milliseconds.\n", (uint32_t)(end - start));
+    return (0);
+}
+
+int main (int argc, char **argv)
+{
+    int rc;
+    if (argc < 3) {
+        fprintf (stderr, "Usage: %s <url> [-s|name]\n", argv[0]);
+        exit (EXIT_FAILURE);
+    }
+    if (strcmp (argv[2], "-s") == 0) {
+        rc = server (argv[1]);
+    } else {
+        rc = client (argv[1], argv[2]);
+    }
+    exit (rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
+}
diff --git a/demo/pubsub_demo.c b/demo/pubsub_demo.c
new file mode 100644
index 0000000..f9a8fc7
--- /dev/null
+++ b/demo/pubsub_demo.c
@@ -0,0 +1,178 @@
+/*
+    Copyright 2016 Garrett D'Amore <garrett@damore.org>
+
+    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.
+
+    "nanomsg" is a trademark of Martin Sustrik
+*/
+
+/*  This program serves as an example for how to write a simple PUB SUB
+    service, The server is just a single threaded for loop which broadcasts
+    messages to clients, every so often.  The message is a binary format
+    message, containing two 32-bit unsigned integers.  The first is UNIX time,
+    and the second is the number of directly connected subscribers.
+
+    The clients stay connected and print a message with this information
+    along with their process ID to standard output.
+
+    To run this program, start the server as pubsub_demo <url> -s
+    Then connect to it with the client as pubsub_demo <url>
+    For example:
+
+    % ./pubsub_demo tcp://127.0.0.1:5555 -s &
+    % ./pubsub_demo tcp://127.0.0.1:5555 &
+    % ./pubsub_demo tcp://127.0.0.1:5555 &
+    11:23:54 <pid 1254> There are 2 clients connected.
+    11:24:04 <pid 1255> There are 2 clients connected.
+    ..
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <netinet/in.h>  /* For htonl and ntohl */
+#include <unistd.h>
+
+#include <nanomsg/nn.h>
+#include <nanomsg/pubsub.h>
+
+/*  The server runs forever. */
+int server(const char *url)
+{
+    int fd; 
+
+    /*  Create the socket. */
+    fd = nn_socket (AF_SP, NN_PUB);
+    if (fd < 0) {
+        fprintf (stderr, "nn_socket: %s\n", nn_strerror (nn_errno ()));
+        return (-1);
+    }
+
+    /*  Bind to the URL.  This will bind to the address and listen
+        synchronously; new clients will be accepted asynchronously
+        without further action from the calling program. */
+
+    if (nn_bind (fd, url) < 0) {
+        fprintf (stderr, "nn_bind: %s\n", nn_strerror (nn_errno ()));
+        nn_close (fd);
+        return (-1);
+    }
+
+    /*  Now we can just publish results.  Note that there is no explicit
+        accept required.  We just start writing the information. */
+
+    for (;;) {
+        uint8_t msg[2 * sizeof (uint32_t)];
+        uint32_t secs, subs;
+        int rc;
+
+        secs = (uint32_t) time (NULL);
+        subs = (uint32_t) nn_get_statistic (fd, NN_STAT_CURRENT_CONNECTIONS);
+
+        secs = htonl (secs);
+        subs = htonl (subs);
+
+        memcpy (msg, &secs, sizeof (secs));
+        memcpy (msg + sizeof (secs), &subs, sizeof (subs));
+
+        rc = nn_send (fd, msg, sizeof (msg), 0);
+        if (rc < 0) {
+            /*  There are several legitimate reasons this can fail.
+                We note them for debugging purposes, but then ignore
+                otherwise. */
+            fprintf (stderr, "nn_send: %s (ignoring)\n",
+                nn_strerror (nn_errno ()));
+        }
+        sleep(10);
+    }
+
+    /* NOTREACHED */
+    nn_close (fd);
+    return (-1);
+}
+
+/*  The client runs in a loop, displaying the content. */
+int client (const char *url)
+{
+    int fd;
+    int rc;
+
+    fd = nn_socket (AF_SP, NN_SUB);
+    if (fd < 0) {
+        fprintf (stderr, "nn_socket: %s\n", nn_strerror (nn_errno ()));
+        return (-1);
+    }
+
+    if (nn_connect (fd, url) < 0) {
+        fprintf (stderr, "nn_socket: %s\n", nn_strerror (nn_errno ()));
+        nn_close (fd);
+        return (-1);        
+    }
+
+    /*  We want all messages, so just subscribe to the empty value. */
+    if (nn_setsockopt (fd, NN_SUB, NN_SUB_SUBSCRIBE, "", 0) < 0) {
+        fprintf (stderr, "nn_setsockopt: %s\n", nn_strerror (nn_errno ()));
+        nn_close (fd);
+        return (-1);        
+    }
+
+    for (;;) {
+        uint8_t msg[2 * sizeof (uint32_t)];
+        char hhmmss[9];  /* HH:MM:SS\0 */
+        uint32_t subs, secs;
+        time_t t;
+
+        rc = nn_recv (fd, msg, sizeof (msg), 0);
+        if (rc < 0) {
+            fprintf (stderr, "nn_recv: %s\n", nn_strerror (nn_errno ()));
+            break;
+        }
+        if (rc != sizeof (msg)) {
+            fprintf (stderr, "nn_recv: got %d bytes, wanted %d\n",
+                rc, (int)sizeof (msg));
+             break;
+        }
+        memcpy (&secs, msg, sizeof (secs));
+        memcpy (&subs, msg + sizeof (secs), sizeof (subs));
+
+        t = (time_t) ntohl(secs);
+        strftime (hhmmss, sizeof (hhmmss), "%T", localtime (&t));
+
+        printf ("%s <pid %u> There are %u clients connected.\n", hhmmss,
+            (unsigned) getpid(), (unsigned) ntohl(subs));
+    }
+
+    nn_close (fd);
+    return (-1);
+}
+
+int main (int argc, char **argv)
+{
+    int rc;
+    if ((argc == 3) && (strcmp (argv[2], "-s") == 0)) {
+        rc = server (argv[1]);
+    } else if (argc == 2) {
+        rc = client (argv[1]);
+    } else {
+        fprintf (stderr, "Usage: %s <url> [-s]\n", argv[0]);
+        exit (EXIT_FAILURE);
+    }
+    exit (rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
+}
diff --git a/demo/rpc_demo.c b/demo/rpc_demo.c
new file mode 100644
index 0000000..c8d87d6
--- /dev/null
+++ b/demo/rpc_demo.c
@@ -0,0 +1,207 @@
+/*
+    Copyright 2016 Garrett D'Amore <garrett@damore.org>
+
+    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.
+
+    "nanomsg" is a trademark of Martin Sustrik
+*/
+
+/*  This program serves as an example for how to write a simple RPC service,
+    using the request/reply pattern.  The server is just a single threaded
+    for loop, which processes each request.  The requests run quickly enough
+    that there is no need for parallelism.
+
+    Our demonstration application layer protocol is simple.  The client sends
+    a name, and the server replies with a greeting based on the time of day.
+    The messages are sent in ASCII, and are not zero terminated.
+
+    To run this program, start the server as rpc_demo <url> -s
+    Then connect to it with the client as rpc_client <url> <name>.
+    The client will print a timezone appropriate greeting, based on
+    the time at the server.  For example:
+
+    % ./rpc_demo tcp://127.0.0.1:5555 -s &
+    % ./rpc_demo tcp://127.0.0.1:5555 Garrett
+    Good morning, Garrett.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <nanomsg/nn.h>
+#include <nanomsg/reqrep.h>
+
+/*  The server runs forever. */
+int server(const char *url)
+{
+    int fd; 
+
+    /*  Create the socket. */
+    fd = nn_socket (AF_SP, NN_REP);
+    if (fd < 0) {
+        fprintf (stderr, "nn_socket: %s\n", nn_strerror (nn_errno ()));
+        return (-1);
+    }
+
+    /*  Bind to the URL.  This will bind to the address and listen
+        synchronously; new clients will be accepted asynchronously
+        without further action from the calling program. */
+
+    if (nn_bind (fd, url) < 0) {
+        fprintf (stderr, "nn_bind: %s\n", nn_strerror (nn_errno ()));
+        nn_close (fd);
+        return (-1);
+    }
+
+    /*  Now we can just process results.  Note that there is no explicit
+        accept required.  We just receive a request, and reply to it.
+        Its important to note that we must not issue two receives in a
+        row without replying first, or the following receive(s) will
+        cancel any unreplied requests. */
+
+    for (;;) {
+        char username[128];
+        char greeting[128];
+        time_t secs;
+        struct tm *now;
+        char *daytime;
+        int rc;
+        char *fmt;
+
+        rc = nn_recv (fd, username, sizeof (username), 0);
+        if (rc < 0) {
+            /*  Any error here is unexpected. */
+            fprintf (stderr, "nn_recv: %s\n", nn_strerror (nn_errno ()));
+            break;
+        }
+
+        secs = time (NULL);
+        now = localtime (&secs);
+        if (now->tm_hour < 12) {
+            daytime = "morning";
+
+        } else if (now->tm_hour < 17) {
+            daytime = "afternoon";
+
+        } else if (now->tm_hour < 20) {
+            daytime = "evening";
+
+        } else {
+            daytime = "night";
+        }
+
+        /*  Ensure ASCIIZ terminated string. */
+        if (rc < sizeof (username)) {
+            username[rc] = '\0';
+        } else {
+            username[sizeof (username) - 1] = '\0';
+        }
+
+        fmt = "Good %s, %s.";
+
+        /*  Technically this might be overly pessimistic about size. */
+        if ((strlen (username) + strlen (daytime) + strlen (fmt)) >=
+            sizeof (greeting)) {
+
+            fmt = "I'm sorry, your name is too long.  But good %s anyway.";
+        }
+
+        /*  snprintf would be safer, but the above check protects us. */
+        sprintf (greeting, fmt, daytime, username);
+
+        rc = nn_send (fd, greeting, strlen (greeting), 0);
+        if (rc < 0) {
+            /*  There are several legitimate reasons this can fail.
+                We note them for debugging purposes, but then ignore
+                otherwise.  If the socket is closed or failing, we will
+                notice in recv above, and exit then. */
+            fprintf (stderr, "nn_send: %s (ignoring)\n",
+                nn_strerror (nn_errno ()));
+        }
+    }
+
+    nn_close (fd);
+    return (-1);
+}
+
+/*  The client runs just once, and then returns. */
+int client (const char *url, const char *username)
+{
+    int fd;
+    int rc;
+    char *greeting;
+    char *msg;
+
+    fd = nn_socket (AF_SP, NN_REQ);
+    if (fd < 0) {
+        fprintf (stderr, "nn_socket: %s\n", nn_strerror (nn_errno ()));
+        return (-1);
+    }
+
+    if (nn_connect (fd, url) < 0) {
+        fprintf (stderr, "nn_socket: %s\n", nn_strerror (nn_errno ()));
+        nn_close (fd);
+        return (-1);        
+    }
+
+    if (nn_send (fd, username, strlen (username), 0) < 0) {
+        fprintf (stderr, "nn_send: %s\n", nn_strerror (nn_errno ()));
+        nn_close (fd);
+        return (-1);
+    }
+
+    /*  Here we ask the library to allocate response buffer for us (NN_MSG). */
+    rc = nn_recv (fd, &msg, NN_MSG, 0);
+    if (rc < 0) {
+        fprintf (stderr, "nn_recv: %s\n", nn_strerror (nn_errno ()));
+        nn_close (fd);
+        return (-1);
+    }
+
+    nn_close (fd);
+
+    /*  Response is not ASCIIZ terminated. */
+    greeting = malloc (rc + 1);
+    if (greeting == NULL) {
+        fprintf (stderr, "malloc: %s\n", strerror (errno));
+        return (-1);
+    }
+    memcpy(greeting, msg, rc);
+
+    nn_freemsg (msg);
+    printf ("%s\n", greeting); 
+    return (0);
+}
+
+int main (int argc, char **argv)
+{
+    int rc;
+    if (argc < 3) {
+        fprintf (stderr, "Usage: %s <url> [-s|name]\n", argv[0]);
+        exit (EXIT_FAILURE);
+    }
+    if (strcmp (argv[2], "-s") == 0) {
+        rc = server (argv[1]);
+    } else {
+        rc = client (argv[1], argv[2]);
+    }
+    exit (rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
+}
diff --git a/tests/README b/tests/README
index 04c2198..e7088b1 100644
--- a/tests/README
+++ b/tests/README
@@ -1,5 +1,9 @@
 This directory contains automatic tests for nanomsg library. To run the tests
-do "make test" in the build directory.
+do "make test" in the build directory, or alternatively run ctest.
 
-The WebSocket stress test depends upon a separate installation of Autobahn
-Testsuite, available at: http://autobahn.ws/testsuite/installation.html
+Note that the code here is probably ill-suited for demonstration purposes, as
+it is primarily oriented towards testing the library, rather than serving as
+any sort of example.
+
+Instead, we recommend looking in ../demo for some example programs that
+demonstrate the API as we feel it is meant to be used.