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>
384 lines
9.6 KiB
C
384 lines
9.6 KiB
C
/*
|
|
* libwebsockets-test-server - libwebsockets test implementation
|
|
*
|
|
* Copyright (C) 2010-2016 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 "test-server.h"
|
|
#include <pthread.h>
|
|
|
|
int close_testing;
|
|
int max_poll_elements;
|
|
|
|
#ifdef EXTERNAL_POLL
|
|
struct lws_pollfd *pollfds;
|
|
int *fd_lookup;
|
|
int count_pollfds;
|
|
#endif
|
|
volatile int force_exit = 0;
|
|
struct lws_context *context;
|
|
|
|
/*
|
|
* This mutex lock protects code that changes or relies on wsi list outside of
|
|
* the service thread. The service thread will acquire it when changing the
|
|
* wsi list and other threads should acquire it while dereferencing wsis or
|
|
* calling apis like lws_callback_on_writable_all_protocol() which
|
|
* use the wsi list and wsis from a different thread context.
|
|
*/
|
|
pthread_mutex_t lock_established_conns;
|
|
|
|
/* http server gets files from this path */
|
|
#define LOCAL_RESOURCE_PATH INSTALL_DATADIR"/libwebsockets-test-server"
|
|
char *resource_path = LOCAL_RESOURCE_PATH;
|
|
|
|
/*
|
|
* multithreaded version - protect wsi lifecycle changes in the library
|
|
* these are called from protocol 0 callbacks
|
|
*/
|
|
|
|
void test_server_lock(int care)
|
|
{
|
|
if (care)
|
|
pthread_mutex_lock(&lock_established_conns);
|
|
}
|
|
void test_server_unlock(int care)
|
|
{
|
|
if (care)
|
|
pthread_mutex_unlock(&lock_established_conns);
|
|
}
|
|
|
|
/*
|
|
* This demo server shows how to use libwebsockets for one or more
|
|
* websocket protocols in the same server
|
|
*
|
|
* It defines the following websocket protocols:
|
|
*
|
|
* dumb-increment-protocol: once the socket is opened, an incrementing
|
|
* ascii string is sent down it every 50ms.
|
|
* If you send "reset\n" on the websocket, then
|
|
* the incrementing number is reset to 0.
|
|
*
|
|
* lws-mirror-protocol: copies any received packet to every connection also
|
|
* using this protocol, including the sender
|
|
*/
|
|
|
|
enum demo_protocols {
|
|
/* always first */
|
|
PROTOCOL_HTTP = 0,
|
|
|
|
PROTOCOL_DUMB_INCREMENT,
|
|
PROTOCOL_LWS_MIRROR,
|
|
|
|
/* always last */
|
|
DEMO_PROTOCOL_COUNT
|
|
};
|
|
|
|
/* list of supported protocols and callbacks */
|
|
|
|
static struct lws_protocols protocols[] = {
|
|
/* first protocol must always be HTTP handler */
|
|
|
|
{
|
|
"http-only", /* name */
|
|
callback_http, /* callback */
|
|
sizeof (struct per_session_data__http), /* per_session_data_size */
|
|
0, /* max frame size / rx buffer */
|
|
},
|
|
{
|
|
"dumb-increment-protocol",
|
|
callback_dumb_increment,
|
|
sizeof(struct per_session_data__dumb_increment),
|
|
10,
|
|
},
|
|
{
|
|
"lws-mirror-protocol",
|
|
callback_lws_mirror,
|
|
sizeof(struct per_session_data__lws_mirror),
|
|
128,
|
|
},
|
|
{ NULL, NULL, 0, 0 } /* terminator */
|
|
};
|
|
|
|
void *thread_dumb_increment(void *threadid)
|
|
{
|
|
while (!force_exit) {
|
|
/*
|
|
* this lock means wsi in the active list cannot
|
|
* disappear underneath us, because the code to add and remove
|
|
* them is protected by the same lock
|
|
*/
|
|
pthread_mutex_lock(&lock_established_conns);
|
|
lws_callback_on_writable_all_protocol(context,
|
|
&protocols[PROTOCOL_DUMB_INCREMENT]);
|
|
pthread_mutex_unlock(&lock_established_conns);
|
|
usleep(100000);
|
|
}
|
|
|
|
pthread_exit(NULL);
|
|
}
|
|
|
|
void *thread_service(void *threadid)
|
|
{
|
|
while (lws_service_tsi(context, 50, (int)(long)threadid) >= 0 && !force_exit)
|
|
;
|
|
|
|
pthread_exit(NULL);
|
|
}
|
|
|
|
void sighandler(int sig)
|
|
{
|
|
force_exit = 1;
|
|
lws_cancel_service(context);
|
|
}
|
|
|
|
static const struct lws_extension exts[] = {
|
|
{
|
|
"permessage-deflate",
|
|
lws_extension_callback_pm_deflate,
|
|
"permessage-deflate"
|
|
},
|
|
{
|
|
"deflate-frame",
|
|
lws_extension_callback_pm_deflate,
|
|
"deflate_frame"
|
|
},
|
|
{ NULL, NULL, NULL /* terminator */ }
|
|
};
|
|
|
|
static struct option options[] = {
|
|
{ "help", no_argument, NULL, 'h' },
|
|
{ "debug", required_argument, NULL, 'd' },
|
|
{ "port", required_argument, NULL, 'p' },
|
|
{ "ssl", no_argument, NULL, 's' },
|
|
{ "allow-non-ssl", no_argument, NULL, 'a' },
|
|
{ "interface", required_argument, NULL, 'i' },
|
|
{ "closetest", no_argument, NULL, 'c' },
|
|
{ "libev", no_argument, NULL, 'e' },
|
|
{ "threads", required_argument, NULL, 'j' },
|
|
#ifndef LWS_NO_DAEMONIZE
|
|
{ "daemonize", no_argument, NULL, 'D' },
|
|
#endif
|
|
{ "resource_path", required_argument, NULL, 'r' },
|
|
{ NULL, 0, 0, 0 }
|
|
};
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
struct lws_context_creation_info info;
|
|
char interface_name[128] = "";
|
|
const char *iface = NULL;
|
|
pthread_t pthread_dumb, pthread_service[32];
|
|
char cert_path[1024];
|
|
char key_path[1024];
|
|
int debug_level = 7;
|
|
int threads = 1;
|
|
int use_ssl = 0;
|
|
void *retval;
|
|
int opts = 0;
|
|
int n = 0;
|
|
#ifndef _WIN32
|
|
int syslog_options = LOG_PID | LOG_PERROR;
|
|
#endif
|
|
#ifndef LWS_NO_DAEMONIZE
|
|
int daemonize = 0;
|
|
#endif
|
|
|
|
/*
|
|
* take care to zero down the info struct, he contains random garbaage
|
|
* from the stack otherwise
|
|
*/
|
|
memset(&info, 0, sizeof info);
|
|
info.port = 7681;
|
|
|
|
pthread_mutex_init(&lock_established_conns, NULL);
|
|
|
|
while (n >= 0) {
|
|
n = getopt_long(argc, argv, "eci:hsap:d:Dr:j:", options, NULL);
|
|
if (n < 0)
|
|
continue;
|
|
switch (n) {
|
|
case 'j':
|
|
threads = atoi(optarg);
|
|
if (threads > ARRAY_SIZE(pthread_service)) {
|
|
lwsl_err("Max threads %d\n", ARRAY_SIZE(pthread_service));
|
|
return 1;
|
|
}
|
|
break;
|
|
case 'e':
|
|
opts |= LWS_SERVER_OPTION_LIBEV;
|
|
break;
|
|
#ifndef LWS_NO_DAEMONIZE
|
|
case 'D':
|
|
daemonize = 1;
|
|
#ifndef _WIN32
|
|
syslog_options &= ~LOG_PERROR;
|
|
#endif
|
|
break;
|
|
#endif
|
|
case 'd':
|
|
debug_level = atoi(optarg);
|
|
break;
|
|
case 's':
|
|
use_ssl = 1;
|
|
break;
|
|
case 'a':
|
|
opts |= LWS_SERVER_OPTION_ALLOW_NON_SSL_ON_SSL_PORT;
|
|
break;
|
|
case 'p':
|
|
info.port = atoi(optarg);
|
|
break;
|
|
case 'i':
|
|
strncpy(interface_name, optarg, sizeof interface_name);
|
|
interface_name[(sizeof interface_name) - 1] = '\0';
|
|
iface = interface_name;
|
|
break;
|
|
case 'c':
|
|
close_testing = 1;
|
|
fprintf(stderr, " Close testing mode -- closes on "
|
|
"client after 50 dumb increments"
|
|
"and suppresses lws_mirror spam\n");
|
|
break;
|
|
case 'r':
|
|
resource_path = optarg;
|
|
printf("Setting resource path to \"%s\"\n", resource_path);
|
|
break;
|
|
case 'h':
|
|
fprintf(stderr, "Usage: test-server "
|
|
"[--port=<p>] [--ssl] "
|
|
"[-d <log bitfield>] "
|
|
"[--resource_path <path>]\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
#if !defined(LWS_NO_DAEMONIZE) && !defined(WIN32)
|
|
/*
|
|
* normally lock path would be /var/lock/lwsts or similar, to
|
|
* simplify getting started without having to take care about
|
|
* permissions or running as root, set to /tmp/.lwsts-lock
|
|
*/
|
|
if (daemonize && lws_daemonize("/tmp/.lwsts-lock")) {
|
|
fprintf(stderr, "Failed to daemonize\n");
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
signal(SIGINT, sighandler);
|
|
|
|
#ifndef _WIN32
|
|
/* we will only try to log things according to our debug_level */
|
|
setlogmask(LOG_UPTO (LOG_DEBUG));
|
|
openlog("lwsts", syslog_options, LOG_DAEMON);
|
|
#endif
|
|
|
|
/* tell the library what debug level to emit and to send it to syslog */
|
|
lws_set_log_level(debug_level, lwsl_emit_syslog);
|
|
|
|
lwsl_notice("libwebsockets test server - "
|
|
"(C) Copyright 2010-2016 Andy Green <andy@warmcat.com> - "
|
|
"licensed under LGPL2.1\n");
|
|
|
|
printf("Using resource path \"%s\"\n", resource_path);
|
|
#ifdef EXTERNAL_POLL
|
|
max_poll_elements = getdtablesize();
|
|
pollfds = malloc(max_poll_elements * sizeof (struct lws_pollfd));
|
|
fd_lookup = malloc(max_poll_elements * sizeof (int));
|
|
if (pollfds == NULL || fd_lookup == NULL) {
|
|
lwsl_err("Out of memory pollfds=%d\n", max_poll_elements);
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
info.iface = iface;
|
|
info.protocols = protocols;
|
|
#ifndef LWS_NO_EXTENSIONS
|
|
info.extensions = lws_get_internal_extensions();
|
|
#endif
|
|
|
|
info.ssl_cert_filepath = NULL;
|
|
info.ssl_private_key_filepath = NULL;
|
|
|
|
if (use_ssl) {
|
|
if (strlen(resource_path) > sizeof(cert_path) - 32) {
|
|
lwsl_err("resource path too long\n");
|
|
return -1;
|
|
}
|
|
sprintf(cert_path, "%s/libwebsockets-test-server.pem",
|
|
resource_path);
|
|
if (strlen(resource_path) > sizeof(key_path) - 32) {
|
|
lwsl_err("resource path too long\n");
|
|
return -1;
|
|
}
|
|
sprintf(key_path, "%s/libwebsockets-test-server.key.pem",
|
|
resource_path);
|
|
|
|
info.ssl_cert_filepath = cert_path;
|
|
info.ssl_private_key_filepath = key_path;
|
|
}
|
|
info.gid = -1;
|
|
info.uid = -1;
|
|
info.options = opts;
|
|
info.count_threads = threads;
|
|
info.extensions = exts;
|
|
|
|
context = lws_create_context(&info);
|
|
if (context == NULL) {
|
|
lwsl_err("libwebsocket init failed\n");
|
|
return -1;
|
|
}
|
|
|
|
/* start the dumb increment thread */
|
|
|
|
n = pthread_create(&pthread_dumb, NULL, thread_dumb_increment, 0);
|
|
if (n) {
|
|
lwsl_err("Unable to create dumb thread\n");
|
|
goto done;
|
|
}
|
|
|
|
/*
|
|
* notice the actual number of threads may be capped by the library,
|
|
* so use lws_get_count_threads() to get the actual amount of threads
|
|
* initialized.
|
|
*/
|
|
|
|
for (n = 0; n < lws_get_count_threads(context); n++)
|
|
if (pthread_create(&pthread_service[n], NULL, thread_service,
|
|
(void *)(long)n))
|
|
lwsl_err("Failed to start service thread\n");
|
|
|
|
/* wait for all the service threads to exit */
|
|
|
|
for (n = 0; n < lws_get_count_threads(context); n++)
|
|
pthread_join(pthread_service[n], &retval);
|
|
|
|
/* wait for pthread_dumb to exit */
|
|
pthread_join(pthread_dumb, &retval);
|
|
|
|
done:
|
|
lws_context_destroy(context);
|
|
pthread_mutex_destroy(&lock_established_conns);
|
|
|
|
|
|
lwsl_notice("libwebsockets-test-server exited cleanly\n");
|
|
|
|
#ifndef _WIN32
|
|
closelog();
|
|
#endif
|
|
|
|
return 0;
|
|
}
|