diff --git a/minimal-examples/ws-client/README.md b/minimal-examples/ws-client/README.md index 10db2fad5..9d2ce078c 100644 --- a/minimal-examples/ws-client/README.md +++ b/minimal-examples/ws-client/README.md @@ -1,5 +1,6 @@ |name|demonstrates| ---|--- +minimal-ws-client|Simple client that connects to libwebsockets.org dumb increment protocol and demonstrates retry and backoff minimal-ws-client-echo|Simple client that connects to a ws server and echos anything the server sends minimal-ws-client-ping|Ws ping test client minimal-ws-client-pmd-bulk|Client that sends bulk multifragment data to the minimal-ws-server-pmd-bulk example diff --git a/minimal-examples/ws-client/minimal-ws-client/CMakeLists.txt b/minimal-examples/ws-client/minimal-ws-client/CMakeLists.txt new file mode 100644 index 000000000..b3a4e9829 --- /dev/null +++ b/minimal-examples/ws-client/minimal-ws-client/CMakeLists.txt @@ -0,0 +1,80 @@ +project(lws-minimal-ws-client-ping) +cmake_minimum_required(VERSION 2.8) +include(CheckIncludeFile) +include(CheckCSourceCompiles) + +set(SAMP lws-minimal-ws-client) +set(SRCS minimal-ws-client.c) + +# If we are being built as part of lws, confirm current build config supports +# reqconfig, else skip building ourselves. +# +# If we are being built externally, confirm installed lws was configured to +# support reqconfig, else error out with a helpful message about the problem. +# +MACRO(require_lws_config reqconfig _val result) + + if (DEFINED ${reqconfig}) + if (${reqconfig}) + set (rq 1) + else() + set (rq 0) + endif() + else() + set(rq 0) + endif() + + if (${_val} EQUAL ${rq}) + set(SAME 1) + else() + set(SAME 0) + endif() + + if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME}) + if (${_val}) + message("${SAMP}: skipping as lws being built without ${reqconfig}") + else() + message("${SAMP}: skipping as lws built with ${reqconfig}") + endif() + set(${result} 0) + else() + if (LWS_WITH_MINIMAL_EXAMPLES) + set(MET ${SAME}) + else() + CHECK_C_SOURCE_COMPILES("#include \nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig}) + if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig}) + set(HAS_${reqconfig} 0) + else() + set(HAS_${reqconfig} 1) + endif() + if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val})) + set(MET 1) + else() + set(MET 0) + endif() + endif() + if (NOT MET) + if (${_val}) + message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}") + else() + message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project") + endif() + endif() + + endif() +ENDMACRO() + +set(requirements 1) +require_lws_config(LWS_ROLE_WS 1 requirements) +require_lws_config(LWS_WITH_CLIENT 1 requirements) + +if (requirements) + add_executable(${SAMP} ${SRCS}) + + if (websockets_shared) + target_link_libraries(${SAMP} websockets_shared) + add_dependencies(${SAMP} websockets_shared) + else() + target_link_libraries(${SAMP} websockets) + endif() +endif() diff --git a/minimal-examples/ws-client/minimal-ws-client/README.md b/minimal-examples/ws-client/minimal-ws-client/README.md new file mode 100644 index 000000000..bf87ab36a --- /dev/null +++ b/minimal-examples/ws-client/minimal-ws-client/README.md @@ -0,0 +1,58 @@ +# lws minimal ws client + +This connects to libwebsockets.org using the dumb-increment-protocol. + +It demonstrates how to use the connection retry and backoff stuff in lws. + +## build + +``` + $ cmake . && make +``` + +## Commandline Options + +Option|Meaning +---|--- +-d|Set logging verbosity +-s|Use a specific server instead of libwebsockets.org, eg `--server localhost`. Implies LCCSCF_ALLOW_SELFSIGNED +-p|Use a specific port instead of 443, eg `--port 7681` +-j|Allow selfsigned tls cert +-k|Allow insecure certs +-m|Skip server hostname check +-e|Allow expired certs +--protocol|Use a specific ws subprotocol rather than dumb-increment-protocol, eg, `--protocol myprotocol` + + +## usage + +Just run it, it will connect to libwebsockets.org and spew incrementing numbers +sent by the server at 20Hz + +``` + $ ./lws-minimal-ws-client +[2020/01/22 05:38:47:3409] U: LWS minimal ws client +[2020/01/22 05:38:47:4456] N: Loading client CA for verification ./libwebsockets.org.cer +[2020/01/22 05:38:48:1649] U: callback_minimal: established +[2020/01/22 05:38:48:1739] N: +[2020/01/22 05:38:48:1763] N: 0000: 30 0 +[2020/01/22 05:38:48:1765] N: + +... +``` + +To test against the lws test server instead of libwebsockets.org, run the test +server as + +``` +$ libwebsockets-test-server -s +``` + +and run this test app with + +``` +$ ./lws-minimal-ws-client -s localhost -p 7681 -j +``` + +You can kill and restart the server to confirm the client connection is re- +established if done within the backoff period. diff --git a/minimal-examples/ws-client/minimal-ws-client/libwebsockets.org.cer b/minimal-examples/ws-client/minimal-ws-client/libwebsockets.org.cer new file mode 100644 index 000000000..4a9fb35cb --- /dev/null +++ b/minimal-examples/ws-client/minimal-ws-client/libwebsockets.org.cer @@ -0,0 +1,58 @@ +-----BEGIN CERTIFICATE----- +MIIFWjCCBEKgAwIBAgISA9x0/oj5PLdW46hsmR82/7ytMA0GCSqGSIb3DQEBCwUA +MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD +ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0xOTA5MDcwNzA5NDBaFw0x +OTEyMDYwNzA5NDBaMBwxGjAYBgNVBAMTEWxpYndlYnNvY2tldHMub3JnMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxPinIkleLmvEcA/YuBss6ASXVi7g +yr6Sss7cB3vTy7Fp8OB2c1N25prHZxVpORAUo0UreiaY2Ws4NFvDaYp08ZffevuC +UhThsEJlbkD0uvt7dPapJt9PNJtlxjNFWyvHEy6PijzIaMYDROiStcCJQn7kAew/ +Za2+5kNVgKqT+7OXukJEFdSdVZI6QC/npeQlkIrFSq1WVthCGBNJehxxES0hSWzk +0gNVKlkD3/SbkupsfUpe73XiawMtrtsSE7cdnul7VZmiP8I/3sJr1+4/3xZ+DEYg +mVB82B0vd08VJYzU7Nf0pz0PWusAmzRoRn81IXkOfBg9ohlSSEoZhHYS7QIDAQAB +o4ICZjCCAmIwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggr +BgEFBQcDAjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBRmKKyGjufWgp7pR2x0tWxG +D9G+WTAfBgNVHSMEGDAWgBSoSmpjBH3duubRObemRWXv86jsoTBvBggrBgEFBQcB +AQRjMGEwLgYIKwYBBQUHMAGGImh0dHA6Ly9vY3NwLmludC14My5sZXRzZW5jcnlw +dC5vcmcwLwYIKwYBBQUHMAKGI2h0dHA6Ly9jZXJ0LmludC14My5sZXRzZW5jcnlw +dC5vcmcvMBwGA1UdEQQVMBOCEWxpYndlYnNvY2tldHMub3JnMEwGA1UdIARFMEMw +CAYGZ4EMAQIBMDcGCysGAQQBgt8TAQEBMCgwJgYIKwYBBQUHAgEWGmh0dHA6Ly9j +cHMubGV0c2VuY3J5cHQub3JnMIIBBAYKKwYBBAHWeQIEAgSB9QSB8gDwAHcAdH7a +gzGtMxCRIZzOJU9CcMK//V5CIAjGNzV55hB7zFYAAAFtCsWIfgAABAMASDBGAiEA +0H55VqSKV3otHK7uHNbcR0QwoUYtCmeObhsqxzCnmDwCIQD3mtuSKrxTD3oA+Yde +nmTgWfFyS4TNgLNEPCJYo2s75gB1ACk8UZZUyDlluqpQ/FgH1Ldvv1h6KXLcpMMM +9OVFR/R4AAABbQrFil4AAAQDAEYwRAIgNSpvz/1JA2aP6fh6ujGNuYfrAvWjlxXo +CJtVGe4XaDYCIGmK1/9tl1uQbVD46P5NswnULq06KQmuOrlI3HO4r86HMA0GCSqG +SIb3DQEBCwUAA4IBAQBiAlV7wkCsWE99VmZHBmcbZChWyWUHG3LM1hnaQRQjTSYk +CIlauCpWzlUd6weuvra85KqBbCYo+1hxbwITI796uAdgtHmBE8nj0VltHwKeSq2s +KKiGXBRT7Z7t0VHYSLOlGOVn1auuQFaWBArc0cQ/m1ZsoHvOiHTlKQvVsA4HnIxA +CjGY9OOQoh0c36ecbJZ44XKnU9J/OXtDx00aW6QodaZmgMp/OOCghFQUvufkgTUL +LZid873/8dJVWjAaj1VdadO1nSbdAfBbeWXy93+vg1aAoig80RoscrzYCaNlwmR7 +EO5zWxL3l+xUZogQSJuICgUgNzVB3wjn8HeHGsqt +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/ +MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT +DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow +SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT +GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF +q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8 +SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0 +Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA +a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj +/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T +AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG +CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv +bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k +c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw +VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC +ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz +MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu +Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF +AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo +uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/ +wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu +X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG +PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6 +KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg== +-----END CERTIFICATE----- diff --git a/minimal-examples/ws-client/minimal-ws-client/minimal-ws-client.c b/minimal-examples/ws-client/minimal-ws-client/minimal-ws-client.c new file mode 100644 index 000000000..35c3d3c27 --- /dev/null +++ b/minimal-examples/ws-client/minimal-ws-client/minimal-ws-client.c @@ -0,0 +1,214 @@ +/* + * lws-minimal-ws-client + * + * Written in 2010-2020 by Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + * + * This demonstrates a ws client that connects by default to libwebsockets.org + * dumb increment ws server. + */ + +#include +#include +#include +#include + +/* + * This represents your object that "contains" the client connection and has + * the client connection bound to it + */ + +static struct my_conn { + lws_sorted_usec_list_t sul; /* schedule connection retry */ + struct lws *wsi; /* related wsi if any */ + uint16_t retry_count; /* count of consequetive retries */ +} mco; + +static struct lws_context *context; +static int interrupted, port = 443, ssl_connection = LCCSCF_USE_SSL; +static const char *server_address = "libwebsockets.org", + *pro = "dumb-increment-protocol"; + +/* + * The retry and backoff policy we want to use for our client connections + */ + +static const uint32_t backoff_ms[] = { 1000, 2000, 3000, 4000, 5000 }; + +static const lws_retry_bo_t retry = { + .retry_ms_table = backoff_ms, + .retry_ms_table_count = LWS_ARRAY_SIZE(backoff_ms), + .conceal_count = LWS_ARRAY_SIZE(backoff_ms), + + .secs_since_valid_ping = 3, /* force PINGs after secs idle */ + .secs_since_valid_hangup = 10, /* hangup after secs idle */ + + .jitter_percent = 20, +}; + +/* + * Scheduled sul callback that starts the connection attempt + */ + +static void +connect_client(lws_sorted_usec_list_t *sul) +{ + struct my_conn *mco = lws_container_of(sul, struct my_conn, sul); + struct lws_client_connect_info i; + + memset(&i, 0, sizeof(i)); + + i.context = context; + i.port = port; + i.address = server_address; + i.path = "/"; + i.host = i.address; + i.origin = i.address; + i.ssl_connection = ssl_connection; + i.protocol = pro; + i.local_protocol_name = "lws-minimal-client"; + i.pwsi = &mco->wsi; + i.retry_and_idle_policy = &retry; + i.userdata = mco; + + if (!lws_client_connect_via_info(&i)) + /* + * Failed... schedule a retry... we can't use the _retry_wsi() + * convenience wrapper api here because no valid wsi at this + * point. + */ + if (lws_retry_sul_schedule(context, 0, sul, &retry, + connect_client, &mco->retry_count)) { + lwsl_err("%s: connection attempts exhausted\n", __func__); + interrupted = 1; + } +} + +static int +callback_minimal(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) +{ + struct my_conn *mco = (struct my_conn *)user; + + switch (reason) { + + case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: + lwsl_err("CLIENT_CONNECTION_ERROR: %s\n", + in ? (char *)in : "(null)"); + goto do_retry; + break; + + case LWS_CALLBACK_CLIENT_RECEIVE: + lwsl_hexdump_notice(in, len); + break; + + case LWS_CALLBACK_CLIENT_ESTABLISHED: + lwsl_user("%s: established\n", __func__); + break; + + case LWS_CALLBACK_CLIENT_CLOSED: + goto do_retry; + + default: + break; + } + + return lws_callback_http_dummy(wsi, reason, user, in, len); + +do_retry: + /* + * retry the connection to keep it nailed up + * + * For this example, we try to conceal any problem for one set of + * backoff retries and then exit the app. + * + * If you set retry.conceal_count to be larger than the number of + * elements in the backoff table, it will never give up and keep + * retrying at the last backoff delay plus the random jitter amount. + */ + if (lws_retry_sul_schedule_retry_wsi(wsi, &mco->sul, connect_client, + &mco->retry_count)) { + lwsl_err("%s: connection attempts exhausted\n", __func__); + interrupted = 1; + } + + return 0; +} + +static const struct lws_protocols protocols[] = { + { "lws-minimal-client", callback_minimal, 0, 0, }, + { NULL, NULL, 0, 0 } +}; + +static void +sigint_handler(int sig) +{ + interrupted = 1; +} + +int main(int argc, const char **argv) +{ + struct lws_context_creation_info info; + const char *p; + int n = 0; + + signal(SIGINT, sigint_handler); + memset(&info, 0, sizeof info); + lws_cmdline_option_handle_builtin(argc, argv, &info); + + lwsl_user("LWS minimal ws client\n"); + + info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; + info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */ + info.protocols = protocols; + +#if defined(LWS_WITH_MBEDTLS) + /* + * OpenSSL uses the system trust store. mbedTLS has to be told which + * CA to trust explicitly. + */ + info.client_ssl_ca_filepath = "./libwebsockets.org.cer"; +#endif + + if ((p = lws_cmdline_option(argc, argv, "--protocol"))) + pro = p; + + if ((p = lws_cmdline_option(argc, argv, "-s"))) + server_address = p; + + if ((p = lws_cmdline_option(argc, argv, "-p"))) + port = atoi(p); + + if (lws_cmdline_option(argc, argv, "-j")) + ssl_connection |= LCCSCF_ALLOW_SELFSIGNED; + + if (lws_cmdline_option(argc, argv, "-k")) + ssl_connection |= LCCSCF_ALLOW_INSECURE; + + if (lws_cmdline_option(argc, argv, "-m")) + ssl_connection |= LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK; + + if (lws_cmdline_option(argc, argv, "-e")) + ssl_connection |= LCCSCF_ALLOW_EXPIRED; + + info.fd_limit_per_thread = 1 + 1 + 1; + + context = lws_create_context(&info); + if (!context) { + lwsl_err("lws init failed\n"); + return 1; + } + + /* schedule the first client connection attempt to happen immediately */ + lws_sul_schedule(context, 0, &mco.sul, connect_client, 1); + + while (n >= 0 && !interrupted) + n = lws_service(context, 0); + + lws_context_destroy(context); + lwsl_user("Completed\n"); + + return 0; +}