TcpServer/TcpReactor Framework
This commit is contained in:
@@ -0,0 +1,225 @@
|
||||
#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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user