32static socket_t bind_and_listen(
const char *address,
int family,
int port) {
33 struct addrinfo hints, *res = NULL;
34 memset(&hints, 0,
sizeof(hints));
35 hints.ai_family = family;
36 hints.ai_socktype = SOCK_STREAM;
37 hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
43 const char *addr_str = (address && address[0] !=
'\0') ? address : NULL;
45 int gai_result = getaddrinfo(addr_str, port_str, &hints, &res);
46 if (gai_result != 0) {
47 log_error(
"getaddrinfo failed for %s:%d: %s", addr_str ? addr_str :
"(wildcard)", port, gai_strerror(gai_result));
51 socket_t server_socket = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
53 log_error(
"Failed to create socket for %s:%d", addr_str ? addr_str :
"(wildcard)", port);
60 if (setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, (
const char *)&reuse,
sizeof(reuse)) < 0) {
61 log_warn(
"Failed to set SO_REUSEADDR on %s:%d", addr_str ? addr_str :
"(wildcard)", port);
65 if (family == AF_INET6) {
67 if (setsockopt(server_socket, IPPROTO_IPV6, IPV6_V6ONLY, (
const char *)&ipv6only,
sizeof(ipv6only)) < 0) {
68 log_warn(
"Failed to set IPV6_V6ONLY on [%s]:%d", addr_str ? addr_str :
"::", port);
73 if (bind(server_socket, res->ai_addr, (socklen_t)res->ai_addrlen) < 0) {
74 log_error(
"Failed to bind %s:%d", addr_str ? addr_str :
"(wildcard)", port);
83 if (listen(server_socket, 128) < 0) {
84 log_error(
"Failed to listen on %s:%d", addr_str ? addr_str :
"(wildcard)", port);
89 log_info(
"Listening on %s%s%s:%d (%s)", family == AF_INET6 ?
"[" :
"",
90 addr_str ? addr_str : (family == AF_INET ?
"0.0.0.0" :
"::"), family == AF_INET6 ?
"]" :
"", port,
91 family == AF_INET ?
"IPv4" :
"IPv6");
97 if (!server || !config) {
105 memset(server, 0,
sizeof(*server));
108 atomic_store(&server->
running,
true);
119 bool should_bind_ipv4 = config->
bind_ipv4;
120 bool should_bind_ipv6 = config->
bind_ipv6;
123 if (should_bind_ipv4) {
128 log_warn(
"Failed to bind IPv4 socket");
133 if (should_bind_ipv6) {
138 log_warn(
"Failed to bind IPv6 socket");
157 "client_handler is required for tcp_server_run() - use custom accept loop if handler is NULL");
160 log_info(
"TCP server starting accept loop...");
162 while (atomic_load(&server->
running)) {
182 struct timeval timeout = {.tv_sec = timeout_sec, .tv_usec = 0};
184 int select_result =
socket_select((
int)(max_fd + 1), &read_fds, NULL, NULL, &timeout);
186 if (select_result < 0) {
191 log_debug(
"select() interrupted by signal");
199 if (select_result == 0) {
218 struct sockaddr_storage client_addr;
219 socklen_t client_addr_len =
sizeof(client_addr);
220 socket_t client_socket = accept(ready_socket, (
struct sockaddr *)&client_addr, &client_addr_len);
223 log_warn(
"Failed to accept connection");
228 char client_ip[INET6_ADDRSTRLEN];
229 int addr_family = (client_addr.ss_family == AF_INET) ? AF_INET : AF_INET6;
231 SAFE_STRNCPY(client_ip,
"(unknown)",
sizeof(client_ip));
234 log_info(
"Accepted connection from %s", client_ip);
239 log_error(
"Failed to allocate client context");
245 ctx->
addr = client_addr;
259 log_error(
"Failed to create client handler thread for %s", client_ip);
269 log_info(
"TCP server accept loop exited");
278 log_info(
"Shutting down TCP server...");
281 atomic_store(&server->
running,
false);
300 HASH_ITER(hh, server->
clients, entry, tmp) {
312 HASH_DEL(server->
clients, entry);
323 log_info(
"TCP server shutdown complete");
357 SAFE_SNPRINTF(pool_name,
sizeof(pool_name),
"client_%d", socket);
369 log_debug(
"Added client socket=%d to registry", socket);
386 log_debug(
"Client socket=%d already removed from registry", socket);
401 HASH_DEL(server->
clients, entry);
406 log_debug(
"Removed client socket=%d from registry", socket);
411 if (!server || !out_data) {
433 if (!server || !callback) {
440 HASH_ITER(hh, server->
clients, entry, tmp) {
453 size_t count = HASH_COUNT(server->
clients);
478 int addr_family = (ctx->
addr.ss_family == AF_INET) ? AF_INET : AF_INET6;
495 if (ctx->
addr.ss_family == AF_INET) {
496 struct sockaddr_in *addr_in = (
struct sockaddr_in *)&ctx->
addr;
497 return ntohs(addr_in->sin_port);
498 }
else if (ctx->
addr.ss_family == AF_INET6) {
499 struct sockaddr_in6 *addr_in6 = (
struct sockaddr_in6 *)&ctx->
addr;
500 return ntohs(addr_in6->sin6_port);
513 log_warn(
"Rejecting client connection: %s", reason ? reason :
"unknown reason");
522 void *thread_arg,
int stop_id,
const char *thread_name) {
523 if (!server || !thread_func) {
534 HASH_FIND(hh, server->
clients, &client_socket,
sizeof(
socket_t), entry);
551 log_debug(
"Spawned thread '%s' (stop_id=%d) for client socket=%d (total_threads=%zu)",
552 thread_name ? thread_name :
"unnamed", stop_id, client_socket, thread_count);
569 HASH_FIND(hh, server->
clients, &client_socket,
sizeof(
socket_t), entry);
584 log_debug(
"All threads stopped for client socket=%d", client_socket);
589 if (!server || !count) {
602 HASH_FIND(hh, server->
clients, &client_socket,
sizeof(
socket_t), entry);
#define SAFE_STRNCPY(dst, src, size)
#define SAFE_MALLOC(size, cast)
#define SAFE_SNPRINTF(buffer, buffer_size,...)
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
asciichat_error_t
Error and exit codes - unified status values (0-255)
#define log_warn(...)
Log a WARN message.
#define log_error(...)
Log an ERROR message.
#define log_info(...)
Log an INFO message.
#define log_debug(...)
Log a DEBUG message.
asciichat_error_t format_ip_address(int family, const struct sockaddr *addr, char *output, size_t output_size)
Format IP address from socket address structure.
๐ IP Address Parsing and Formatting Utilities
void tcp_server_set_cleanup_callback(tcp_server_t *server, tcp_client_cleanup_fn cleanup_fn)
Set client cleanup callback.
void tcp_server_reject_client(socket_t socket, const char *reason)
Reject client connection with reason.
asciichat_error_t tcp_server_spawn_thread(tcp_server_t *server, socket_t client_socket, void *(*thread_func)(void *), void *thread_arg, int stop_id, const char *thread_name)
Spawn a worker thread for a client.
asciichat_error_t tcp_server_remove_client(tcp_server_t *server, socket_t socket)
Remove client from registry.
void tcp_server_foreach_client(tcp_server_t *server, tcp_client_foreach_fn callback, void *user_arg)
Iterate over all clients.
asciichat_error_t tcp_server_stop_client_threads(tcp_server_t *server, socket_t client_socket)
Stop all threads for a client in stop_id order.
asciichat_error_t tcp_server_init(tcp_server_t *server, const tcp_server_config_t *config)
Initialize TCP server.
asciichat_error_t tcp_server_get_client(tcp_server_t *server, socket_t socket, void **out_data)
Get client data.
int tcp_client_context_get_port(const tcp_client_context_t *ctx)
Get port number from client context.
asciichat_error_t tcp_server_run(tcp_server_t *server)
Run TCP server accept loop.
asciichat_error_t tcp_server_get_thread_count(tcp_server_t *server, socket_t client_socket, size_t *count)
Get thread count for a client.
void tcp_server_shutdown(tcp_server_t *server)
Shutdown TCP server.
const char * tcp_client_context_get_ip(const tcp_client_context_t *ctx, char *buf, size_t len)
Get formatted IP address from client context.
size_t tcp_server_get_client_count(tcp_server_t *server)
Get client count.
asciichat_error_t tcp_server_add_client(tcp_server_t *server, socket_t socket, void *client_data)
Add client to registry.
void(* tcp_client_foreach_fn)(socket_t socket, void *client_data, void *user_arg)
Callback for iterating over clients.
void(* tcp_client_cleanup_fn)(void *client_data)
Callback for client cleanup.
๐ Logging API with multiple log levels and terminal output control
Cross-platform socket interface for ascii-chat.
Per-client connection context.
socket_t client_socket
Client connection socket.
socklen_t addr_len
Address length.
struct sockaddr_storage addr
Client address.
void * user_data
User-provided data from config.
void * client_data
User-provided client data.
thread_pool_t * threads
Thread pool for client worker threads.
socket_t socket
Client socket (hash key)
TCP server configuration.
bool bind_ipv6
Whether to bind IPv6 socket.
void * user_data
User data passed to each client handler.
tcp_client_handler_fn client_handler
Client handler callback.
bool bind_ipv4
Whether to bind IPv4 socket.
const char * ipv4_address
IPv4 bind address (NULL or empty = don't bind)
int accept_timeout_sec
select() timeout in seconds (for responsive shutdown)
const char * ipv6_address
IPv6 bind address (NULL or empty = don't bind)
mutex_t clients_mutex
Mutex protecting client registry.
atomic_bool running
Server running flag (set false to shutdown)
tcp_server_config_t config
Server configuration.
tcp_client_entry_t * clients
Hash table of connected clients.
socket_t listen_socket
IPv4 listen socket.
socket_t listen_socket6
IPv6 listen socket.
tcp_client_cleanup_fn cleanup_fn
Callback for cleaning up client data.
void thread_pool_destroy(thread_pool_t *pool)
Destroy a thread pool.
thread_pool_t * thread_pool_create(const char *pool_name)
Create a new thread pool.
size_t thread_pool_get_count(const thread_pool_t *pool)
Get thread count in the pool.
asciichat_error_t thread_pool_spawn(thread_pool_t *pool, void *(*thread_func)(void *), void *thread_arg, int stop_id, const char *thread_name)
Spawn a worker thread in the pool.
asciichat_error_t thread_pool_stop_all(thread_pool_t *pool)
Stop all threads in the pool in stop_id order.
๐งต Generic thread pool abstraction for managing worker threads