ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
Client Management

👥 Per-client lifecycle management and threading coordination More...

Files

file  client.c
 ðŸ‘¥ Per-client lifecycle manager: threading coordination, state management, and client lifecycle orchestration
 
file  client.h
 Per-client state management and lifecycle orchestration.
 

Data Structures

struct  client_manager_t
 Global client manager structure for server-side client coordination. More...
 

Detailed Description

👥 Per-client lifecycle management and threading coordination

Client Management

Overview

The client management module orchestrates the complete lifecycle of each connected client, from initial connection through thread spawning, state synchronization, and graceful disconnection cleanup. It serves as the integration hub between the main server loop and all per-client subsystems, managing the sophisticated per-client threading architecture that enables real-time video chat at 60fps per client.

Implementation: src/server/client.c, src/server/client.h

Key Responsibilities:

  • Client connection establishment and initialization
  • Per-client thread creation and management (4 threads per client)
  • Client state management with thread-safe access patterns
  • Hash table management for O(1) client ID lookups
  • Client disconnection handling and resource cleanup
  • Integration point between main.c and other server modules

Client Lifecycle

Connection Establishment

When a new client connects:

int add_client(socket_t socket, const char *client_ip, int client_port)
{
// 1. Find available slot in client array
int slot = find_available_client_slot();
// 2. Generate unique client ID (based on local port)
uint32_t client_id = generate_client_id(client_port);
// 3. Initialize client data structures
atomic_store(&client->client_id, client_id);
atomic_store(&client->active, true);
client->socket = socket;
// 4. Add to uthash table for O(1) lookups
HASH_ADD_INT(g_client_manager.clients_by_id, client_id, client);
// 5. Perform cryptographic handshake
crypto_handshake_result_t handshake_result =
perform_crypto_handshake_server(socket, &client->crypto_ctx);
if (handshake_result != HANDSHAKE_SUCCESS) {
// Handshake failed - cleanup and return error
remove_client(client_id);
return -1;
}
// 6. Create per-client threads (receive, send, render)
create_client_receive_thread(client);
create_client_send_thread(client);
create_client_render_threads(client); // Video + Audio render threads
return client_id;
}
unsigned int uint32_t
Definition common.h:58
int mutex_init(mutex_t *mutex)
Initialize a mutex.
#define rwlock_wrunlock(lock)
Release a write lock (with debug tracking in debug builds)
Definition rwlock.h:249
int socket_t
Socket handle type (POSIX: int)
Definition socket.h:50
#define rwlock_wrlock(lock)
Acquire a write lock (with debug tracking in debug builds)
Definition rwlock.h:213
int create_client_render_threads(server_context_t *server_ctx, client_info_t *client)
Create and initialize per-client rendering threads.
Definition render.c:1121
rwlock_t g_client_manager_rwlock
Reader-writer lock protecting the global client manager.
client_manager_t g_client_manager
Global client manager singleton - central coordination point.
int add_client(server_context_t *server_ctx, socket_t socket, const char *client_ip, int port)
int remove_client(server_context_t *server_ctx, uint32_t client_id)
Per-client state structure for server-side client management.
atomic_uint client_id
mutex_t client_state_mutex
atomic_bool active
client_info_t * clients_by_id
uthash head pointer for O(1) client_id -> client_info_t* lookups
client_info_t clients[MAX_CLIENTS]
Array of client_info_t structures (backing storage)

Thread Creation Order:

  1. Send Thread: Created first (cleanest exit, no blocking I/O typically)
  2. Receive Thread: Created second (handles incoming packets, may block on network)
  3. Video Render Thread: Created third (60fps frame generation)
  4. Audio Render Thread: Created fourth (172fps audio mixing)

Disconnection Handling

Client disconnection is detected and handled gracefully:

int remove_client(uint32_t client_id)
{
// 1. Find client in uthash table
client_info_t *client = NULL;
HASH_FIND_INT(g_client_manager.clients_by_id, &client_id, client);
// 2. Mark client as inactive (causes threads to exit)
atomic_store(&client->active, false);
atomic_store(&client->video_render_thread_running, false);
atomic_store(&client->audio_render_thread_running, false);
// 3. Join threads in reverse creation order
// 4. Cleanup resources
crypto_handshake_cleanup(&client->crypto_ctx);
packet_queue_destroy(client->video_packet_queue);
packet_queue_destroy(client->audio_packet_queue);
// 5. Remove from uthash table
HASH_DELETE(hh, g_client_manager.clients_by_id, client);
// 6. Clear client slot
memset(client, 0, sizeof(client_info_t));
return 0;
}
void crypto_handshake_cleanup(crypto_handshake_context_t *ctx)
Cleanup crypto handshake context with secure memory wiping.
void packet_queue_destroy(packet_queue_t *queue)
Destroy a packet queue and free all resources.
int asciichat_thread_join(asciichat_thread_t *thread, void **retval)
Wait for a thread to complete (blocking)
void image_destroy(image_t *p)
Destroy an image allocated with image_new()
Definition video/image.c:85
atomic_bool video_render_thread_running
asciichat_thread_t audio_render_thread
asciichat_thread_t send_thread
video_frame_buffer_t * incoming_video_buffer
atomic_bool audio_render_thread_running
asciichat_thread_t receive_thread
asciichat_thread_t video_render_thread
int client_count
Current number of active clients.

Cleanup Order:

  1. Mark inactive (threads exit loops)
  2. Join send thread (quickest, usually not blocked)
  3. Join receive thread (may be blocked on recv(), will timeout)
  4. Join render threads (computational work, clean exit)
  5. Cleanup resources (crypto, queues, buffers)
  6. Remove from hash table
  7. Clear slot for reuse

Per-Client Threading Architecture

Each connected client spawns exactly 4 dedicated threads:

Receive Thread

Purpose: Handle incoming packets from client Module: protocol.c functions called by receive thread Operations:

  • Receives encrypted packets from socket
  • Validates packet headers and CRC32 checksums
  • Decrypts packets using per-client crypto context
  • Dispatches to protocol handler functions based on packet type
  • Stores media data (video frames, audio samples) in client buffers
  • Updates client state (capabilities, dimensions, stream status)

Thread Safety:

  • Uses per-client mutex for state updates
  • Snapshot pattern for minimal lock holding time
  • Thread-safe media buffers (double-buffer system)

Send Thread

Purpose: Manage outgoing packet delivery to client Module: client.c (send thread implementation) Operations:

  • Reads packets from client's video and audio packet queues
  • Encrypts packets using per-client crypto context
  • Sends encrypted packets to client socket
  • Handles chunked transmission for large packets
  • Detects send failures and marks connection lost

Thread Safety:

  • Packet queues are internally thread-safe
  • Socket access protected by connection state checks
  • Connection loss detected atomically

Video Render Thread

Purpose: Generate personalized ASCII frames at 60fps Module: render.c (video render thread implementation) Operations:

  • Collects video frames from all active clients (via stream.c)
  • Creates composite frame with appropriate grid layout
  • Converts composite to ASCII using client's terminal capabilities
  • Queues ASCII frame in client's video packet queue
  • Rate limits to exactly 60fps with precise timing

Performance:

  • 16.67ms intervals (60fps)
  • Per-client frame generation (each client gets personalized view)
  • Terminal capability awareness (color depth, palette, UTF-8 support)
  • Linear scaling (no shared bottlenecks)

Audio Render Thread

Purpose: Mix audio streams at 172fps (excluding client's own audio) Module: render.c (audio render thread implementation) Operations:

  • Reads audio samples from all clients via audio mixer
  • Excludes client's own audio to prevent echo
  • Mixes remaining audio streams with ducking and compression
  • Queues mixed audio in client's audio packet queue
  • Rate limits to exactly 172fps (5.8ms intervals)

Performance:

  • 5.8ms intervals (172fps)
  • Low-latency audio delivery
  • Professional audio processing (ducking, compression, noise gate)
  • Linear scaling (per-client dedicated threads)

Data Structures

Client Manager

Global singleton managing all clients:

typedef struct {
client_info_t clients[MAX_CLIENTS]; // Backing storage array
client_info_t *clients_by_id; // uthash head pointer for O(1) client_id lookup
int client_count; // Current number of active clients
mutex_t mutex; // Protects client_count updates
extern rwlock_t g_client_manager_rwlock; // Protects entire manager
#define MAX_CLIENTS
Maximum possible clients (static array size) - actual runtime limit set by –max-clients (1-32)
Definition limits.h:23
pthread_mutex_t mutex_t
Mutex type (POSIX: pthread_mutex_t)
Definition mutex.h:38
pthread_rwlock_t rwlock_t
Read-write lock type (POSIX: pthread_rwlock_t)
Definition rwlock.h:40
Global client manager structure for server-side client coordination.

Synchronization:

  • Reader-writer lock allows concurrent reads
  • Exclusive write access for client add/remove
  • Hash table provides O(1) client lookups
  • Array provides O(1) slot access by index

Client Information

Per-client state structure:

typedef struct {
// Connection state
socket_t socket;
_Atomic uint32_t client_id;
_Atomic bool active;
// Threading
asciichat_thread_t receive_thread;
asciichat_thread_t send_thread;
asciichat_thread_t video_render_thread;
asciichat_thread_t audio_render_thread;
_Atomic bool video_render_thread_running;
_Atomic bool audio_render_thread_running;
// Cryptography
crypto_handshake_ctx_t crypto_ctx;
_Atomic bool crypto_initialized;
// Media buffers
video_frame_t *incoming_video_buffer;
ringbuffer_t *incoming_audio_buffer;
// Packet queues
packet_queue_t *video_packet_queue;
packet_queue_t *audio_packet_queue;
// Terminal capabilities
unsigned short width;
unsigned short height;
// Synchronization
mutex_t client_state_mutex;
mutex_t video_buffer_mutex;
pthread_t asciichat_thread_t
Thread handle type (POSIX: pthread_t)
struct client_info client_info_t
Per-client state structure for server-side client management.
Thread-safe packet queue for producer-consumer communication.
Lock-free ring buffer structure.
Definition ringbuffer.h:95
Complete terminal capabilities structure.
Definition terminal.h:485
Video frame structure.

State Protection:

  • client_state_mutex: Protects most client state fields
  • video_buffer_mutex: Protects video frame buffer
  • Atomic variables: Thread control flags (no mutex needed)
  • Atomic snapshot pattern: Copy state under mutex, process without locks

Synchronization Patterns

Lock Ordering Protocol

CRITICAL RULE: Always acquire locks in this order:

  1. g_client_manager_rwlock (global reader-writer lock)
  2. client->client_state_mutex (per-client mutex)
  3. Specialized mutexes (video_buffer_mutex, etc.)

This ordering prevents deadlocks across all server code.

Example:

// CORRECT: Global lock first, then per-client mutex
client_info_t *client = NULL;
HASH_FIND_INT(g_client_manager.clients_by_id, &client_id, client);
// ... access client state ...
#define mutex_lock(mutex)
Lock a mutex (with debug tracking in debug builds)
Definition mutex.h:140
#define rwlock_rdlock(lock)
Acquire a read lock (with debug tracking in debug builds)
Definition rwlock.h:194
#define mutex_unlock(mutex)
Unlock a mutex (with debug tracking in debug builds)
Definition mutex.h:175
#define rwlock_rdunlock(lock)
Release a read lock (with debug tracking in debug builds)
Definition rwlock.h:231

Snapshot Pattern

All client state access uses the snapshot pattern to minimize lock contention:

// 1. Acquire mutex
// 2. Copy needed state to local variables
bool should_continue = client->video_render_thread_running && client->active;
uint32_t client_id_snapshot = client->client_id;
unsigned short width_snapshot = client->width;
unsigned short height_snapshot = client->height;
// 3. Release mutex immediately
// 4. Process using local copies (no locks held)
if (should_continue) {
generate_frame(client_id_snapshot, width_snapshot, height_snapshot);
}
atomic_ushort height
atomic_ushort width

Benefits:

  • Minimal lock holding time (reduces contention)
  • No blocking operations while holding locks
  • Prevents deadlocks from complex call chains
  • Enables parallel processing across clients

Hash Table Integration

The client manager uses a hash table for O(1) client ID lookups:

// Add client to uthash table
HASH_ADD_INT(g_client_manager.clients_by_id, client_id, client);
// Lookup client by ID
client_info_t *client = NULL;
HASH_FIND_INT(g_client_manager.clients_by_id, &client_id, client);
// Remove client from uthash table
HASH_DELETE(hh, g_client_manager.clients_by_id, client);

Performance:

  • O(1) average-case lookup time
  • Handles hash collisions internally
  • Thread-safe operations (protected by RWLock)
  • Scales to 9+ clients efficiently

Client ID Generation:

  • Based on client's local port number
  • Ensures uniqueness across connections
  • Provides stable identifier for session

Integration with Other Modules

Integration with main.c

Called By:

Provides To:

  • Global client manager (g_client_manager) for thread access
  • Client lookup functions for other modules
  • Thread lifecycle coordination

Integration with protocol.c

Called By:

Provides To:

  • Client state access for protocol handlers
  • Media buffer storage (video frames, audio samples)
  • State update functions

Integration with render.c

Called By:

Provides To:

  • Client state snapshots for render threads
  • Media buffers for frame generation
  • Terminal capabilities for ASCII conversion

Integration with stream.c

Called By:

  • Render threads call stream generation functions
  • generate_personalized_ascii_frame(): Creates client-specific frames

Provides To:

  • Client capabilities for frame generation
  • Video frame access for mixing
  • Frame queue for delivery

Error Handling

Connection Errors:

  • Handshake failures: Client rejected, socket closed
  • Thread creation failures: Client rejected, resources cleaned up
  • Resource allocation failures: Graceful degradation, client rejected

Runtime Errors:

  • Receive thread errors: Connection marked inactive, cleanup triggered
  • Send thread errors: Connection marked inactive, cleanup triggered
  • Render thread errors: Thread exits, connection continues (graceful degradation)

Cleanup Errors:

  • Thread join timeouts: Logged but don't block shutdown
  • Resource cleanup failures: Logged but don't crash server
  • Hash table errors: Handled gracefully with fallback

Performance Characteristics

Linear Scaling:

  • Each client gets dedicated CPU resources
  • No shared bottlenecks between clients
  • Performance scales linearly up to 9+ clients
  • Real-time guarantees maintained per client

Memory Usage:

  • Per-client allocations: ~50KB per client (buffers, queues)
  • Hash table overhead: ~1KB per client
  • Frame buffers: Variable based on terminal size
  • Audio buffers: Fixed size ring buffers

Thread Overhead:

  • 4 threads per client (receive, send, video render, audio render)
  • Minimal context switching overhead
  • CPU affinity possible for performance tuning

Best Practices

DO:

  • Always use proper lock ordering (RWLock → per-client mutex)
  • Use snapshot pattern for client state access
  • Check client->active before operations
  • Join threads before resource cleanup
  • Use hash table for client lookups (not array iteration)

DON'T:

  • Don't acquire per-client mutex before global RWLock
  • Don't hold locks during blocking operations
  • Don't access client state without mutex protection
  • Don't skip thread joins during cleanup
  • Don't modify client array without write lock
See also
src/server/client.c
src/server/client.h
Server Overview
Server Main Entry Point
Protocol Handler
Render Threads
Concurrency Documentation