From 301de3ac5c7cf8a5eaaf3c58157251db781841d6 Mon Sep 17 00:00:00 2001 From: Garrett D'Amore Date: Thu, 24 May 2018 14:59:39 -0700 Subject: fixes #486 Revisit SOVERSION and VERSION fixes #485 Honor BUILD_SHARED_LIBS fixes #483 Don't expose private symbols in shared library fixes #481 Export CMake target This is a "large" commit involving changes that don't affect the code directly, but which have an impact on how we package and build our project. The most significant of these changes is that we now build only either a shared or a static library, depending on the setting of the BUILD_SHARED_LIBS option. We also suppress private symbols from being exposed when the underlying toolchain lets us do so. Minor updates to the way we version the ABI are used, and we now have a nice exported CMake project. To import this project in another, simply do find_package(nng) and you can add target_link_libraries(nng::nng) to your targets. CMake does the rest for you. --- demo/async/CMakeLists.txt | 24 +++++ demo/async/README.adoc | 9 +- demo/http_client/CMakeLists.txt | 18 ++++ demo/http_client/README.adoc | 12 ++- demo/http_client/http_client.c | 1 + demo/raw/CMakeLists.txt | 20 ++++ demo/raw/README.adoc | 35 ++++--- demo/raw/async.c | 224 ---------------------------------------- demo/raw/raw.c | 224 ++++++++++++++++++++++++++++++++++++++++ demo/reqrep/CMakeLists.txt | 17 +++ demo/reqrep/README.adoc | 15 ++- demo/rest/CMakeLists.txt | 17 +++ demo/rest/README.adoc | 24 ++++- demo/rest/server.c | 2 +- 14 files changed, 398 insertions(+), 244 deletions(-) create mode 100644 demo/async/CMakeLists.txt create mode 100644 demo/http_client/CMakeLists.txt create mode 100644 demo/raw/CMakeLists.txt delete mode 100644 demo/raw/async.c create mode 100644 demo/raw/raw.c create mode 100644 demo/reqrep/CMakeLists.txt create mode 100644 demo/rest/CMakeLists.txt (limited to 'demo') diff --git a/demo/async/CMakeLists.txt b/demo/async/CMakeLists.txt new file mode 100644 index 00000000..614bcfc3 --- /dev/null +++ b/demo/async/CMakeLists.txt @@ -0,0 +1,24 @@ +# +# Copyright 2018 Capitar IT Group BV +# Copyright 2018 Staysail Systems, Inc. +# +# This software 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). A copy of the license may also be +# found online at https://opensource.org/licenses/MIT. + +cmake_minimum_required (VERSION 2.8.7) + +project(nng-asyncdemo) + +set(PARALLEL 128 CACHE STRING "Parallelism (min 4, max 1000)") + +# Call this from your own project's makefile. +find_package(nng CONFIG REQUIRED) + +add_executable(server server.c) +target_link_libraries(server nng::nng) +target_compile_definitions(server PRIVATE -DPARALLEL=${PARALLEL}) + +add_executable(client client.c) +target_link_libraries(client nng::nng) diff --git a/demo/async/README.adoc b/demo/async/README.adoc index ec91c722..87c44cec 100644 --- a/demo/async/README.adoc +++ b/demo/async/README.adoc @@ -6,8 +6,11 @@ processing with minimal fuss. == Compiling -You can override the level of concurrency with the `PARALLEL` -define. This determines how many requests the server will accept +This is set up for configuration with CMake for ease of use. + +You can override the level of concurrency with the `PARALLEL` option. + +This determines how many requests the server will accept at a time, and keep outstanding. Note that for our toy implementation, we create this many "logical" flows of execution (contexts) (these are _NOT_ threads), where a request is followed by a reply. @@ -20,6 +23,8 @@ you can't have more than one client per descriptor. Contexts can be used on the client side to support many thousands of concurrent requests over even just a single TCP connection, however.) +You can also build this all by hand with Make or whatever. + On UNIX-style systems: [source, bash] diff --git a/demo/http_client/CMakeLists.txt b/demo/http_client/CMakeLists.txt new file mode 100644 index 00000000..3fe126f9 --- /dev/null +++ b/demo/http_client/CMakeLists.txt @@ -0,0 +1,18 @@ +# +# Copyright 2018 Capitar IT Group BV +# Copyright 2018 Staysail Systems, Inc. +# +# This software 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). A copy of the license may also be +# found online at https://opensource.org/licenses/MIT. + +cmake_minimum_required (VERSION 2.8.7) + +project(http_client) + +# Call this from your own project's makefile. +find_package(nng CONFIG REQUIRED) + +add_executable(http_client http_client.c) +target_link_libraries(http_client nng::nng) diff --git a/demo/http_client/README.adoc b/demo/http_client/README.adoc index a0fb54e4..f8777557 100644 --- a/demo/http_client/README.adoc +++ b/demo/http_client/README.adoc @@ -30,6 +30,17 @@ Linux and macOS: % ${CC} ${CPPFLAGS} http_client.c -o http_client ${LDFLAGS} ---- +Alternatively, CMake can be used. Here's an example if you have +Ninja build handy (highly recommended): + +[source, bash] +---- +% mkdir build +% cd build +% cmake -G Ninja .. +% ninja +---- + == Running Make sure you specify the full URL (if the root page include @@ -39,4 +50,3 @@ the simple "/". The URL parser does not add it for you automatically.) ---- % ./http_client http://httpbin.org/ip ---- - diff --git a/demo/http_client/http_client.c b/demo/http_client/http_client.c index 522c1cd1..a00c842f 100644 --- a/demo/http_client/http_client.c +++ b/demo/http_client/http_client.c @@ -34,6 +34,7 @@ // #include +#include #include #include diff --git a/demo/raw/CMakeLists.txt b/demo/raw/CMakeLists.txt new file mode 100644 index 00000000..15e481d5 --- /dev/null +++ b/demo/raw/CMakeLists.txt @@ -0,0 +1,20 @@ +# +# Copyright 2018 Capitar IT Group BV +# Copyright 2018 Staysail Systems, Inc. +# +# This software 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). A copy of the license may also be +# found online at https://opensource.org/licenses/MIT. + +cmake_minimum_required (VERSION 2.8.7) + +project(raw) + +set(PARALLEL 128 CACHE STRING "Parallelism (min 4, max 1000)") + +find_package(nng CONFIG REQUIRED) + +add_executable(raw raw.c) +target_link_libraries(raw nng::nng) +target_compile_definitions(raw PRIVATE -DPARALLEL=${PARALLEL}) diff --git a/demo/raw/README.adoc b/demo/raw/README.adoc index 35290616..2190dc49 100644 --- a/demo/raw/README.adoc +++ b/demo/raw/README.adoc @@ -1,14 +1,15 @@ -= async += raw This is a simple asynchronous demo, that demonstrates use of the RAW -option with a server, along with async message handling, to obtain a +sockets with a server, along with async message handling, to obtain a very high level of asynchronous operation, suitable for use in a highly concurrent server application. == Compiling -You can override the level of concurrency with the `PARALLEL` -define. This determines how many requests the server will accept +You can override the level of concurrency with the `PARALLEL` option. + +This determines how many requests the server will accept at a time, and keep outstanding. Note that for our toy implementation, we create this many "logical" flows of execution (these are _NOT_ threads), where a request is followed by a reply. @@ -16,14 +17,24 @@ implementation, we create this many "logical" flows of execution The value of `PARALLEL` must be at least one, and may be as large as your memory will permit. (The default value is 32.) -On UNIX-style systems: +The best way to build is using cmake and Ninja build: + +[source, bash] +---- +% mkdir build +% cd build +% cmake -G Ninja .. +% ninja +---- + +You can also build the hard way. For example, on UNIX-style systems: [source, bash] ---- % export CPPFLAGS="-D PARALLEL=32 -I /usr/local/include" % export LDFLAGS="-L /usr/local/lib -lnng" % export CC="cc" -% ${CC} ${CPPFLAGS} async.c -o async ${LDFLAGS} +% ${CC} ${CPPFLAGS} raw.c -o raw ${LDFLAGS} ---- == Running @@ -42,12 +53,12 @@ In the following example, all of the clients should complete within ---- % export URL="tcp://127.0.0.1:55995" # start the server -% ./async $URL -s & +% ./raw $URL -s & # start a bunch of clients # Note that these all run concurrently! -% ./async $URL 2 & -% ./async $URL 2 & -% ./async $URL 2 & -% ./async $URL 2 & -% ./async $URL 2 & +% ./raw $URL 2 & +% ./raw $URL 2 & +% ./raw $URL 2 & +% ./raw $URL 2 & +% ./raw $URL 2 & ---- diff --git a/demo/raw/async.c b/demo/raw/async.c deleted file mode 100644 index 0285d8d2..00000000 --- a/demo/raw/async.c +++ /dev/null @@ -1,224 +0,0 @@ -// Copyright 2018 Staysail Systems, Inc. -// Copyright 2018 Capitar IT Group BV -// -// This software 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). A copy of the license may also be -// found online at https://opensource.org/licenses/MIT. -// - -// 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 -s -// Then connect to it with the client as async_client . -// -// For example: -// -// % ./async tcp://127.0.0.1:5555 -s & -// % ./async tcp://127.0.0.1:5555 323 -// Request took 324 milliseconds. - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -// Parallel is the maximum number of outstanding requests we can handle. -// This is *NOT* the number of threads in use, but instead represents -// outstanding work items. Select a small number to reduce memory size. -// (Each one of these can be thought of as a request-reply loop.) -#ifndef PARALLEL -#define PARALLEL 32 -#endif - -// 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 { - enum { INIT, RECV, WAIT, SEND } state; - nng_aio * aio; - nng_socket sock; - nng_msg * msg; -}; - -void -fatal(const char *func, int rv) -{ - fprintf(stderr, "%s: %s\n", func, nng_strerror(rv)); - exit(1); -} - -void -server_cb(void *arg) -{ - struct work *work = arg; - nng_msg * msg; - int rv; - uint32_t when; - - switch (work->state) { - case INIT: - work->state = RECV; - nng_recv_aio(work->sock, work->aio); - break; - case RECV: - if ((rv = nng_aio_result(work->aio)) != 0) { - fatal("nng_recv_aio", rv); - } - msg = nng_aio_get_msg(work->aio); - if ((rv = nng_msg_trim_u32(msg, &when)) != 0) { - // bad message, just ignore it. - nng_msg_free(msg); - nng_recv_aio(work->sock, work->aio); - return; - } - work->msg = msg; - work->state = WAIT; - nng_sleep_aio(when, work->aio); - break; - case WAIT: - // We could add more data to the message here. - nng_aio_set_msg(work->aio, work->msg); - work->msg = NULL; - work->state = SEND; - nng_send_aio(work->sock, work->aio); - break; - case SEND: - if ((rv = nng_aio_result(work->aio)) != 0) { - nng_msg_free(work->msg); - fatal("nng_send_aio", rv); - } - work->state = RECV; - nng_recv_aio(work->sock, work->aio); - break; - default: - fatal("bad state!", NNG_ESTATE); - break; - } -} - -struct work * -alloc_work(nng_socket sock) -{ - struct work *w; - int rv; - - if ((w = nng_alloc(sizeof(*w))) == NULL) { - fatal("nng_alloc", NNG_ENOMEM); - } - if ((rv = nng_aio_alloc(&w->aio, server_cb, w)) != 0) { - fatal("nng_aio_alloc", rv); - } - w->state = INIT; - w->sock = sock; - return (w); -} - -// The server runs forever. -int -server(const char *url) -{ - nng_socket sock; - struct work *works[PARALLEL]; - int rv; - int i; - - /* Create the socket. */ - rv = nng_rep0_open(&sock); - if (rv != 0) { - fatal("nng_rep0_open", rv); - } - if ((rv = nng_setopt_int(sock, NNG_OPT_RAW, 1)) != 0) { - fatal("nng_setopt_int", rv); - } - - for (i = 0; i < PARALLEL; i++) { - works[i] = alloc_work(sock); - } - - if ((rv = nng_listen(sock, url, NULL, 0)) != 0) { - fatal("nng_listen", rv); - } - - for (i = 0; i < PARALLEL; i++) { - server_cb(works[i]); // this starts them going (INIT state) - } - - for (;;) { - nng_msleep(3600000); // neither pause() nor sleep() portable - } -} - -/* The client runs just once, and then returns. */ -int -client(const char *url, const char *msecstr) -{ - nng_socket sock; - int rv; - nng_msg * msg; - nng_time start; - nng_time end; - unsigned msec; - - msec = atoi(msecstr) * 1000; - - if ((rv = nng_req0_open(&sock)) != 0) { - fatal("nng_req0_open", rv); - } - - if ((rv = nng_dial(sock, url, NULL, 0)) < 0) { - fatal("nng_dial", rv); - } - - start = nng_clock(); - - if ((rv = nng_msg_alloc(&msg, 0)) != 0) { - fatal("nng_msg_alloc", rv); - } - if ((rv = nng_msg_append_u32(msg, msec)) != 0) { - fatal("nng_msg_append_u32", rv); - } - - if ((rv = nng_sendmsg(sock, msg, 0)) != 0) { - fatal("nng_send", rv); - } - - if ((rv = nng_recvmsg(sock, &msg, 0)) != 0) { - fatal("nng_recvmsg", rv); - } - end = nng_clock(); - nng_msg_free(msg); - nng_close(sock); - - 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 [-s|]\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/raw/raw.c b/demo/raw/raw.c new file mode 100644 index 00000000..0285d8d2 --- /dev/null +++ b/demo/raw/raw.c @@ -0,0 +1,224 @@ +// Copyright 2018 Staysail Systems, Inc. +// Copyright 2018 Capitar IT Group BV +// +// This software 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). A copy of the license may also be +// found online at https://opensource.org/licenses/MIT. +// + +// 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 -s +// Then connect to it with the client as async_client . +// +// For example: +// +// % ./async tcp://127.0.0.1:5555 -s & +// % ./async tcp://127.0.0.1:5555 323 +// Request took 324 milliseconds. + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +// Parallel is the maximum number of outstanding requests we can handle. +// This is *NOT* the number of threads in use, but instead represents +// outstanding work items. Select a small number to reduce memory size. +// (Each one of these can be thought of as a request-reply loop.) +#ifndef PARALLEL +#define PARALLEL 32 +#endif + +// 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 { + enum { INIT, RECV, WAIT, SEND } state; + nng_aio * aio; + nng_socket sock; + nng_msg * msg; +}; + +void +fatal(const char *func, int rv) +{ + fprintf(stderr, "%s: %s\n", func, nng_strerror(rv)); + exit(1); +} + +void +server_cb(void *arg) +{ + struct work *work = arg; + nng_msg * msg; + int rv; + uint32_t when; + + switch (work->state) { + case INIT: + work->state = RECV; + nng_recv_aio(work->sock, work->aio); + break; + case RECV: + if ((rv = nng_aio_result(work->aio)) != 0) { + fatal("nng_recv_aio", rv); + } + msg = nng_aio_get_msg(work->aio); + if ((rv = nng_msg_trim_u32(msg, &when)) != 0) { + // bad message, just ignore it. + nng_msg_free(msg); + nng_recv_aio(work->sock, work->aio); + return; + } + work->msg = msg; + work->state = WAIT; + nng_sleep_aio(when, work->aio); + break; + case WAIT: + // We could add more data to the message here. + nng_aio_set_msg(work->aio, work->msg); + work->msg = NULL; + work->state = SEND; + nng_send_aio(work->sock, work->aio); + break; + case SEND: + if ((rv = nng_aio_result(work->aio)) != 0) { + nng_msg_free(work->msg); + fatal("nng_send_aio", rv); + } + work->state = RECV; + nng_recv_aio(work->sock, work->aio); + break; + default: + fatal("bad state!", NNG_ESTATE); + break; + } +} + +struct work * +alloc_work(nng_socket sock) +{ + struct work *w; + int rv; + + if ((w = nng_alloc(sizeof(*w))) == NULL) { + fatal("nng_alloc", NNG_ENOMEM); + } + if ((rv = nng_aio_alloc(&w->aio, server_cb, w)) != 0) { + fatal("nng_aio_alloc", rv); + } + w->state = INIT; + w->sock = sock; + return (w); +} + +// The server runs forever. +int +server(const char *url) +{ + nng_socket sock; + struct work *works[PARALLEL]; + int rv; + int i; + + /* Create the socket. */ + rv = nng_rep0_open(&sock); + if (rv != 0) { + fatal("nng_rep0_open", rv); + } + if ((rv = nng_setopt_int(sock, NNG_OPT_RAW, 1)) != 0) { + fatal("nng_setopt_int", rv); + } + + for (i = 0; i < PARALLEL; i++) { + works[i] = alloc_work(sock); + } + + if ((rv = nng_listen(sock, url, NULL, 0)) != 0) { + fatal("nng_listen", rv); + } + + for (i = 0; i < PARALLEL; i++) { + server_cb(works[i]); // this starts them going (INIT state) + } + + for (;;) { + nng_msleep(3600000); // neither pause() nor sleep() portable + } +} + +/* The client runs just once, and then returns. */ +int +client(const char *url, const char *msecstr) +{ + nng_socket sock; + int rv; + nng_msg * msg; + nng_time start; + nng_time end; + unsigned msec; + + msec = atoi(msecstr) * 1000; + + if ((rv = nng_req0_open(&sock)) != 0) { + fatal("nng_req0_open", rv); + } + + if ((rv = nng_dial(sock, url, NULL, 0)) < 0) { + fatal("nng_dial", rv); + } + + start = nng_clock(); + + if ((rv = nng_msg_alloc(&msg, 0)) != 0) { + fatal("nng_msg_alloc", rv); + } + if ((rv = nng_msg_append_u32(msg, msec)) != 0) { + fatal("nng_msg_append_u32", rv); + } + + if ((rv = nng_sendmsg(sock, msg, 0)) != 0) { + fatal("nng_send", rv); + } + + if ((rv = nng_recvmsg(sock, &msg, 0)) != 0) { + fatal("nng_recvmsg", rv); + } + end = nng_clock(); + nng_msg_free(msg); + nng_close(sock); + + 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 [-s|]\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/reqrep/CMakeLists.txt b/demo/reqrep/CMakeLists.txt new file mode 100644 index 00000000..d6982446 --- /dev/null +++ b/demo/reqrep/CMakeLists.txt @@ -0,0 +1,17 @@ +# +# Copyright 2018 Capitar IT Group BV +# Copyright 2018 Staysail Systems, Inc. +# +# This software 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). A copy of the license may also be +# found online at https://opensource.org/licenses/MIT. + +cmake_minimum_required (VERSION 2.8.7) + +project(reqrep) + +find_package(nng CONFIG REQUIRED) + +add_executable(reqrep reqrep.c) +target_link_libraries(reqrep nng::nng) diff --git a/demo/reqrep/README.adoc b/demo/reqrep/README.adoc index b66d694c..5bc3d043 100644 --- a/demo/reqrep/README.adoc +++ b/demo/reqrep/README.adoc @@ -19,8 +19,19 @@ compiled on 64-bit systems.) == Compiling -The following is an example typical of UNIX and similar systems like -Linux and macOS: +CMake with ninja-build is simplest: + +[source, bash] +---- +% mkdir build +% cd build +% cmake -G Ninja .. +% ninja +---- + +Or if you prefer a traditional approach, +the following is an example typical of UNIX and similar systems like +Linux and macOS may appeal: [source, bash] ---- diff --git a/demo/rest/CMakeLists.txt b/demo/rest/CMakeLists.txt new file mode 100644 index 00000000..5466e3c7 --- /dev/null +++ b/demo/rest/CMakeLists.txt @@ -0,0 +1,17 @@ +# +# Copyright 2018 Capitar IT Group BV +# Copyright 2018 Staysail Systems, Inc. +# +# This software 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). A copy of the license may also be +# found online at https://opensource.org/licenses/MIT. + +cmake_minimum_required (VERSION 2.8.7) + +project(rest) + +find_package(nng CONFIG REQUIRED) + +add_executable(rest-server server.c) +target_link_libraries(rest-server nng::nng) diff --git a/demo/rest/README.adoc b/demo/rest/README.adoc index 6a363329..df3e30b0 100644 --- a/demo/rest/README.adoc +++ b/demo/rest/README.adoc @@ -3,7 +3,7 @@ This is a somewhat contrived demonstration, but may be useful in a pattern for solving real world problems. -There is a single "server" program, that does these: +There is a single "server" (rest-server) program, that does these: . REST API at /api/rest/rot13 - this API takes data from HTTP POST commands, and forwards them to an NNG REQ socket. When the REQ response comes, @@ -17,9 +17,29 @@ There is a single "server" program, that does these: [source, bash] ---- % env PORT=8888 # default -% ./server & +% ./rest-server & % curl -d ABC http://127.0.0.1:8888/api/rest/rot13; echo NOP % curl -d ABC http://127.0.0.1:8888/api/rest/rot13; echo ABC ---- + +== Compiling + +To build the program, we recommend CMake and Ninja-Build. + +[source, bash] +---- +% mkdir build +% cd build +% cmake -G Ninja .. +% ninja +---- + +Alternatively, you can go old-school. +Here's the simplest option for Linux: + +[source, bash] +---- +% cc server.c -o rest-server -I /usr/local/include -lnng +---- diff --git a/demo/rest/server.c b/demo/rest/server.c index 788fc8f1..72c24cb1 100644 --- a/demo/rest/server.c +++ b/demo/rest/server.c @@ -98,7 +98,7 @@ rest_free_job(rest_job *job) if (job->msg != NULL) { nng_msg_free(job->msg); } - if (job->ctx != 0) { + if (nng_ctx_id(job->ctx) != 0) { nng_ctx_close(job->ctx); } free(job); -- cgit v1.2.3-70-g09d2