mirror of
https://github.com/warmcat/libwebsockets.git
synced 2025-03-16 00:00:07 +01:00

This adds support for multithreaded service to lws without adding any threading or locking code in the library. At context creation time you can request split the service part of the context into n service domains, which are load-balanced so that the most idle one gets the next listen socket accept. There's a single listen socket on one port still. User code may then spawn n threads doing n service loops / poll()s simultaneously. Locking is only required (I think) in the existing FD lock callbacks already handled by the pthreads server example, and that locking takes place in user code. So the library remains completely agnostic about the threading / locking scheme. And by default, it's completely compatible with one service thread so no changes are required by people uninterested in multithreaded service. However for people interested in extremely lightweight mass http[s]/ ws[s] service with minimum provisioning, the library can now do everything out of the box. To test it, just try $ libwebsockets-test-server-pthreads -j 8 where -j controls the number of service threads Signed-off-by: Andy Green <andy.green@linaro.org>
401 lines
12 KiB
C
401 lines
12 KiB
C
/*
|
|
* libwebsockets - small server side websockets and web server implementation
|
|
*
|
|
* Copyright (C) 2010-2015 Andy Green <andy@warmcat.com>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation:
|
|
* version 2.1 of the License.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
* MA 02110-1301 USA
|
|
*/
|
|
|
|
#include "private-libwebsockets.h"
|
|
|
|
#ifndef LWS_BUILD_HASH
|
|
#define LWS_BUILD_HASH "unknown-build-hash"
|
|
#endif
|
|
|
|
static const char *library_version = LWS_LIBRARY_VERSION " " LWS_BUILD_HASH;
|
|
|
|
/**
|
|
* lws_get_library_version: get version and git hash library built from
|
|
*
|
|
* returns a const char * to a string like "1.1 178d78c"
|
|
* representing the library version followed by the git head hash it
|
|
* was built from
|
|
*/
|
|
|
|
LWS_VISIBLE const char *
|
|
lws_get_library_version(void)
|
|
{
|
|
return library_version;
|
|
}
|
|
|
|
/**
|
|
* lws_create_context() - Create the websocket handler
|
|
* @info: pointer to struct with parameters
|
|
*
|
|
* This function creates the listening socket (if serving) and takes care
|
|
* of all initialization in one step.
|
|
*
|
|
* After initialization, it returns a struct lws_context * that
|
|
* represents this server. After calling, user code needs to take care
|
|
* of calling lws_service() with the context pointer to get the
|
|
* server's sockets serviced. This must be done in the same process
|
|
* context as the initialization call.
|
|
*
|
|
* The protocol callback functions are called for a handful of events
|
|
* including http requests coming in, websocket connections becoming
|
|
* established, and data arriving; it's also called periodically to allow
|
|
* async transmission.
|
|
*
|
|
* HTTP requests are sent always to the FIRST protocol in @protocol, since
|
|
* at that time websocket protocol has not been negotiated. Other
|
|
* protocols after the first one never see any HTTP callack activity.
|
|
*
|
|
* The server created is a simple http server by default; part of the
|
|
* websocket standard is upgrading this http connection to a websocket one.
|
|
*
|
|
* This allows the same server to provide files like scripts and favicon /
|
|
* images or whatever over http and dynamic data over websockets all in
|
|
* one place; they're all handled in the user callback.
|
|
*/
|
|
|
|
LWS_VISIBLE struct lws_context *
|
|
lws_create_context(struct lws_context_creation_info *info)
|
|
{
|
|
struct lws_context *context = NULL;
|
|
struct lws wsi;
|
|
#ifndef LWS_NO_DAEMONIZE
|
|
int pid_daemon = get_daemonize_pid();
|
|
#endif
|
|
char *p;
|
|
int n;
|
|
|
|
lwsl_notice("Initial logging level %d\n", log_level);
|
|
|
|
lwsl_notice("Libwebsockets version: %s\n", library_version);
|
|
#if LWS_POSIX
|
|
#ifdef LWS_USE_IPV6
|
|
if (!(info->options & LWS_SERVER_OPTION_DISABLE_IPV6))
|
|
lwsl_notice("IPV6 compiled in and enabled\n");
|
|
else
|
|
lwsl_notice("IPV6 compiled in but disabled\n");
|
|
#else
|
|
lwsl_notice("IPV6 not compiled in\n");
|
|
#endif
|
|
lws_feature_status_libev(info);
|
|
#endif
|
|
lwsl_info(" LWS_MAX_HEADER_LEN : %u\n", LWS_MAX_HEADER_LEN);
|
|
lwsl_info(" LWS_MAX_PROTOCOLS : %u\n", LWS_MAX_PROTOCOLS);
|
|
lwsl_info(" LWS_MAX_SMP : %u\n", LWS_MAX_SMP);
|
|
lwsl_info(" SPEC_LATEST_SUPPORTED : %u\n", SPEC_LATEST_SUPPORTED);
|
|
lwsl_info(" AWAITING_TIMEOUT : %u\n", AWAITING_TIMEOUT);
|
|
lwsl_info(" sizeof (*info) : %u\n", sizeof(*info));
|
|
#if LWS_POSIX
|
|
lwsl_info(" SYSTEM_RANDOM_FILEPATH: '%s'\n", SYSTEM_RANDOM_FILEPATH);
|
|
#endif
|
|
if (lws_plat_context_early_init())
|
|
return NULL;
|
|
|
|
context = lws_zalloc(sizeof(struct lws_context));
|
|
if (!context) {
|
|
lwsl_err("No memory for websocket context\n");
|
|
return NULL;
|
|
}
|
|
#ifndef LWS_NO_DAEMONIZE
|
|
if (pid_daemon) {
|
|
context->started_with_parent = pid_daemon;
|
|
lwsl_notice(" Started with daemon pid %d\n", pid_daemon);
|
|
}
|
|
#endif
|
|
context->max_fds = getdtablesize();
|
|
|
|
if (info->count_threads)
|
|
context->count_threads = info->count_threads;
|
|
else
|
|
context->count_threads = 1;
|
|
|
|
if (context->count_threads > LWS_MAX_SMP)
|
|
context->count_threads = LWS_MAX_SMP;
|
|
|
|
context->lserv_seen = 0;
|
|
context->protocols = info->protocols;
|
|
context->token_limits = info->token_limits;
|
|
context->listen_port = info->port;
|
|
context->http_proxy_port = 0;
|
|
context->http_proxy_address[0] = '\0';
|
|
context->options = info->options;
|
|
context->iface = info->iface;
|
|
context->ka_time = info->ka_time;
|
|
context->ka_interval = info->ka_interval;
|
|
context->ka_probes = info->ka_probes;
|
|
|
|
/* we zalloc only the used ones, so the memory is not wasted
|
|
* allocating for unused threads
|
|
*/
|
|
for (n = 0; n < context->count_threads; n++) {
|
|
context->pt[n].serv_buf = lws_zalloc(LWS_MAX_SOCKET_IO_BUF);
|
|
if (!context->pt[n].serv_buf) {
|
|
lwsl_err("OOM\n");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (info->fd_limit_per_thread)
|
|
context->fd_limit_per_thread = info->fd_limit_per_thread;
|
|
else
|
|
context->fd_limit_per_thread = context->max_fds /
|
|
context->count_threads;
|
|
|
|
lwsl_notice(" Threads: %d each %d fds\n", context->count_threads,
|
|
context->fd_limit_per_thread);
|
|
|
|
memset(&wsi, 0, sizeof(wsi));
|
|
wsi.context = context;
|
|
|
|
if (!info->ka_interval && info->ka_time > 0) {
|
|
lwsl_err("info->ka_interval can't be 0 if ka_time used\n");
|
|
return NULL;
|
|
}
|
|
|
|
#ifdef LWS_USE_LIBEV
|
|
/* (Issue #264) In order to *avoid breaking backwards compatibility*, we
|
|
* enable libev mediated SIGINT handling with a default handler of
|
|
* lws_sigint_cb. The handler can be overridden or disabled
|
|
* by invoking lws_sigint_cfg after creating the context, but
|
|
* before invoking lws_initloop:
|
|
*/
|
|
context->use_ev_sigint = 1;
|
|
context->lws_ev_sigint_cb = &lws_sigint_cb;
|
|
#endif /* LWS_USE_LIBEV */
|
|
|
|
lwsl_info(" mem: context: %5u bytes (%d + (%d x %d))\n",
|
|
sizeof(struct lws_context) +
|
|
(context->count_threads * LWS_MAX_SOCKET_IO_BUF),
|
|
sizeof(struct lws_context),
|
|
context->count_threads,
|
|
LWS_MAX_SOCKET_IO_BUF);
|
|
|
|
/*
|
|
* allocate and initialize the pool of
|
|
* allocated_header structs + data
|
|
*/
|
|
if (info->max_http_header_data)
|
|
context->max_http_header_data = info->max_http_header_data;
|
|
else
|
|
context->max_http_header_data = LWS_MAX_HEADER_LEN;
|
|
if (info->max_http_header_pool)
|
|
context->max_http_header_pool = info->max_http_header_pool;
|
|
else
|
|
context->max_http_header_pool = LWS_MAX_HEADER_POOL;
|
|
|
|
context->http_header_data = lws_malloc(context->max_http_header_data *
|
|
context->max_http_header_pool);
|
|
if (!context->http_header_data)
|
|
goto bail;
|
|
context->ah_pool = lws_zalloc(sizeof(struct allocated_headers) *
|
|
context->max_http_header_pool);
|
|
if (!context->ah_pool)
|
|
goto bail;
|
|
|
|
for (n = 0; n < context->max_http_header_pool; n++)
|
|
context->ah_pool[n].data = (char *)context->http_header_data +
|
|
(n * context->max_http_header_data);
|
|
|
|
/* this is per context */
|
|
lwsl_info(" mem: http hdr rsvd: %5u bytes ((%u + %u) x %u)\n",
|
|
(context->max_http_header_data +
|
|
sizeof(struct allocated_headers)) *
|
|
context->max_http_header_pool,
|
|
context->max_http_header_data,
|
|
sizeof(struct allocated_headers),
|
|
context->max_http_header_pool);
|
|
n = sizeof(struct lws_pollfd) * context->count_threads *
|
|
context->fd_limit_per_thread;
|
|
context->pt[0].fds = lws_zalloc(n);
|
|
if (context->pt[0].fds == NULL) {
|
|
lwsl_err("OOM allocating %d fds\n", context->max_fds);
|
|
goto bail;
|
|
}
|
|
lwsl_info(" mem: pollfd map: %5u\n", n);
|
|
/* each thread serves his own chunk of fds */
|
|
for (n = 1; n < (int)info->count_threads; n++)
|
|
context->pt[n].fds = context->pt[0].fds +
|
|
(n * context->fd_limit_per_thread);
|
|
|
|
if (lws_plat_init(context, info))
|
|
goto bail;
|
|
|
|
lws_context_init_extensions(info, context);
|
|
|
|
context->user_space = info->user;
|
|
|
|
lwsl_notice(" mem: per-conn: %5u bytes + protocol rx buf\n",
|
|
sizeof(struct lws));
|
|
|
|
strcpy(context->canonical_hostname, "unknown");
|
|
lws_server_get_canonical_hostname(context, info);
|
|
|
|
/* either use proxy from info, or try get it from env var */
|
|
|
|
if (info->http_proxy_address) {
|
|
/* override for backwards compatibility */
|
|
if (info->http_proxy_port)
|
|
context->http_proxy_port = info->http_proxy_port;
|
|
lws_set_proxy(context, info->http_proxy_address);
|
|
} else {
|
|
#ifdef LWS_HAVE_GETENV
|
|
p = getenv("http_proxy");
|
|
if (p)
|
|
lws_set_proxy(context, p);
|
|
#endif
|
|
}
|
|
|
|
if (lws_context_init_server_ssl(info, context))
|
|
goto bail;
|
|
|
|
if (lws_context_init_client_ssl(info, context))
|
|
goto bail;
|
|
|
|
if (lws_context_init_server(info, context))
|
|
goto bail;
|
|
|
|
/*
|
|
* drop any root privs for this process
|
|
* to listen on port < 1023 we would have needed root, but now we are
|
|
* listening, we don't want the power for anything else
|
|
*/
|
|
lws_plat_drop_app_privileges(info);
|
|
|
|
/* initialize supported protocols */
|
|
|
|
for (context->count_protocols = 0;
|
|
info->protocols[context->count_protocols].callback;
|
|
context->count_protocols++)
|
|
/*
|
|
* inform all the protocols that they are doing their one-time
|
|
* initialization if they want to.
|
|
*
|
|
* NOTE the wsi is all zeros except for the context pointer
|
|
* so lws_get_context(wsi) can work in the callback.
|
|
*/
|
|
info->protocols[context->count_protocols].callback(&wsi,
|
|
LWS_CALLBACK_PROTOCOL_INIT, NULL, NULL, 0);
|
|
|
|
/*
|
|
* give all extensions a chance to create any per-context
|
|
* allocations they need
|
|
*/
|
|
if (info->port != CONTEXT_PORT_NO_LISTEN) {
|
|
if (lws_ext_cb_all_exts(context, NULL,
|
|
LWS_EXT_CB_SERVER_CONTEXT_CONSTRUCT, NULL, 0) < 0)
|
|
goto bail;
|
|
} else
|
|
if (lws_ext_cb_all_exts(context, NULL,
|
|
LWS_EXT_CB_CLIENT_CONTEXT_CONSTRUCT, NULL, 0) < 0)
|
|
goto bail;
|
|
|
|
return context;
|
|
|
|
bail:
|
|
lws_context_destroy(context);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* lws_context_destroy() - Destroy the websocket context
|
|
* @context: Websocket context
|
|
*
|
|
* This function closes any active connections and then frees the
|
|
* context. After calling this, any further use of the context is
|
|
* undefined.
|
|
*/
|
|
LWS_VISIBLE void
|
|
lws_context_destroy(struct lws_context *context)
|
|
{
|
|
const struct lws_protocols *protocol = NULL;
|
|
struct lws wsi;
|
|
int n, m = context->count_threads;
|
|
|
|
lwsl_notice("%s\n", __func__);
|
|
|
|
if (!context)
|
|
return;
|
|
|
|
context->being_destroyed = 1;
|
|
|
|
memset(&wsi, 0, sizeof(wsi));
|
|
wsi.context = context;
|
|
|
|
#ifdef LWS_LATENCY
|
|
if (context->worst_latency_info[0])
|
|
lwsl_notice("Worst latency: %s\n", context->worst_latency_info);
|
|
#endif
|
|
|
|
while (m--)
|
|
for (n = 0; n < context->pt[m].fds_count; n++) {
|
|
struct lws *wsi = wsi_from_fd(context, context->pt[m].fds[n].fd);
|
|
if (!wsi)
|
|
continue;
|
|
lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY
|
|
/* no protocol close */);
|
|
n--;
|
|
}
|
|
|
|
/*
|
|
* give all extensions a chance to clean up any per-context
|
|
* allocations they might have made
|
|
*/
|
|
|
|
n = lws_ext_cb_all_exts(context, NULL,
|
|
LWS_EXT_CB_SERVER_CONTEXT_DESTRUCT, NULL, 0);
|
|
|
|
n = lws_ext_cb_all_exts(context, NULL,
|
|
LWS_EXT_CB_CLIENT_CONTEXT_DESTRUCT, NULL, 0);
|
|
|
|
/*
|
|
* inform all the protocols that they are done and will have no more
|
|
* callbacks
|
|
*/
|
|
protocol = context->protocols;
|
|
if (protocol) {
|
|
while (protocol->callback) {
|
|
protocol->callback(&wsi,
|
|
LWS_CALLBACK_PROTOCOL_DESTROY,
|
|
NULL, NULL, 0);
|
|
protocol++;
|
|
}
|
|
}
|
|
#ifdef LWS_USE_LIBEV
|
|
ev_io_stop(context->io_loop, &context->w_accept.watcher);
|
|
if(context->use_ev_sigint)
|
|
ev_signal_stop(context->io_loop, &context->w_sigint.watcher);
|
|
#endif /* LWS_USE_LIBEV */
|
|
|
|
for (n = 0; n < context->count_threads; n++)
|
|
lws_free_set_NULL(context->pt[n].serv_buf);
|
|
|
|
lws_plat_context_early_destroy(context);
|
|
lws_ssl_context_destroy(context);
|
|
if (context->pt[0].fds)
|
|
lws_free(context->pt[0].fds);
|
|
if (context->ah_pool)
|
|
lws_free(context->ah_pool);
|
|
if (context->http_header_data)
|
|
lws_free(context->http_header_data);
|
|
|
|
lws_plat_context_late_destroy(context);
|
|
|
|
lws_free(context);
|
|
}
|