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

WebSocket server implementation. More...

Go to the source code of this file.

Data Structures

struct  websocket_connection_data_t
 Per-connection user data. More...
 

Macros

#define R_OK   4
 

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_cancel_service (websocket_server_t *server)
 Wake the WebSocket server event loop immediately.
 
void websocket_server_destroy (websocket_server_t *server)
 Destroy WebSocket server and free resources.
 

Detailed Description

WebSocket server implementation.

Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
February 2026

Definition in file lib/network/websocket/server.c.

Macro Definition Documentation

◆ R_OK

#define R_OK   4

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

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
@ ASCIICHAT_OK
Definition error_codes.h:51
@ 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.