ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
server.h File Reference

WebSocket server for accepting browser client connections. More...

Go to the source code of this file.

Data Structures

struct  websocket_client_context_t
 Context passed to client handler thread. More...
 
struct  websocket_server_config_t
 WebSocket server configuration. More...
 
struct  websocket_server
 WebSocket server state. More...
 

Typedefs

typedef struct websocket_server websocket_server_t
 
typedef struct acip_transport acip_transport_t
 
typedef struct thread_pool thread_pool_t
 
typedef void *(* websocket_client_handler_fn) (void *arg)
 Client handler function type.
 

Functions

asciichat_error_t websocket_server_init (websocket_server_t *server, const websocket_server_config_t *config)
 Initialize WebSocket server.
 
asciichat_error_t websocket_server_run (websocket_server_t *server)
 Run WebSocket server event loop.
 
void websocket_server_destroy (websocket_server_t *server)
 Destroy WebSocket server and free resources.
 
void websocket_server_cancel_service (websocket_server_t *server)
 Wake the WebSocket server event loop immediately.
 

Detailed Description

WebSocket server for accepting browser client connections.

Provides a WebSocket server implementation using libwebsockets to accept connections from browser-based WASM clients alongside the TCP server.

This module mirrors the tcp_server API to provide consistent server-side connection handling regardless of transport type.

Usage Pattern

  1. Configure server with websocket_server_config_t
  2. Call websocket_server_init() to create libwebsockets context
  3. Call websocket_server_run() to start event loop (blocks)
  4. Signal shutdown by setting running flag to false
  5. Call websocket_server_destroy() to clean up

Example

// Define client handler (same as TCP)
void *my_client_handler(void *arg) {
// Process client connection via transport
SAFE_FREE(ctx);
return NULL;
}
// Configure and run server
.port = 27224,
.client_handler = my_client_handler,
.user_data = NULL
};
if (websocket_server_init(&server, &config) == ASCIICHAT_OK) {
}
#define SAFE_FREE(ptr)
Definition common.h:376
@ ASCIICHAT_OK
Definition error_codes.h:51
asciichat_error_t websocket_server_init(websocket_server_t *server, const websocket_server_config_t *config)
Initialize WebSocket server.
asciichat_error_t websocket_server_run(websocket_server_t *server)
Run WebSocket server event loop.
void websocket_server_destroy(websocket_server_t *server)
Destroy WebSocket server and free resources.
Context passed to client handler thread.
acip_transport_t * transport
ACIP transport for this client.
void acip_transport_destroy(acip_transport_t *transport)
Destroy transport and free all resources.
Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
February 2026

Definition in file include/ascii-chat/network/websocket/server.h.

Typedef Documentation

◆ acip_transport_t

◆ thread_pool_t

typedef struct thread_pool thread_pool_t

◆ websocket_client_handler_fn

typedef void *(* websocket_client_handler_fn) (void *arg)

Client handler function type.

Called when a new WebSocket client connects. The handler receives a fully initialized transport ready for ACIP packets.

Parameters
argwebsocket_client_context_t* containing connection info
Returns
Thread exit value (ignored)

Definition at line 74 of file include/ascii-chat/network/websocket/server.h.

◆ websocket_server_t

Function Documentation

◆ websocket_server_cancel_service()

void websocket_server_cancel_service ( websocket_server_t *  server)

Wake the WebSocket server event loop immediately.

Thread-safe. Wakes lws_service() from its timeout wait, allowing the event loop to check the running flag without waiting for the full service timeout (50ms).

Parameters
serverServer instance (must have a valid context)

Definition at line 979 of file lib/network/websocket/server.c.

979 {
980 if (server && server->context) {
981 lws_cancel_service(server->context);
982 }
983}
struct lws_context * context
libwebsockets context

References websocket_server::context.

Referenced by session_server_like_run().

◆ websocket_server_destroy()

void websocket_server_destroy ( websocket_server_t *  server)

Destroy WebSocket server and free resources.

Parameters
serverServer instance to destroy

Definition at line 985 of file lib/network/websocket/server.c.

985 {
986 if (!server) {
987 return;
988 }
989
990 atomic_store_bool(&server->running, false);
991
992 // Cancel any pending libwebsockets service calls to interrupt blocking lws_service()
993 // This prevents the event loop from getting stuck in lws_service()
994 if (server->context) {
995 log_debug("[WEBSOCKET_SERVER_DESTROY] Cancelling libwebsockets service");
996 lws_cancel_service(server->context);
997 }
998
999 // Unregister atomic fields
1000 NAMED_UNREGISTER(&server->running);
1001
1002 // Destroy handler thread pool (waits for pending work to complete)
1003 if (server->handler_pool) {
1005 server->handler_pool = NULL;
1006 }
1007
1008 // Context is normally destroyed by websocket_server_run (from the event loop
1009 // thread) for fast shutdown. This handles the case where run() wasn't called
1010 // or didn't complete normally.
1011 if (server->context) {
1012 NAMED_UNREGISTER(server->context);
1013 log_debug("WebSocket context still alive in destroy, cleaning up");
1014 lws_context_destroy(server->context);
1015 server->context = NULL;
1016 }
1017
1018 log_debug("WebSocket server destroyed");
1019}
void atomic_store_bool(atomic_t *a, bool value)
Atomically store a boolean value.
Definition atomic.c:177
#define NAMED_UNREGISTER(ptr)
Unregister a pointer.
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
struct thread_pool * handler_pool
Thread pool for queueing handler work.
void thread_pool_destroy(thread_pool_t *pool)
Destroy a thread pool.

References atomic_store_bool(), websocket_server::context, websocket_server::handler_pool, log_debug, NAMED_UNREGISTER, websocket_server::running, and thread_pool_destroy().

Referenced by session_server_like_run().

◆ websocket_server_init()

asciichat_error_t websocket_server_init ( websocket_server_t *  server,
const websocket_server_config_t *  config 
)

Initialize WebSocket server.

Creates libwebsockets context and prepares for accepting connections.

Parameters
serverServer instance to initialize
configServer configuration
Returns
ASCIICHAT_OK on success, error code on failure

Definition at line 817 of file lib/network/websocket/server.c.

817 {
818 if (!server || !config || !config->client_handler) {
819 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters");
820 }
821
822 memset(server, 0, sizeof(*server));
823 server->handler = config->client_handler;
824 server->user_data = config->user_data;
825 server->port = config->port;
826 atomic_store_bool(&server->running, true);
827
828 // Store server pointer in protocol user data so callbacks can access it
829 websocket_protocols[0].user = server; // http protocol
830 websocket_protocols[1].user = server; // acip protocol
831
832 // Enable libwebsockets logging through centralized logging system
834
835 // HTTP mount for /health endpoint (required for LWS_CALLBACK_HTTP to fire)
836 static const struct lws_http_mount health_mount = {
837 .mount_next = NULL,
838 .mountpoint = "/",
839 .mountpoint_len = 1,
840 .origin = "http",
841 .def = NULL,
842 .protocol = "http",
843 .origin_protocol = LWSMPRO_CALLBACK,
844 };
845
846 // Configure libwebsockets context
847 struct lws_context_creation_info info = {0};
848 info.port = config->port;
849 info.protocols = websocket_protocols;
850 info.mounts = &health_mount;
851 info.gid = (gid_t)-1; // Cast to avoid undefined behavior with unsigned type
852 info.uid = (uid_t)-1; // Cast to avoid undefined behavior with unsigned type
853 info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; // Initialize SSL/TLS support (required for server binding)
854 info.extensions = NULL; // Disable permessage-deflate - causing connection issues
855 info.retry_and_idle_policy = &keep_alive_policy; // Configure keep-alive to prevent idle disconnects during handshake
856
857 // Configure TLS/WSS support if certificates are provided (check for non-empty strings)
858 if (config->tls_cert_path && config->tls_cert_path[0] != '\0' && config->tls_key_path &&
859 config->tls_key_path[0] != '\0') {
860#ifdef LWS_WITH_TLS
861 // Validate certificate and key files are readable
862 if (platform_access(config->tls_cert_path, R_OK) != 0) {
863 return SET_ERRNO(ERROR_NETWORK_BIND, "TLS certificate file not readable: %s (errno=%d)", config->tls_cert_path,
864 errno);
865 }
866 if (platform_access(config->tls_key_path, R_OK) != 0) {
867 return SET_ERRNO(ERROR_NETWORK_BIND, "TLS key file not readable: %s (errno=%d)", config->tls_key_path, errno);
868 }
869
870 info.ssl_cert_filepath = config->tls_cert_path;
871 info.ssl_private_key_filepath = config->tls_key_path;
872 log_info("WebSocket server configured for WSS (TLS): cert=%s, key=%s", config->tls_cert_path, config->tls_key_path);
873#else
874 log_warn("WebSocket server: TLS support not compiled in libwebsockets; WSS unavailable");
875#endif
876 } else if ((config->tls_cert_path && config->tls_cert_path[0] != '\0') ||
877 (config->tls_key_path && config->tls_key_path[0] != '\0')) {
878 log_warn("WebSocket server: Both TLS certificate and key must be provided for WSS; using plain WS");
879 }
880
881 // Increase per-thread service buffer to prevent fragmentation of large messages
882 // Default is 4KB, causing large frames to fragment into many chunks
883 // Video frames are 921KB, so increase to 2MB to handle them without fragmentation
884 info.pt_serv_buf_size = 2 * 1024 * 1024; // 2MB per-thread service buffer
885
886 // Disable ALL default timeouts and keep-alive mechanisms
887 // Use only the explicit retry_and_idle_policy we set above (30/35 seconds)
888 info.ka_time = 0; // Disable TCP keep-alive probes (use WebSocket pings instead)
889 info.ka_probes = 0; // Disable TCP probes
890 info.ka_interval = 0; // Disable TCP probe intervals
891 info.keepalive_timeout = 0; // Disable HTTP keep-alive timeout entirely
892
893 // Explicitly set a very large idle timeout to prevent early disconnection
894 // Without this, libwebsockets defaults to 5 seconds for HTTP connections
895 // Set to effectively infinite (100 hours) since we use retry_and_idle_policy instead
896 info.timeout_secs = 360000; // 100 hours - effectively disabled
897
898 // Create libwebsockets context
899 // Log diagnostic info before attempting creation
900 log_debug("lws_create_context: port=%u, protocols=%p, options=0x%x", info.port, (void *)info.protocols, info.options);
901
902 server->context = lws_create_context(&info);
903 if (!server->context) {
904 // Capture any OpenSSL errors that may have occurred during context creation
905 unsigned long ssl_err = 0;
906 char ssl_err_str[256] = "no OpenSSL errors in queue";
907
908 // Extract all errors from OpenSSL error queue
909 while ((ssl_err = ERR_get_error()) != 0) {
910 ERR_error_string_n(ssl_err, ssl_err_str, sizeof(ssl_err_str));
911 log_error("OpenSSL error: %lu: %s", ssl_err, ssl_err_str);
912 }
913
914 return SET_ERRNO(ERROR_NETWORK_BIND, "Failed to create libwebsockets context (last OpenSSL error: %s)",
915 ssl_err_str);
916 }
917
918 // Create thread pool for handling client connections
919 // Use 4 worker threads to handle concurrent client handlers without blocking LWS event loop
920 server->handler_pool = thread_pool_create_with_workers("websocket_handlers", 4);
921 if (!server->handler_pool) {
922 lws_context_destroy(server->context);
923 server->context = NULL;
924 return SET_ERRNO(ERROR_THREAD, "Failed to create WebSocket handler thread pool");
925 }
926
927 /* Register WebSocket server with named registry */
928 char ws_port_name[32];
929 snprintf(ws_port_name, sizeof(ws_port_name), "ws:%d", server->port);
930 NAMED_REGISTER(server, ws_port_name, "websocket_server", "0x%tx", NULL);
931
932 /* Register server's sync primitives with hierarchical naming */
933 NAMED_REGISTER_ATOMIC(&server->running, "is_running", (uintptr_t)(const void *)(server));
934
935 /* Register WebSocket context as child of server */
936 NAMED_REGISTER_WEBSOCKET(server->context, "context", (uintptr_t)(const void *)(server));
937
938 log_info("WebSocket server initialized on port %d with static file serving", config->port);
939 return ASCIICHAT_OK;
940}
#define NAMED_REGISTER_ATOMIC(a, name, parent_ptr)
Register an atomic_t with automatic format specifier.
#define NAMED_REGISTER(ptr, name, type, fmt, parent_ptr)
Register any pointer with base name, type, format spec, and location (auto-suffix)
#define NAMED_REGISTER_WEBSOCKET(websocket, name, parent_ptr)
Register a WebSocket connection with automatic format specifier.
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
@ ERROR_NETWORK_BIND
Definition error_codes.h:78
@ ERROR_INVALID_PARAM
@ ERROR_THREAD
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
#define log_info(...)
Log an INFO message.
Definition log/log.h:561
int platform_access(const char *pathname, int mode)
Check file/directory access permissions.
int errno
websocket_client_handler_fn client_handler
Handler for new connections.
void * user_data
User data passed to handlers.
const char * tls_cert_path
TLS certificate file path (optional, for wss://)
const char * tls_key_path
TLS private key file path (optional, for wss://)
websocket_client_handler_fn handler
Client handler function.
thread_pool_t * thread_pool_create_with_workers(const char *pool_name, size_t num_workers)
Create a new thread pool in work queue mode.
void lws_log_init_server(void)
Initialize libwebsockets logging for server mode.
Definition websocket.c:87

References ASCIICHAT_OK, atomic_store_bool(), websocket_server_config_t::client_handler, websocket_server::context, errno, ERROR_INVALID_PARAM, ERROR_NETWORK_BIND, ERROR_THREAD, websocket_server::handler, websocket_server::handler_pool, log_debug, log_error, log_info, log_warn, lws_log_init_server(), NAMED_REGISTER, NAMED_REGISTER_ATOMIC, NAMED_REGISTER_WEBSOCKET, platform_access(), websocket_server_config_t::port, websocket_server::port, R_OK, websocket_server::running, SET_ERRNO, thread_pool_create_with_workers(), websocket_server_config_t::tls_cert_path, websocket_server_config_t::tls_key_path, websocket_server_config_t::user_data, and websocket_server::user_data.

Referenced by session_server_like_run().

◆ websocket_server_run()

asciichat_error_t websocket_server_run ( websocket_server_t *  server)

Run WebSocket server event loop.

Blocks until server is signaled to stop via running flag.

Parameters
serverServer instance
Returns
ASCIICHAT_OK on success, error code on failure

Definition at line 942 of file lib/network/websocket/server.c.

942 {
943 if (!server || !server->context) {
944 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid server");
945 }
946
947 log_info("WebSocket server starting event loop on port %d", server->port);
948
949 // Run libwebsockets event loop
950 uint64_t last_service_ns = 0;
951 int service_call_count = 0;
952 while (atomic_load_bool(&server->running)) {
953 // Service libwebsockets with 16ms timeout to match 60 FPS frame rate.
954 // This provides frequent event processing (~60 callback invocations per second) so that
955 // WebSocket frames queued by the render loop are sent promptly, matching TCP performance.
956 // All client connections share this single server context, so a single lws_service() call
957 // services fragments and events for all clients simultaneously.
958 uint64_t service_start_ns = time_get_ns();
959 if (last_service_ns && service_start_ns - last_service_ns > 30 * US_PER_MS_INT) {
960 // > 30ms gap between service calls
961 double gap_ms = (double)(service_start_ns - last_service_ns) / 1e6;
962 log_info_every(1 * US_PER_MS_INT, "[LWS_SERVICE_GAP] %.1fms gap between lws_service calls", gap_ms);
963 }
964 service_call_count++;
965 log_debug_every(500 * US_PER_MS_INT, "[LWS_SERVICE] Call #%d, context=%s", service_call_count,
966 NAMED_DESCRIBE(server->context, "websocket_server"));
967 int result = lws_service(server->context, 1);
968 if (result < 0) {
969 log_error("libwebsockets service error: %d", result);
970 break;
971 }
972 }
973
974 log_info(
975 "WebSocket server event loop exited (context will be destroyed by main thread after handler threads complete)");
976 return ASCIICHAT_OK;
977}
bool atomic_load_bool(atomic_t *a)
Atomically load a boolean value.
Definition atomic.c:169
unsigned long long uint64_t
Definition common.h:59
#define NAMED_DESCRIBE(ptr, hint)
Describe a pointer in log format.
uint64_t time_get_ns(void)
Get current monotonic time in nanoseconds.
Definition util/time.c:108
#define US_PER_MS_INT
Definition time.h:160
#define log_info_every(interval_us, fmt,...)
Rate-limited INFO logging.
Definition log/log.h:705
#define log_debug_every(interval_us, fmt,...)
Rate-limited DEBUG logging.
Definition log/log.h:702

References ASCIICHAT_OK, atomic_load_bool(), websocket_server::context, ERROR_INVALID_PARAM, log_debug_every, log_error, log_info, log_info_every, NAMED_DESCRIBE, websocket_server::port, websocket_server::running, SET_ERRNO, time_get_ns(), and US_PER_MS_INT.