[Libwebsockets] minimal-ws-client with foreign libevent loop

John Dunn John.Dunn at qsc.com
Tue Feb 9 19:35:31 CET 2021


I am trying to modify the minimal-ws-client example code to use an externally supplied libevent event loop. I am basing my changes off of the minimal-http-server-eventlib-foreign sample. This is on Windows 10 using VS 2015 if that matters. Here's what I've done

- configured libwebsocket with LWS_WITH_LIBEVENT=1 which required defining LIBEVENT_INCLUDE_DIRS
- built libwebsocket, verifying that LWS_WITH_LIBEVENT was defined in lws_config.h and linked to event.lib. I also see a libevent.c in the project file
- verified the minimal-ws-client builds and runs properly without any modifications
- defined LWS_WITH_LIBEVENT in the test client
- created an event_base* in main to run the loop
- configure the lws_context_creation_info In main with the following
    info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT | LWS_SERVER_OPTION_LIBEVENT;
    void *foreign_loops[1];
    foreign_loops[0] = loop_event;
    info.foreign_loops = foreign_loops;

- run the libevent event loop instead of the lws_service loop

Everything compiles/links/runs and I see my timer running the in the loop so I know that is functional.  The following gets printed when the application starts
[2021/02/09 11:31:30:0990] U: LWS minimal ws client
[2021/02/09 11:31:30:1080] N: LWS: 4.1.99-v4.1.0-268-g9862f515, loglevel 1295
[2021/02/09 11:31:30:1159] N: NET CLI SRV H1 H2 WS IPv6-absent
[2021/02/09 11:31:30:1239] I: Event loop: libevent
[2021/02/09 11:31:30:1310] I:  ctx:  6392B (2296 ctx + pt(1 thr x 4096)), pt-fds: 5, fdmap: 80
[2021/02/09 11:31:30:1439] I:  http: ah_data: 4096, ah: 976, max count 5
[2021/02/09 11:31:30:1519] I: elops_init_pt_event: loop 000001D287080180
[2021/02/09 11:31:30:1629] N:  ++ [wsi|0|pipe] (1)
[2021/02/09 11:31:30:1688] I: lws_plat_pipe_create: cancel UDP skt port 60759
[2021/02/09 11:31:30:1818] I:  Compiled with OpenSSL support
[2021/02/09 11:31:30:1918] I: Doing SSL library init
[2021/02/09 11:31:30:2214] I:  canonical_hostname = USBRL0002436
[2021/02/09 11:31:30:2333] N:  ++ [vh|0|default] (1)
[2021/02/09 11:31:30:2420] I: Creating Vhost 'default' (serving disabled), 1 protocols, IPv6 off
[2021/02/09 11:31:30:2679] I: lws_tls_client_create_vhost_context: vh default: created new client ctx 0
[2021/02/09 11:31:30:2894] I: created client ssl context for default
---> callback_minimal
[2021/02/09 11:31:30:3054] I:  mem: per-conn:         1080 bytes + protocol rx buf

callback_minimal gets called when the context is created with LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS. A timer is started and one second later I call lws_sul_schedule(context, 0, &mco.sul, connect_client, 1). The issue is that connect_client or callback_minimal never gets called again after this point and the client doesn't connect. It looks like the event loop should be hooked up based on the messages I see printed out - is there something else I need to do to push things through the event loop?

Here's the full source of my modified client

/*
* lws-minimal-ws-client
*
* Written in 2010-2020 by Andy Green <andy at warmcat.com>
*
* 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 <libwebsockets.h>
#include <string.h>
#include <signal.h>
#include <event2/event.h>
/*
* 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";


static struct event_base* loop_event;
static struct event* timer_event;
static struct event *sighandler_event;


/*
* 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)
{
  printf("---> connect_client\n");

  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)
{
  printf("---> callback_minimal\n");
  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 fd, short event, void *arg)
{
  int signum = (int)(lws_intptr_t)arg;
  lwsl_notice("Signal %d caught, exiting...\n", signum);
  switch (signum) {
  case SIGTERM:
  case SIGINT:
    break;
  default:
    break;
  }
  lws_context_destroy(context);
}

static int count = 0;

static void
timer_cb_event(int fd, short event, void *arg)
{
  printf("---> this is a timer %i!\n", count);
  if (count == 0)
  {
    printf("---> connecting to host!\n");

    lws_sul_schedule(context, 0, &mco.sul, connect_client, 1);
  }
  count = count + 1;
  //foreign_timer_service(loop_event);
}

int main(int argc, const char **argv)
{
  WSADATA wsa_data;
  WSAStartup(0x0201, &wsa_data);

  struct timeval tv = {.tv_sec = 1, .tv_usec = 0};
  loop_event = event_base_new();
  sighandler_event = evsignal_new(loop_event, SIGINT, sigint_handler, (void*)SIGINT);
  timer_event = event_new(loop_event, -1, EV_PERSIST, timer_cb_event, NULL);
  evtimer_add(timer_event, &tv);

  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");
  int logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE | LLL_INFO | LLL_CLIENT;
  lws_set_log_level(logs, NULL);

  info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT | LWS_SERVER_OPTION_LIBEVENT;
  info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */
  info.protocols = protocols;
  void *foreign_loops[1];
  foreign_loops[0] = loop_event;
  info.foreign_loops = foreign_loops;


#if defined(LWS_WITH_MBEDTLS) || defined(USE_WOLFSSL)
  /*
  * 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, "-n"))
    ssl_connection &= ~LCCSCF_USE_SSL;

  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);

  event_base_loop(loop_event, 0);


//  while (n >= 0 && !interrupted)
//    n = lws_service(context, 0);

  lws_context_destroy(context);
  lwsl_user("Completed\n");

  return 0;
}



More information about the Libwebsockets mailing list