diff --git a/minimal-examples/raw/README.md b/minimal-examples/raw/README.md index 18e3fb48..ccc91643 100644 --- a/minimal-examples/raw/README.md +++ b/minimal-examples/raw/README.md @@ -1,6 +1,7 @@ |name|demonstrates| ---|--- minimal-raw-adopt-tcp|Shows how to have lws adopt an existing tcp socket something else had connected +minimal-raw-adopt-udp|Shows how to create a udp socket and read and write on it minimal-raw-file|Shows how to adopt a file descriptor (device node, fifo, file, etc) into the lws event loop and handle events minimal-raw-vhost|Shows how to set up a vhost that listens and accepts RAW socket connections diff --git a/minimal-examples/raw/minimal-raw-adopt-udp/CMakeLists.txt b/minimal-examples/raw/minimal-raw-adopt-udp/CMakeLists.txt new file mode 100644 index 00000000..72627055 --- /dev/null +++ b/minimal-examples/raw/minimal-raw-adopt-udp/CMakeLists.txt @@ -0,0 +1,76 @@ +cmake_minimum_required(VERSION 2.8) +include(CheckCSourceCompiles) + +set(SAMP lws-minimal-raw-adopt-udp) +set(SRCS minimal-raw-adopt-udp.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_WITHOUT_SERVER 0 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/raw/minimal-raw-adopt-udp/README.md b/minimal-examples/raw/minimal-raw-adopt-udp/README.md new file mode 100644 index 00000000..edaf8d21 --- /dev/null +++ b/minimal-examples/raw/minimal-raw-adopt-udp/README.md @@ -0,0 +1,49 @@ +# lws minimal ws server raw adopt udp + +This example demonstrates echoing packets on a UDP socket in lws. + +A "foreign" UDP socket is created, bound (so it can "listen"), and +adopted into lws event loop. It acts like a tcp RAW mode connection in +lws and uses the same callbacks. + +Writing is a bit different for UDP. By default, the system has no +idea about the receiver state and so asking for a callback_on_writable() +always believes that the socket is writeable... the callback will +happen next time around the event loop if there are no pending partials. + +With UDP, there is no "connection". You need to write with sendto() and +direct the packets to a specific destination. You can learn the source +of the last packet that arrived at the LWS_CALLBACK_RAW_RX callback by +getting a `struct lws_udp *` from `lws_get_udp(wsi)`. To be able to +send back to that guy, you should take a copy of the `struct lws_udp *` and +use the .sa and .salen members in your sendto(). + +However the kernel may not accept to buffer / write everything you wanted to send. +So you are responsible to watch the result of sendto() and resend the +unsent part next time. + +## build + +``` + $ cmake . && make +``` + +## usage + +``` + $ ./lws-minimal-raw-adopt-udp +$ ./lws-minimal-raw-adopt-udp +[2018/03/24 08:12:37:8869] USER: LWS minimal raw adopt udp | nc -u 127.0.0.1 7681 +[2018/03/24 08:12:37:8870] NOTICE: Creating Vhost 'default' (no listener), 1 protocols, IPv6 off +[2018/03/24 08:12:37:8878] USER: LWS_CALLBACK_RAW_ADOPT +[2018/03/24 08:12:41:5656] USER: LWS_CALLBACK_RAW_RX (6) +[2018/03/24 08:12:41:5656] NOTICE: +[2018/03/24 08:12:41:5656] NOTICE: 0000: 68 65 6C 6C 6F 0A hello. +[2018/03/24 08:12:41:5656] NOTICE: +``` + +``` + $ nc -u 127.0.0.1 7681 +hello +hello +``` diff --git a/minimal-examples/raw/minimal-raw-adopt-udp/minimal-raw-adopt-udp.c b/minimal-examples/raw/minimal-raw-adopt-udp/minimal-raw-adopt-udp.c new file mode 100644 index 00000000..bef4bbb6 --- /dev/null +++ b/minimal-examples/raw/minimal-raw-adopt-udp/minimal-raw-adopt-udp.c @@ -0,0 +1,171 @@ +/* + * lws-minimal-raw-adopt-udp + * + * Copyright (C) 2018 Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + * + * This demonstrates integrating a connected udp + * socket into the lws event loop as a RAW wsi. It's interesting in + * the kind of situation where you already have a connected socket + * in your application, and you need to hand it over to lws to deal with. + * + * Lws supports "adopting" these foreign sockets, and also has a helper API + * to create, bind, and adopt them inside lws. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static uint8_t sendbuf[4096]; +static size_t sendlen; +struct lws_udp udp; + +static int +callback_raw_test(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) +{ + ssize_t n; + + switch (reason) { + + /* callbacks related to raw socket descriptor */ + + case LWS_CALLBACK_RAW_ADOPT: + lwsl_user("LWS_CALLBACK_RAW_ADOPT\n"); + break; + + case LWS_CALLBACK_RAW_CLOSE: + lwsl_user("LWS_CALLBACK_RAW_CLOSE\n"); + break; + + case LWS_CALLBACK_RAW_RX: + lwsl_user("LWS_CALLBACK_RAW_RX (%d)\n", (int)len); + lwsl_hexdump_level(LLL_NOTICE, in, len); + /* + * Take a copy of the buffer and the source socket address... + */ + udp = *(lws_get_udp(wsi)); + sendlen = len; + if (sendlen > sizeof(sendbuf)) + sendlen = sizeof(sendbuf); + memcpy(sendbuf, in, sendlen); + /* + * ... and we send it next time around the event loop. This + * can be extended to having a ringbuffer of different send + * buffers and targets queued. + * + * Note that UDP is ALWAYS writable as far as poll() knows + * because there is no mechanism like the tcp window to + * understand that packets are not being acknowledged. But + * this allows the event loop to share out the work. + */ + lws_callback_on_writable(wsi); + break; + + case LWS_CALLBACK_RAW_WRITEABLE: + + if (!sendlen) + break; + /* + * We can write directly on the UDP socket, specifying + * the peer the write is directed to. + * + * However the kernel may only accept parts of large sendto()s, + * leaving you to try to resend the remainder later. However + * depending on how your protocol on top of UDP works, that + * may involve sticking new headers before the remainder. + * + * For clarity partial sends just drop the remainder here. + */ + n = sendto(lws_get_socket_fd(wsi), sendbuf, sendlen, 0, &udp.sa, + udp.salen); + if (n < (ssize_t)len) + lwsl_notice("%s: send returned %d\n", __func__, (int)n); + break; + + default: + break; + } + + return 0; +} + +static struct lws_protocols protocols[] = { + { "raw-test", callback_raw_test, 0, 0 }, + { NULL, NULL, 0, 0 } /* terminator */ +}; + +static int interrupted; + +void sigint_handler(int sig) +{ + interrupted = 1; +} + +int main(int argc, char **argv) +{ + struct lws_context_creation_info info; + struct lws_context *context; + struct lws_vhost *vhost; + int n = 0; + + lws_set_log_level(LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE + /* for LLL_ verbosity above NOTICE to be built into lws, + * lws must have been configured and built with + * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ + /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ + /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ + /* | LLL_DEBUG */, NULL); + + memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS; + + lwsl_user("LWS minimal raw adopt udp | nc -u 127.0.0.1 7681\n"); + + signal(SIGINT, sigint_handler); + + context = lws_create_context(&info); + if (!context) { + lwsl_err("lws init failed\n"); + return 1; + } + + info.port = CONTEXT_PORT_NO_LISTEN_SERVER; + info.protocols = protocols; + + vhost = lws_create_vhost(context, &info); + if (!vhost) { + lwsl_err("lws vhost creation failed\n"); + goto bail; + } + + /* + * Create our own "foreign" UDP socket bound to 7681/udp + */ + if (!lws_create_adopt_udp(vhost, 7681, LWS_CAUDP_BIND, + protocols[0].name, NULL)) { + lwsl_err("%s: foreign socket adoption failed\n", __func__); + goto bail; + } + + while (n >= 0 && !interrupted) + n = lws_service(context, 1000); + +bail: + lws_context_destroy(context); + + return 0; +}