Files
cKit/Foundation/c_TcpSelectReactor.c
T
2026-09-08 10:35:16 +08:00

226 lines
8.0 KiB
C

#include <c_TcpSelectReactor.h>
#include <c_Macros.h>
/* ================================================================================================================== */
/* Concrete Implementation: Refactored Cross-Platform select() Driver Subclass Context */
typedef struct {
c_TcpReactor_t base;
c_socket_t* sockets; /* Array tracking all registered socket handles (Listener + Clients) */
c_size_t count; /* Current count of monitored sockets */
c_size_t capacity; /* Upper bounds allocation capacity limit */
c_Allocator_t allocator; /* Deep copy of the user-provided allocator */
} c_SelectReactorImpl_t;
static c_err_t Select_Add(c_TcpReactor_t* self, c_socket_t sock, uint32_t events) {
c_SelectReactorImpl_t* impl = (c_SelectReactorImpl_t*)self;
(void)events;
/* Enforce hard ceiling compliance checks against maximum threshold allowances for select */
if (impl->count >= FD_SETSIZE) {
return C_ERR_OUTOFBOUND;
}
if (impl->count >= impl->capacity) {
c_size_t old_cap = impl->capacity;
c_size_t new_cap = old_cap == 0 ? 16 : C_MIN(old_cap * 2, FD_SETSIZE);
c_socket_t* new_sockets = (c_socket_t*)c_Allocator_Realloc(
&impl->allocator, impl->sockets, old_cap * sizeof(c_socket_t), new_cap * sizeof(c_socket_t)
);
if (!new_sockets) return C_ERR_NOMEM;
impl->sockets = new_sockets;
impl->capacity = new_cap;
}
/* Prevent duplicate entries inside the tracking index */
for (c_size_t i = 0; i < impl->count; ++i) {
if (impl->sockets[i] == sock) return C_SUCCESS;
}
impl->sockets[impl->count++] = sock;
return C_SUCCESS;
}
static c_err_t Select_Remove(c_TcpReactor_t* self, c_socket_t sock) {
c_SelectReactorImpl_t* impl = (c_SelectReactorImpl_t*)self;
for (c_size_t i = 0; i < impl->count; ++i) {
if (impl->sockets[i] == sock) {
impl->sockets[i] = impl->sockets[impl->count - 1]; /* O(1) tail-swap optimization */
impl->count--;
return C_SUCCESS;
}
}
return C_ERR_NOTFOUND;
}
static c_err_t Select_Poll(c_TcpReactor_t* self, long timeout_ms, c_TcpServer_t* server, void* args) {
c_SelectReactorImpl_t* impl = (c_SelectReactorImpl_t*)self;
if (impl->count == 0 || !server->is_running) return C_SUCCESS;
fd_set read_set;
fd_set err_set;
FD_ZERO(&read_set);
FD_ZERO(&err_set);
c_socket_t max_fd = 0;
for (c_size_t i = 0; i < impl->count; ++i) {
FD_SET(impl->sockets[i], &read_set);
FD_SET(impl->sockets[i], &err_set);
#if !defined(_WIN32) && !defined(_WIN64)
if (impl->sockets[i] > max_fd) {
max_fd = impl->sockets[i];
}
#endif
}
struct timeval tv;
struct timeval* tv_ptr = NULL;
if (timeout_ms >= 0) {
tv.tv_sec = timeout_ms / 1000;
tv.tv_usec = (timeout_ms % 1000) * 1000;
tv_ptr = &tv;
}
#if defined(_WIN32) || defined(_WIN64)
int ret = select(0, &read_set, NULL, &err_set, tv_ptr);
#else
int ret = select((int)(max_fd + 1), &read_set, NULL, &err_set, tv_ptr);
#endif
if (ret < 0) {
if (server->fnOnError) {
server->fnOnError(server, server->listen_sock, C_TCPSERVER_ERR_ON_POLL, args);
}
return C_SUCCESS;
}
if (ret == 0) return C_SUCCESS; /* Timeout pass, exit early */
/*
* Safe Backward Traversal Pass:
* Scanning the tracked sockets pool backward ensures that deleting an element via
* tail-swapping doesn't corrupt unvisited index boundaries during the current pass.
*/
for (c_size_t i = impl->count; i > 0; --i) {
c_size_t idx = i - 1;
c_socket_t active_sock = impl->sockets[idx];
c_bool_t has_read = FD_ISSET(active_sock, &read_set);
c_bool_t has_error = FD_ISSET(active_sock, &err_set);
if (!has_read && !has_error) continue;
/* Channel A: Master server listener handles events */
if (active_sock == server->listen_sock) {
if (has_read) {
c_SockAddr_t peer_addr;
c_socket_t client = c_SocketUtil_Accept(server->listen_sock, &peer_addr);
if (c_Socket_IsValid(client)) {
bool keep = true;
if (server->fnOnConnect) {
keep = server->fnOnConnect(server, client, &peer_addr, args);
}
if (keep) {
Select_Add(self, client, 1); /* Add client onto select watchlist */
} else {
c_Socket_Close(client);
}
} else if (server->fnOnError) {
server->fnOnError(server, server->listen_sock, C_TCPSERVER_ERR_ON_ACCEPT, args);
}
}
if (has_error) {
if (server->fnOnError) {
server->fnOnError(server, server->listen_sock, C_TCPSERVER_ERR_ON_POLLERR, args);
}
}
ret--;
}
/* Channel B: Connected active client descriptor processing */
else {
c_bool_t keep_alive = C_TRUE;
/* Intercept operational error signals first */
if (has_error) {
if (server->fnOnError) {
server->fnOnError(server, active_sock, C_TCPSERVER_ERR_ON_POLLERR, args);
}
keep_alive = C_FALSE;
}
if (keep_alive && has_read) {
/* Intercept silent socket termination conditions using zero-byte peeks */
char peek_buf;
#if defined(_WIN32) || defined(_WIN64)
int peek_res = recv(active_sock, &peek_buf, 1, MSG_PEEK);
#else
ssize_t peek_res = recv(active_sock, &peek_buf, 1, MSG_PEEK);
#endif
if (peek_res == 0 || peek_res == C_SOCKET_ERROR) {
keep_alive = C_FALSE; /* Connection broken or closed by remote peer node */
} else if (server->fnOnRequest) {
/* Fire your exact matching signature callback layout */
keep_alive = server->fnOnRequest(server, active_sock, args);
}
}
/* Clean up active client descriptor arrays if data pipeline drops or errors out */
if (!keep_alive) {
Select_Remove(self, active_sock);
/* Fire your formal on_disconnect registration interface hook */
if (server->fnOnDisconnect) {
server->fnOnDisconnect(server, active_sock, args);
}
c_Socket_Close(active_sock);
}
ret--;
}
if (ret == 0) break; /* Event counters satisfied, return early */
}
return C_SUCCESS;
}
static void Select_Destroy(c_TcpReactor_t* self) {
c_SelectReactorImpl_t* impl = (c_SelectReactorImpl_t*)self;
c_Allocator_t alloc = impl->allocator;
/* Safely dismantle any hanging active client sockets remaining in the array pool */
for (c_size_t i = 0; i < impl->count; ++i) {
c_socket_t sock = impl->sockets[i];
c_Socket_Close(sock);
}
if (impl->sockets) c_Allocator_Free(&alloc, impl->sockets);
c_Allocator_Free(&alloc, impl);
}
static const c_TcpReactorVtbl_t g_SelectReactorVtbl = { Select_Add, Select_Remove, Select_Poll, Select_Destroy };
/* ------------------------------------------------------------------------------------------------------------------ */
/* */
c_err_t c_TcpSelectReactor_Create(c_TcpReactor_t** out_reactor, c_Allocator_t* allocator) {
if (!out_reactor) return C_ERR_PARAM;
c_Allocator_t alloc = allocator ? *allocator : c_DefaultAllocator;
c_SelectReactorImpl_t* impl = (c_SelectReactorImpl_t*)c_Allocator_Calloc(&alloc, 1, sizeof(c_SelectReactorImpl_t));
if (!impl) return C_ERR_NOMEM;
impl->base.vtbl = &g_SelectReactorVtbl;
impl->sockets = NULL;
impl->count = 0;
impl->capacity = 0;
impl->allocator = alloc;
*out_reactor = &impl->base;
return C_SUCCESS;
}