TcpClient/Reactor 框架
This commit is contained in:
@@ -123,6 +123,7 @@ typedef int c_err_t;
|
|||||||
#define C_SUCCESS C_ERR_OK
|
#define C_SUCCESS C_ERR_OK
|
||||||
#define C_ERR_NOTFOUND C_ERR_FAIL
|
#define C_ERR_NOTFOUND C_ERR_FAIL
|
||||||
#define C_ERR_OUTOFBOUND C_ERR_FAIL
|
#define C_ERR_OUTOFBOUND C_ERR_FAIL
|
||||||
|
#define C_ERR_EOF C_ERR_OUTOFBOUND
|
||||||
|
|
||||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||||
/* */
|
/* */
|
||||||
|
|||||||
@@ -0,0 +1,138 @@
|
|||||||
|
#include <c_TcpClient.h>
|
||||||
|
#include <c_AddrInfo.h>
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||||
|
/* */
|
||||||
|
|
||||||
|
c_err_t c_TcpClient_Connect(c_TcpClient_t* self, int family, const char* server, const char* port, void* args, c_Allocator_t* allocator) {
|
||||||
|
if (!self || (!server && !port)) return C_ERR_PARAM;
|
||||||
|
|
||||||
|
self->allocator = allocator ? *allocator : c_DefaultAllocator;
|
||||||
|
self->sock = C_INVALID_SOCKET;
|
||||||
|
self->is_linked = C_FALSE;
|
||||||
|
|
||||||
|
/* 1. Resolve network address dynamically using your custom c_AddrInfo_t wrapper */
|
||||||
|
c_AddrInfo_t addr_resolver;
|
||||||
|
c_err_t err = c_AddrInfo_Init(&addr_resolver, family, SOCK_STREAM, 0, server, port);
|
||||||
|
if (err != C_ERR_OK) {
|
||||||
|
c_Socket_Destroy();
|
||||||
|
return C_ERR_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
c_AddrInfo_t curr_node = addr_resolver;
|
||||||
|
c_bool_t connected = C_FALSE;
|
||||||
|
|
||||||
|
/* 2. Traverse the resolved address link chain until a connection succeeds */
|
||||||
|
while (curr_node.addrinfo_p != NULL) {
|
||||||
|
/* Open the system socket using descriptors from addrinfo */
|
||||||
|
self->sock = socket(
|
||||||
|
c_AddrInfo_GetFamily(&curr_node),
|
||||||
|
c_AddrInfo_GetSockType(&curr_node),
|
||||||
|
c_AddrInfo_GetProtocol(&curr_node)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (c_Socket_IsValid(self->sock)) {
|
||||||
|
/* Execute synchronous network connect transaction handshake */
|
||||||
|
int rc = connect(self->sock, curr_node.addrinfo_p->ai_addr, (int)curr_node.addrinfo_p->ai_addrlen);
|
||||||
|
if (rc != C_SOCKET_ERROR) {
|
||||||
|
connected = C_TRUE;
|
||||||
|
break; /* Route successfully established, break chain safely */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Connection failed on this node: close socket using custom macro helper */
|
||||||
|
c_Socket_Close(self->sock);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Shift forward to parse alternative network address frames */
|
||||||
|
c_AddrInfo_GetNext(&curr_node, &curr_node);
|
||||||
|
}
|
||||||
|
|
||||||
|
c_AddrInfo_Destroy(&addr_resolver);
|
||||||
|
|
||||||
|
if (!connected) {
|
||||||
|
if (self->fnOnError) {
|
||||||
|
self->fnOnError(self, C_TCPCLIENT_ERR_ON_CONNECT, args);
|
||||||
|
}
|
||||||
|
return C_ERR_FAIL; /* Host unreachable or timed out */
|
||||||
|
}
|
||||||
|
|
||||||
|
self->is_linked = C_TRUE;
|
||||||
|
if (self->fnOnConnect) {
|
||||||
|
if (!self->fnOnConnect(self, args)) {
|
||||||
|
self->is_linked = C_FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return C_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
c_err_t c_TcpClient_Send(c_TcpClient_t* self, const void* buffer, c_size_t bytes_to_send, c_size_t* out_bytes_sent) {
|
||||||
|
if (!self || !self->is_linked || !c_Socket_IsValid(self->sock) || !buffer) return C_ERR_PARAM;
|
||||||
|
if (bytes_to_send == 0) {
|
||||||
|
if (out_bytes_sent) *out_bytes_sent = 0;
|
||||||
|
return C_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
c_size_t total_sent = 0;
|
||||||
|
const char* ptr = (const char*)buffer;
|
||||||
|
|
||||||
|
/* Loop until the complete raw buffer payload block is flushed out to the wire */
|
||||||
|
while (total_sent < bytes_to_send) {
|
||||||
|
c_size_t remaining = bytes_to_send - total_sent;
|
||||||
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
|
int n = send(self->sock, ptr + total_sent, (int)remaining, 0);
|
||||||
|
#else
|
||||||
|
#ifdef MSG_NOSIGNAL
|
||||||
|
ssize_t n = send(self->sock, ptr + total_sent, (size_t)remaining, MSG_NOSIGNAL); /* Guard against SIGPIPE */
|
||||||
|
#else
|
||||||
|
ssize_t n = send(self->sock, ptr + total_sent, (size_t)remaining, 0);
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (n == C_SOCKET_ERROR) {
|
||||||
|
if (out_bytes_sent) *out_bytes_sent = total_sent;
|
||||||
|
return C_ERR_FAIL; /* Network pipeline transmission error */
|
||||||
|
}
|
||||||
|
total_sent += (c_size_t)n;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (out_bytes_sent) *out_bytes_sent = total_sent;
|
||||||
|
return C_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
c_err_t c_TcpClient_Recv(c_TcpClient_t* self, void* buffer, c_size_t max_bytes_to_read, c_size_t* out_bytes_read) {
|
||||||
|
if (!self || !self->is_linked || !c_Socket_IsValid(self->sock) || !buffer || max_bytes_to_read == 0) return C_ERR_PARAM;
|
||||||
|
|
||||||
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
|
int n = recv(self->sock, (char*)buffer, (int)max_bytes_to_read, 0);
|
||||||
|
#else
|
||||||
|
ssize_t n = recv(self->sock, buffer, (size_t)max_bytes_to_read, 0);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (n == 0) {
|
||||||
|
if (out_bytes_read) *out_bytes_read = 0;
|
||||||
|
return C_ERR_EOF; /* Connection clean cut closed by peer node */
|
||||||
|
}
|
||||||
|
if (n == C_SOCKET_ERROR) {
|
||||||
|
if (out_bytes_read) *out_bytes_read = 0;
|
||||||
|
return C_ERR_FAIL; /* Network pipeline failure */
|
||||||
|
}
|
||||||
|
|
||||||
|
if (out_bytes_read) *out_bytes_read = (c_size_t)n;
|
||||||
|
return C_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
void c_TcpClient_Destroy(c_TcpClient_t* self) {
|
||||||
|
if (!self) return;
|
||||||
|
|
||||||
|
self->is_linked = C_FALSE;
|
||||||
|
if (c_Socket_IsValid(self->sock)) {
|
||||||
|
c_Socket_Close(self->sock);
|
||||||
|
}
|
||||||
|
if (self->reactor) c_TcpClientReactor_Destroy(self->reactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
c_err_t c_TcpClient_PollEvents(c_TcpClient_t* self, long timeout_ms, void* args) {
|
||||||
|
if (!self || !self->reactor || self->is_linked==C_FALSE) return C_ERR_PARAM;
|
||||||
|
return c_TcpClientReactor_Poll(self->reactor, timeout_ms, self, args);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
#ifndef INCLUDED_C_TCPCLIENT_H
|
||||||
|
#define INCLUDED_C_TCPCLIENT_H
|
||||||
|
|
||||||
|
#ifndef INCLUDED_C_SOCKET_H
|
||||||
|
#include <c_Socket.h>
|
||||||
|
#endif /*INCLUDED_C_SOCKET_H*/
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef INCLUDED_C_ALLOCATOR_H
|
||||||
|
#include <c_Allocator.h>
|
||||||
|
#endif /*INCLUDED_C_ALLOCATOR_H*/
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||||
|
/* */
|
||||||
|
|
||||||
|
#define C_TCPCLIENT_ERR_ON_POLL 1001
|
||||||
|
#define C_TCPCLIENT_ERR_ON_ACCEPT 1002
|
||||||
|
#define C_TCPCLIENT_ERR_ON_POLLERR 1003
|
||||||
|
#define C_TCPCLIENT_ERR_ON_RECV 1004
|
||||||
|
#define C_TCPCLIENT_ERR_ON_CONNECT 1005
|
||||||
|
|
||||||
|
#define C_TCPCLIENT_INITIALIZE {0}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||||
|
/* */
|
||||||
|
|
||||||
|
typedef struct c_TcpClient_t c_TcpClient_t;
|
||||||
|
|
||||||
|
typedef bool (*c_TcpClient_OnConnect_t)(c_TcpClient_t* client, void* args);
|
||||||
|
typedef bool (*c_TcpClient_OnData_t)(c_TcpClient_t* client, void* args);
|
||||||
|
typedef void (*c_TcpClient_OnDisconnect_t)(c_TcpClient_t* client, void* args);
|
||||||
|
typedef void (*c_TcpClient_OnError_t)(c_TcpClient_t* client, c_err_t error_code, void* args);
|
||||||
|
|
||||||
|
typedef struct c_TcpClientReactor_t c_TcpClientReactor_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
c_err_t (*add)(c_TcpClientReactor_t* self, c_socket_t sock, uint32_t events);
|
||||||
|
c_err_t (*remove)(c_TcpClientReactor_t* self, c_socket_t sock);
|
||||||
|
c_err_t (*poll)(c_TcpClientReactor_t* self, long timeout_ms, c_TcpClient_t* client, void* args);
|
||||||
|
void (*destroy)(c_TcpClientReactor_t* self);
|
||||||
|
} c_TcpClientReactorVtbl_t;
|
||||||
|
|
||||||
|
struct c_TcpClientReactor_t {
|
||||||
|
const c_TcpClientReactorVtbl_t* vtbl;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct c_TcpClient_t {
|
||||||
|
c_socket_t sock;
|
||||||
|
c_bool_t is_linked;
|
||||||
|
c_TcpClientReactor_t* reactor;
|
||||||
|
c_TcpClient_OnConnect_t fnOnConnect;
|
||||||
|
c_TcpClient_OnData_t fnOnData;
|
||||||
|
c_TcpClient_OnDisconnect_t fnOnDisconnect;
|
||||||
|
c_TcpClient_OnError_t fnOnError;
|
||||||
|
c_Allocator_t allocator;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||||
|
/* */
|
||||||
|
|
||||||
|
C_STATIC_FORCE_INLINE
|
||||||
|
c_err_t c_TcpClientReactor_Add(c_TcpClientReactor_t* self, c_socket_t sock, uint32_t events) {
|
||||||
|
if (!self || !self->vtbl || !self->vtbl->add || !c_Socket_IsValid(sock)) return C_ERR_PARAM;
|
||||||
|
return self->vtbl->add(self, sock, events);
|
||||||
|
}
|
||||||
|
|
||||||
|
C_STATIC_FORCE_INLINE
|
||||||
|
c_err_t c_TcpClientReactor_Remove(c_TcpClientReactor_t* self, c_socket_t sock) {
|
||||||
|
if (!self || !self->vtbl || !self->vtbl->remove || !c_Socket_IsValid(sock)) return C_ERR_PARAM;
|
||||||
|
return self->vtbl->remove(self, sock);
|
||||||
|
}
|
||||||
|
|
||||||
|
C_STATIC_FORCE_INLINE
|
||||||
|
c_err_t c_TcpClientReactor_Poll(c_TcpClientReactor_t* self, long timeout_ms, c_TcpClient_t* client, void* args) {
|
||||||
|
if (!self || !self->vtbl || !self->vtbl->poll || !client) return C_ERR_PARAM;
|
||||||
|
return self->vtbl->poll(self, timeout_ms, client, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
C_STATIC_FORCE_INLINE
|
||||||
|
void c_TcpClientReactor_Destroy(c_TcpClientReactor_t* self) {
|
||||||
|
if (!self || !self->vtbl || !self->vtbl->destroy) return;
|
||||||
|
self->vtbl->destroy(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||||
|
/* */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Establishes a synchronous stream connection to a remote server host.
|
||||||
|
* @param family Address family (e.g., AF_INET, AF_INET6, or AF_UNSPEC)
|
||||||
|
* @param server Host destination string (e.g., "127.0.0.1" or "domain.com")
|
||||||
|
* @param port Service port string identifier (e.g., "8080")
|
||||||
|
* @param args User defined arguments
|
||||||
|
* @param allocator Explicit allocator context used to configure socket instances
|
||||||
|
*/
|
||||||
|
c_err_t c_TcpClient_Connect(c_TcpClient_t* self, int family, const char* server, const char* port,
|
||||||
|
void* args,
|
||||||
|
c_Allocator_t* allocator);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sends a raw data block across the socket, looping until the full buffer is dispatched.
|
||||||
|
* @param buffer Pointer to the source data block payload to transmit.
|
||||||
|
* @param bytes_to_send Exact size threshold of the payload block in bytes.
|
||||||
|
* @param out_bytes_sent Receives the actual number of bytes written out to the wire.
|
||||||
|
*/
|
||||||
|
c_err_t c_TcpClient_Send(c_TcpClient_t* self, const void* buffer, c_size_t bytes_to_send, c_size_t* out_bytes_sent);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reads a data block up to the requested size from the socket channel.
|
||||||
|
* @param buffer Pointer to the destination buffer to hold incoming data.
|
||||||
|
* @param max_bytes_to_read Upper ceiling threshold size of bytes to pull.
|
||||||
|
* @param out_bytes_read Receives the actual number of bytes fetched from the wire.
|
||||||
|
*/
|
||||||
|
c_err_t c_TcpClient_Recv(c_TcpClient_t* self, void* buffer, c_size_t max_bytes_to_read, c_size_t* out_bytes_read);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Safely shuts down pipelines, closes active sockets, and recycles memory.
|
||||||
|
*/
|
||||||
|
void c_TcpClient_Destroy(c_TcpClient_t* self);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Executes a non-recursive, single-pass asynchronous polling event dispatch iteration for the client.
|
||||||
|
*/
|
||||||
|
c_err_t c_TcpClient_PollEvents(c_TcpClient_t* self, long timeout_ms, void* args);
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||||
|
/* */
|
||||||
|
|
||||||
|
C_STATIC_FORCE_INLINE
|
||||||
|
void c_TcpClient_SetReactor(c_TcpClient_t* self, c_TcpClientReactor_t* reactor) {
|
||||||
|
if (!self || !reactor) return;
|
||||||
|
self->reactor = reactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
C_STATIC_FORCE_INLINE
|
||||||
|
void c_TcpClient_SetOnConnect(c_TcpClient_t* self, c_TcpClient_OnConnect_t fnOnConnect) {
|
||||||
|
if (!self ) return;
|
||||||
|
self->fnOnConnect = fnOnConnect;
|
||||||
|
}
|
||||||
|
|
||||||
|
C_STATIC_FORCE_INLINE
|
||||||
|
void c_TcpClient_SetOnData(c_TcpClient_t* self, c_TcpClient_OnData_t fnOnData) {
|
||||||
|
if (!self ) return;
|
||||||
|
self->fnOnData = fnOnData;
|
||||||
|
}
|
||||||
|
|
||||||
|
C_STATIC_FORCE_INLINE
|
||||||
|
void c_TcpClient_SetOnDisconnect(c_TcpClient_t* self, c_TcpClient_OnDisconnect_t fnOnDisconnect) {
|
||||||
|
if (!self ) return;
|
||||||
|
self->fnOnDisconnect = fnOnDisconnect;
|
||||||
|
}
|
||||||
|
|
||||||
|
C_STATIC_FORCE_INLINE
|
||||||
|
void c_TcpClient_SetOnError(c_TcpClient_t* self, c_TcpClient_OnError_t fnOnError) {
|
||||||
|
if (!self ) return;
|
||||||
|
self->fnOnError = fnOnError;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /*INCLUDED_C_TCPCLIENT_H*/
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
#include "c_Test.h"
|
||||||
|
#include "c_TcpClient.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
TEST_CASE(test_raw_stream_free_client_lifecycle) {
|
||||||
|
c_Socket_Init();
|
||||||
|
c_TcpClient_t client=C_TCPCLIENT_INITIALIZE;
|
||||||
|
|
||||||
|
/* Connect operation should gracefully fail on an unbound dead port */
|
||||||
|
c_err_t err = c_TcpClient_Connect(&client, AF_INET, "127.0.0.1", "9999", 0, NULL);
|
||||||
|
ASSERT_INT_EQ(C_ERR_FAIL, err);
|
||||||
|
ASSERT_FALSE(client.is_linked);
|
||||||
|
ASSERT_FALSE(c_Socket_IsValid(client.sock));
|
||||||
|
|
||||||
|
/* Raw Send operations must catch parameter unassigned states safely */
|
||||||
|
c_size_t sent = 0;
|
||||||
|
err = c_TcpClient_Send(&client, "DATA", 4, &sent);
|
||||||
|
ASSERT_INT_EQ(C_ERR_PARAM, err);
|
||||||
|
|
||||||
|
/* Execute explicit structure cleanup safety tests */
|
||||||
|
c_TcpClient_Destroy(&client);
|
||||||
|
c_Socket_Destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
TEST_START(TcpClient_RawStreamFree_Suite);
|
||||||
|
RUN_TEST(test_raw_stream_free_client_lifecycle);
|
||||||
|
TEST_REPORT();
|
||||||
|
RETURN_TEST_STATUS;
|
||||||
|
}
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
#include <c_TcpClientPollReactor.h>
|
||||||
|
|
||||||
|
#if !defined(_WIN32) && !defined(_WIN64)
|
||||||
|
#include <poll.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
c_TcpClientReactor_t base;
|
||||||
|
struct pollfd* fds;
|
||||||
|
c_size_t count;
|
||||||
|
c_size_t capacity;
|
||||||
|
c_Allocator_t allocator;
|
||||||
|
} c_ClientPollImpl_t;
|
||||||
|
|
||||||
|
static c_err_t ClientPoll_Add(c_TcpClientReactor_t* self, c_socket_t sock, uint32_t events) {
|
||||||
|
c_ClientPollImpl_t* impl = (c_ClientPollImpl_t*)self;
|
||||||
|
if (impl->count >= impl->capacity) {
|
||||||
|
c_size_t old_cap = impl->capacity;
|
||||||
|
c_size_t new_cap = old_cap == 0 ? 4 : old_cap * 2;
|
||||||
|
struct pollfd* new_fds = (struct pollfd*)c_Allocator_Realloc(&impl->allocator, impl->fds, old_cap * sizeof(struct pollfd), new_cap * sizeof(struct pollfd));
|
||||||
|
if (!new_fds) return C_ERR_NOMEM;
|
||||||
|
impl->fds = new_fds;
|
||||||
|
impl->capacity = new_cap;
|
||||||
|
}
|
||||||
|
impl->fds[impl->count].fd = (int)sock;
|
||||||
|
impl->fds[impl->count].events = (short)(events ? POLLIN : 0);
|
||||||
|
impl->fds[impl->count].revents = 0;
|
||||||
|
impl->count++;
|
||||||
|
return C_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static c_err_t ClientPoll_Remove(c_TcpClientReactor_t* self, c_socket_t sock) {
|
||||||
|
c_ClientPollImpl_t* impl = (c_ClientPollImpl_t*)self;
|
||||||
|
for (c_size_t i = 0; i < impl->count; ++i) {
|
||||||
|
if (impl->fds[i].fd == (int)sock) {
|
||||||
|
impl->fds[i] = impl->fds[impl->count - 1]; /* O(1) tail-swap */
|
||||||
|
impl->count--;
|
||||||
|
return C_SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return C_ERR_NOTFOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
static c_err_t ClientPoll_Poll(c_TcpClientReactor_t* self, long timeout_ms, c_TcpClient_t* client, void* args) {
|
||||||
|
c_ClientPollImpl_t* impl = (c_ClientPollImpl_t*)self;
|
||||||
|
if (!client->is_linked || !c_Socket_IsValid(client->sock)) return C_SUCCESS;
|
||||||
|
|
||||||
|
if (impl->count == 0) {
|
||||||
|
ClientPoll_Add(self, client->sock, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
|
int ret = WSAPoll(impl->fds, (ULONG)impl->count, (INT)timeout_ms);
|
||||||
|
#else
|
||||||
|
int ret = poll(impl->fds, (nfds_t)impl->count, (int)timeout_ms);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (ret < 0) {
|
||||||
|
if (client->fnOnError) {
|
||||||
|
client->fnOnError(client, C_TCPCLIENT_ERR_ON_POLL, args);
|
||||||
|
}
|
||||||
|
return C_SUCCESS;
|
||||||
|
}
|
||||||
|
if (ret == 0) return C_SUCCESS;
|
||||||
|
|
||||||
|
struct pollfd* client_fd = &impl->fds[0];
|
||||||
|
if (client_fd->revents & (POLLIN | POLLHUP | POLLERR)) {
|
||||||
|
c_bool_t keep_alive = C_TRUE;
|
||||||
|
|
||||||
|
if (client_fd->revents & POLLERR) {
|
||||||
|
if (client->fnOnError) {
|
||||||
|
client->fnOnError(client, C_TCPCLIENT_ERR_ON_POLLERR, args);
|
||||||
|
}
|
||||||
|
keep_alive = C_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keep_alive) {
|
||||||
|
char peek_buf;
|
||||||
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
|
int peek_res = recv(client->sock, &peek_buf, 1, MSG_PEEK);
|
||||||
|
#else
|
||||||
|
ssize_t peek_res = recv(client->sock, &peek_buf, 1, MSG_PEEK);
|
||||||
|
#endif
|
||||||
|
if (peek_res == 0 || peek_res == C_SOCKET_ERROR) {
|
||||||
|
keep_alive = C_FALSE;
|
||||||
|
} else if (client->fnOnData) {
|
||||||
|
keep_alive = client->fnOnData(client, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!keep_alive || (client_fd->revents & POLLHUP)) {
|
||||||
|
ClientPoll_Remove(self, client->sock);
|
||||||
|
if (client->fnOnDisconnect) {
|
||||||
|
client->fnOnDisconnect(client, args);
|
||||||
|
}
|
||||||
|
c_Socket_Close(client->sock);
|
||||||
|
client->is_linked = false;
|
||||||
|
return C_ERR_EOF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return C_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ClientPoll_Destroy(c_TcpClientReactor_t* self) {
|
||||||
|
c_ClientPollImpl_t* impl = (c_ClientPollImpl_t*)self;
|
||||||
|
c_Allocator_t alloc = impl->allocator;
|
||||||
|
if (impl->fds) c_Allocator_Free(&alloc, impl->fds);
|
||||||
|
c_Allocator_Free(&alloc, impl);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const c_TcpClientReactorVtbl_t g_ClientPollVtbl = { ClientPoll_Add, ClientPoll_Remove, ClientPoll_Poll, ClientPoll_Destroy };
|
||||||
|
|
||||||
|
c_err_t c_TcpClientPollReactor_Create(c_TcpClientReactor_t** out_reactor, c_Allocator_t* allocator) {
|
||||||
|
if (!out_reactor) return C_ERR_PARAM;
|
||||||
|
c_Allocator_t alloc = allocator ? *allocator : c_DefaultAllocator;
|
||||||
|
c_ClientPollImpl_t* impl = (c_ClientPollImpl_t*)c_Allocator_Calloc(&alloc, 1, sizeof(c_ClientPollImpl_t));
|
||||||
|
if (!impl) return C_ERR_NOMEM;
|
||||||
|
impl->base.vtbl = &g_ClientPollVtbl;
|
||||||
|
impl->fds = NULL;
|
||||||
|
impl->count = 0;
|
||||||
|
impl->capacity = 0;
|
||||||
|
impl->allocator = alloc;
|
||||||
|
*out_reactor = &impl->base;
|
||||||
|
return C_SUCCESS;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
#ifndef INCLUDED_C_TCPCLIENTPOLLREACTOR_H
|
||||||
|
#define INCLUDED_C_TCPCLIENTPOLLREACTOR_H
|
||||||
|
|
||||||
|
#ifndef INCLUDED_C_TCPCLIENT_H
|
||||||
|
#include <c_TcpClient.h>
|
||||||
|
#endif /*INCLUDED_C_TCPCLIENT_H*/
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||||
|
/* */
|
||||||
|
|
||||||
|
c_err_t c_TcpClientPollReactor_Create(c_TcpClientReactor_t** out_reactor, c_Allocator_t* allocator);
|
||||||
|
|
||||||
|
#endif /*INCLUDED_C_TCPCLIENTPOLLREACTOR_H*/
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
#include "c_TcpClient.h"
|
||||||
|
#include "c_TcpClientPollReactor.h"
|
||||||
|
#include "c_SocketUtil.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
static bool OnConnect(c_TcpClient_t* client, void* args) {
|
||||||
|
char ip[128];
|
||||||
|
char port[32];
|
||||||
|
c_SocketUtil_GetNameInfoForSocket(client->sock, ip, sizeof(ip), port, sizeof(port));
|
||||||
|
printf("[CONN] %s:%s\n", ip, port);
|
||||||
|
c_TcpClient_Send(client, "Hello", 5, 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool OnData(c_TcpClient_t* client, void* args) {
|
||||||
|
char ip[128];
|
||||||
|
char port[32];
|
||||||
|
c_SocketUtil_GetNameInfoForSocket(client->sock, ip, sizeof(ip), port, sizeof(port));
|
||||||
|
printf("[DATA] %s:%s\n", ip, port);
|
||||||
|
char buffer[1024]={0};
|
||||||
|
c_size_t recv_bytes = 0;
|
||||||
|
c_TcpClient_Recv(client, buffer, sizeof(buffer), &recv_bytes);
|
||||||
|
printf("%.*s\n", (int)recv_bytes, buffer);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void OnDisconnect(c_TcpClient_t* client, void* args) {
|
||||||
|
char ip[128];
|
||||||
|
char port[32];
|
||||||
|
c_SocketUtil_GetNameInfoForSocket(client->sock, ip, sizeof(ip), port, sizeof(port));
|
||||||
|
printf("[CLOSE] %s:%s\n", ip, port);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void OnError(c_TcpClient_t* client, c_err_t err, void* args) {
|
||||||
|
|
||||||
|
if (c_Socket_IsValid(client->sock)) {
|
||||||
|
char ip[128];
|
||||||
|
char port[32];
|
||||||
|
c_SocketUtil_GetNameInfoForSocket(client->sock, ip, sizeof(ip), port, sizeof(port));
|
||||||
|
printf("[ERROR] %s:%s code:%d\n", ip, port, err);
|
||||||
|
}else {
|
||||||
|
printf("[ERROR] code:%d\n", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv){
|
||||||
|
c_Socket_Init();
|
||||||
|
|
||||||
|
c_TcpClient_t client=C_TCPCLIENT_INITIALIZE;
|
||||||
|
c_TcpClient_SetOnConnect(&client, OnConnect);
|
||||||
|
c_TcpClient_SetOnData(&client, OnData);
|
||||||
|
c_TcpClient_SetOnDisconnect(&client, OnDisconnect);
|
||||||
|
c_TcpClient_SetOnError(&client, OnError);
|
||||||
|
|
||||||
|
c_err_t err = c_TcpClient_Connect(&client, AF_INET, "127.0.0.1", "1313", 0, NULL);
|
||||||
|
|
||||||
|
c_TcpClientReactor_t* reactor = NULL;
|
||||||
|
c_TcpClientPollReactor_Create(&reactor, NULL);
|
||||||
|
c_TcpClient_SetReactor(&client, reactor);
|
||||||
|
|
||||||
|
while (client.is_linked) {
|
||||||
|
c_TcpClient_PollEvents(&client, 1000, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
c_TcpClient_Destroy(&client);
|
||||||
|
c_Socket_Destroy();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
#include <c_TcpClient.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "c_Test.h"
|
||||||
|
|
||||||
|
int main(int argc, char** argv){
|
||||||
|
c_Socket_Init();
|
||||||
|
const char* server_host = "127.0.0.1";
|
||||||
|
const char* server_port = "1313"; /* Your time server daytime service port */
|
||||||
|
|
||||||
|
c_TcpClient_t client;
|
||||||
|
printf("[CLIENT] Connecting to Time Server at %s:%s...\n", server_host, server_port);
|
||||||
|
|
||||||
|
/* 1. Establish a raw stream connection using your explicit family-agnostic constructor */
|
||||||
|
c_err_t err = c_TcpClient_Connect(&client, AF_INET, server_host, server_port, NULL);
|
||||||
|
if (err != C_SUCCESS) {
|
||||||
|
fprintf(stderr, "[ERROR] Unable to connect to remote Time Server host destination.\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("[CLIENT] Connection successful! Awaiting network timestamp data package...\n");
|
||||||
|
|
||||||
|
char recv_buffer[128];
|
||||||
|
memset(recv_buffer, 0, sizeof(recv_buffer));
|
||||||
|
c_size_t total_bytes_read = 0;
|
||||||
|
char send_buffer[1024];
|
||||||
|
c_size_t send_bytes = 0;
|
||||||
|
/*
|
||||||
|
* 2. Execute a raw block read operation.
|
||||||
|
* Loop to read stream chunks until the server sends the full packet and shuts down (returns C_ERR_EOF).
|
||||||
|
*/
|
||||||
|
while (true) {
|
||||||
|
c_size_t chunk_bytes = 0;
|
||||||
|
char* current_ptr = recv_buffer + total_bytes_read;
|
||||||
|
c_size_t available_space = sizeof(recv_buffer) - total_bytes_read - 1;
|
||||||
|
|
||||||
|
if (available_space == 0) break; /* Safety boundary ceiling check */
|
||||||
|
|
||||||
|
if (send_bytes == 0) {
|
||||||
|
c_size_t sz = snprintf(send_buffer, sizeof(send_buffer), "GET_TIME");
|
||||||
|
c_TcpClient_Send(&client, send_buffer, sz, &send_bytes);
|
||||||
|
}else {
|
||||||
|
// c_size_t sz = snprintf(send_buffer, sizeof(send_buffer), "STOP");
|
||||||
|
// c_TcpClient_Send(&client, send_buffer, sz, &send_bytes);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c_TcpClient_Recv(&client, current_ptr, available_space, &chunk_bytes);
|
||||||
|
|
||||||
|
if (err == C_ERR_EOF) {
|
||||||
|
break; /* Server closed the connection gracefully, transaction complete */
|
||||||
|
}
|
||||||
|
if (err != C_SUCCESS) {
|
||||||
|
fprintf(stderr, "[ERROR] Network stream read transaction failure caught.\n");
|
||||||
|
c_TcpClient_Destroy(&client);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
total_bytes_read += chunk_bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3. Null-terminate the raw buffer safely before string logging operations */
|
||||||
|
recv_buffer[total_bytes_read] = '\0';
|
||||||
|
|
||||||
|
printf("\n " COLOR_GREEN "[SUCCESS] Network Time String Intercepted:" COLOR_RESET "\n");
|
||||||
|
printf(" >>> %s", recv_buffer);
|
||||||
|
|
||||||
|
/* 4. Safely close physical socket descriptors and reclaim global system assets */
|
||||||
|
c_TcpClient_Destroy(&client);
|
||||||
|
printf("\n[CLIENT] Client connection resources cleanly recycled.\n");
|
||||||
|
|
||||||
|
c_Socket_Destroy();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
#include <c_TcpEpollReactor.h>
|
||||||
|
#include "c_TcpServer.h"
|
||||||
|
|
||||||
|
#if defined(__linux__)
|
||||||
|
#include <sys/epoll.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define MAX_EPOLL_EVENTS 1024
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
c_TcpReactor_t base;
|
||||||
|
int epoll_fd; /* Raw Linux epoll kernel handle instance descriptor */
|
||||||
|
struct epoll_event events[MAX_EPOLL_EVENTS]; /* Pre-allocated active kernel alerts buffer */
|
||||||
|
c_Allocator_t allocator; /* Deep copy of the user-provided allocator */
|
||||||
|
} c_EPollReactorImpl_t;
|
||||||
|
|
||||||
|
static c_err_t EPoll_Add(c_TcpReactor_t* self, c_socket_t sock, uint32_t events) {
|
||||||
|
c_EPollReactorImpl_t* impl = (c_EPollReactorImpl_t*)self;
|
||||||
|
|
||||||
|
struct epoll_event ev;
|
||||||
|
memset(&ev, 0, sizeof(ev));
|
||||||
|
/* Map incoming events to POLLIN / read data availability triggers */
|
||||||
|
ev.events = (events ? EPOLLIN : 0) | EPOLLERR | EPOLLHUP;
|
||||||
|
ev.data.fd = (int)sock;
|
||||||
|
|
||||||
|
if (epoll_ctl(impl->epoll_fd, EPOLL_CTL_ADD, (int)sock, &ev) < 0) {
|
||||||
|
return C_ERR_FAIL;
|
||||||
|
}
|
||||||
|
return C_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static c_err_t EPoll_Remove(c_TcpReactor_t* self, c_socket_t sock) {
|
||||||
|
c_EPollReactorImpl_t* impl = (c_EPollReactorImpl_t*)self;
|
||||||
|
|
||||||
|
/* Passing NULL for the epoll_event structure is valid for deletions since Linux 2.6.9 */
|
||||||
|
if (epoll_ctl(impl->epoll_fd, EPOLL_CTL_DEL, (int)sock, NULL) < 0) {
|
||||||
|
return C_ERR_NOTFOUND;
|
||||||
|
}
|
||||||
|
return C_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static c_err_t EPoll_Poll(c_TcpReactor_t* self, int timeout_ms, c_TcpServer_t* server, void* args) {
|
||||||
|
c_EPollReactorImpl_t* impl = (c_EPollReactorImpl_t*)self;
|
||||||
|
if (!server->is_running) return C_SUCCESS;
|
||||||
|
|
||||||
|
/* Intercept readiness frames directly out of the epoll kernel tree instance */
|
||||||
|
int ret = epoll_wait(impl->epoll_fd, impl->events, MAX_EPOLL_EVENTS, timeout_ms);
|
||||||
|
|
||||||
|
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 cleanly */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Iterative Forward Processing Pass:
|
||||||
|
* Unlike arrays, epoll_wait packs active ready elements sequentially into indices 0 to ret-1.
|
||||||
|
* This provides a strict O(1) loop traversal path that is entirely immune to index mutations.
|
||||||
|
*/
|
||||||
|
for (int i = 0; i < ret; ++i) {
|
||||||
|
struct epoll_event* current_ev = &impl->events[i];
|
||||||
|
c_socket_t active_sock = (c_socket_t)current_ev->data.fd;
|
||||||
|
|
||||||
|
/* Channel A: Master server listener handles incoming connection handshakes */
|
||||||
|
if (active_sock == server->listen_sock) {
|
||||||
|
if (current_ev->events & EPOLLIN) {
|
||||||
|
c_SockAddr_t peer_addr;
|
||||||
|
socklen_t addr_len = sizeof(peer_addr);
|
||||||
|
c_socket_t client = accept(server->listen_sock, (struct sockaddr*)&peer_addr, &addr_len);
|
||||||
|
|
||||||
|
if (c_Socket_IsValid(client)) {
|
||||||
|
bool keep = true;
|
||||||
|
if (server->fnOnConnect) {
|
||||||
|
keep = server->fnOnConnect(server, client, &peer_addr, args);
|
||||||
|
}
|
||||||
|
if (keep) {
|
||||||
|
EPoll_Add(self, client, 1); /* Inject new client into the red-black tree watchlist */
|
||||||
|
} else {
|
||||||
|
c_Socket_Close(client);
|
||||||
|
}
|
||||||
|
} else if (server->fnOnError) {
|
||||||
|
server->fnOnError(server, server->listen_sock, C_TCPSERVER_ERR_ON_ACCEPT, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (current_ev->events & (EPOLLERR | EPOLLHUP)) {
|
||||||
|
if (server->fnOnError) {
|
||||||
|
server->fnOnError(server, server->listen_sock, C_TCPSERVER_ERR_ON_POLLERR, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Channel B: Connected active client descriptors processing data readiness loops */
|
||||||
|
else {
|
||||||
|
c_bool_t keep_alive = C_TRUE;
|
||||||
|
|
||||||
|
/* Intercept hardware-level operational error signals first */
|
||||||
|
if (current_ev->events & EPOLLERR) {
|
||||||
|
if (server->fnOnError) {
|
||||||
|
server->fnOnError(server, active_sock, C_TCPSERVER_ERR_ON_POLLERR, args);
|
||||||
|
}
|
||||||
|
keep_alive = C_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keep_alive && (current_ev->events & EPOLLIN)) {
|
||||||
|
/* Identify clean socket EOF drops via zero-byte peeks */
|
||||||
|
char peek_buf;
|
||||||
|
ssize_t peek_res = recv(active_sock, &peek_buf, 1, MSG_PEEK);
|
||||||
|
|
||||||
|
if (peek_res == 0 || peek_res < 0) {
|
||||||
|
keep_alive = C_FALSE; /* Connection explicitly severed by remote peer node */
|
||||||
|
} else if (server->fnOnRequest) {
|
||||||
|
keep_alive = server->fnOnRequest(server, active_sock, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Clean up active client descriptor tracking states if loop signaling drops or errors out */
|
||||||
|
if (!keep_alive || (current_ev->events & EPOLLHUP)) {
|
||||||
|
/*
|
||||||
|
* Remove explicitly from epoll tracking first.
|
||||||
|
* Closing the socket handle automatically removes it from epoll,
|
||||||
|
* but manual synchronization ensures clean deterministic state tracking.
|
||||||
|
*/
|
||||||
|
EPoll_Remove(self, active_sock);
|
||||||
|
|
||||||
|
if (server->fnOnDisconnect) {
|
||||||
|
server->fnOnDisconnect(server, active_sock, args);
|
||||||
|
}
|
||||||
|
c_Socket_Close(active_sock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return C_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void EPoll_Destroy(c_TcpReactor_t* self) {
|
||||||
|
c_EPollReactorImpl_t* impl = (c_EPollReactorImpl_t*)self;
|
||||||
|
c_Allocator_t alloc = impl->allocator;
|
||||||
|
|
||||||
|
if (impl->epoll_fd >= 0) {
|
||||||
|
close(impl->epoll_fd);
|
||||||
|
}
|
||||||
|
c_Allocator_Free(&alloc, impl);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const c_TcpReactorVtbl_t g_EPollReactorVtbl = { EPoll_Add, EPoll_Remove, EPoll_Poll, EPoll_Destroy };
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||||
|
/* */
|
||||||
|
|
||||||
|
|
||||||
|
c_err_t c_TcpEPollReactor_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_EPollReactorImpl_t* impl = (c_EPollReactorImpl_t*)c_Allocator_Calloc(&alloc, 1, sizeof(c_EPollReactorImpl_t));
|
||||||
|
if (!impl) return C_ERR_NOMEM;
|
||||||
|
|
||||||
|
/* Create the epoll instance handle (Size parameter must be > 0; ignored since Linux 2.6.8) */
|
||||||
|
impl->epoll_fd = epoll_create(1);
|
||||||
|
if (impl->epoll_fd < 0) {
|
||||||
|
c_Allocator_Free(&alloc, impl);
|
||||||
|
return C_ERR_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl->base.vtbl = &g_EPollReactorVtbl;
|
||||||
|
impl->allocator = alloc;
|
||||||
|
*out_reactor = &impl->base;
|
||||||
|
return C_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* __linux__ */
|
||||||
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
#ifndef INCLUDED_C_TCPEPOLLREACTOR_H
|
||||||
|
#define INCLUDED_C_TCPEPOLLREACTOR_H
|
||||||
|
|
||||||
|
#ifndef INCLUDED_C_TCPSERVER_H
|
||||||
|
#include <c_TcpServer.h>
|
||||||
|
#endif /*INCLUDED_C_TCPSERVER_H*/
|
||||||
|
|
||||||
|
|
||||||
|
#if defined(__linux__)
|
||||||
|
c_err_t c_TcpEPollReactor_Create(c_TcpReactor_t** out_reactor, c_Allocator_t* allocator);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /*INCLUDED_C_TCPEPOLLREACTOR_H*/
|
||||||
@@ -85,12 +85,12 @@ struct c_TcpReactor_t {
|
|||||||
|
|
||||||
struct c_TcpServer_t {
|
struct c_TcpServer_t {
|
||||||
c_socket_t listen_sock; /* Master server listening socket descriptor */
|
c_socket_t listen_sock; /* Master server listening socket descriptor */
|
||||||
|
c_bool_t is_running; /* Server master thread loop state flag */
|
||||||
c_TcpReactor_t* reactor; /* Polymorphic multiplexing backend driver link */
|
c_TcpReactor_t* reactor; /* Polymorphic multiplexing backend driver link */
|
||||||
c_TcpServer_OnConnect_t fnOnConnect; /* Connection accept callback hook */
|
c_TcpServer_OnConnect_t fnOnConnect; /* Connection accept callback hook */
|
||||||
c_TcpServer_OnRequest_t fnOnRequest; /* Data incoming available callback hook */
|
c_TcpServer_OnRequest_t fnOnRequest; /* Data incoming available callback hook */
|
||||||
c_TcpServer_OnDisconnect_t fnOnDisconnect; /* Disconnect termination event hook */
|
c_TcpServer_OnDisconnect_t fnOnDisconnect; /* Disconnect termination event hook */
|
||||||
c_TcpServer_OnError_t fnOnError; /* Network operational error event hook */
|
c_TcpServer_OnError_t fnOnError; /* Network operational error event hook */
|
||||||
c_bool_t is_running; /* Server master thread loop state flag */
|
|
||||||
c_Allocator_t allocator; /* Deep copy of the user-provided allocator */
|
c_Allocator_t allocator; /* Deep copy of the user-provided allocator */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user