TcpServer/TcpReactor Framework
This commit is contained in:
@@ -49,6 +49,11 @@
|
|||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#endif /*INCLUDED_CTYPE_H*/
|
#endif /*INCLUDED_CTYPE_H*/
|
||||||
|
|
||||||
|
#ifndef INCLUDED_ASSERT_H
|
||||||
|
#define INCLUDED_ASSERT_H
|
||||||
|
#include <assert.h>
|
||||||
|
#endif /*INCLUDED_ASSERT_H*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||||
@@ -113,6 +118,7 @@ typedef int c_err_t;
|
|||||||
#define C_ERR_EMPTY (-5)
|
#define C_ERR_EMPTY (-5)
|
||||||
#define C_ERR_FULL (-6)
|
#define C_ERR_FULL (-6)
|
||||||
#define C_ERR_EXIST (-7)
|
#define C_ERR_EXIST (-7)
|
||||||
|
#define C_ERR_CLOSED (-8)
|
||||||
|
|
||||||
#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
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
#include <c_AddrInfo.h>
|
||||||
|
|
||||||
|
c_err_t c_AddrInfo_Init(c_AddrInfo_t* self,
|
||||||
|
int family,
|
||||||
|
int socktype,
|
||||||
|
int flags,
|
||||||
|
const char* server, const char* port) {
|
||||||
|
|
||||||
|
struct addrinfo hints={0};
|
||||||
|
hints.ai_family = family;
|
||||||
|
hints.ai_socktype = socktype;
|
||||||
|
hints.ai_flags = flags;
|
||||||
|
|
||||||
|
int err = getaddrinfo(server, port, &hints, &self->addrinfo_p);
|
||||||
|
if (err != 0) {
|
||||||
|
self->addrinfo_p = NULL;
|
||||||
|
return C_ERR_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return C_ERR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void c_AddrInfo_Destroy(c_AddrInfo_t* self) {
|
||||||
|
if (!self || !self->addrinfo_p) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
freeaddrinfo(self->addrinfo_p);
|
||||||
|
self->addrinfo_p = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
#ifndef INCLUDED_C_ADDRINFO_H
|
||||||
|
#define INCLUDED_C_ADDRINFO_H
|
||||||
|
|
||||||
|
#ifndef INCLUDED_C_SOCKET_H
|
||||||
|
#include <c_Socket.h>
|
||||||
|
#endif /*INCLUDED_C_SOCKET_H*/
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||||
|
/* */
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
struct addrinfo* addrinfo_p;
|
||||||
|
}c_AddrInfo_t;
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||||
|
/* */
|
||||||
|
|
||||||
|
c_err_t c_AddrInfo_Init(c_AddrInfo_t* self,
|
||||||
|
int family,
|
||||||
|
int socktype,
|
||||||
|
int flags,
|
||||||
|
const char* server, const char* port);
|
||||||
|
|
||||||
|
void c_AddrInfo_Destroy(c_AddrInfo_t* self);
|
||||||
|
|
||||||
|
C_STATIC_FORCE_INLINE
|
||||||
|
c_err_t c_AddrInfo_GetNext(c_AddrInfo_t* self, c_AddrInfo_t* next){
|
||||||
|
if (!self || !self->addrinfo_p) return C_ERR_PARAM;
|
||||||
|
if (next) {
|
||||||
|
next->addrinfo_p = self->addrinfo_p->ai_next;
|
||||||
|
}
|
||||||
|
return C_ERR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
C_STATIC_FORCE_INLINE
|
||||||
|
int c_AddrInfo_GetFlags(c_AddrInfo_t* self) {
|
||||||
|
if (!self || !self->addrinfo_p) return -1;
|
||||||
|
return self->addrinfo_p->ai_flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
C_STATIC_FORCE_INLINE
|
||||||
|
int c_AddrInfo_GetFamily(c_AddrInfo_t* self) {
|
||||||
|
if (!self || !self->addrinfo_p) return -1;
|
||||||
|
return self->addrinfo_p->ai_family;
|
||||||
|
}
|
||||||
|
|
||||||
|
C_STATIC_FORCE_INLINE
|
||||||
|
int c_AddrInfo_GetSockType(c_AddrInfo_t* self) {
|
||||||
|
if (!self || !self->addrinfo_p) return -1;
|
||||||
|
return self->addrinfo_p->ai_socktype;
|
||||||
|
}
|
||||||
|
|
||||||
|
C_STATIC_FORCE_INLINE
|
||||||
|
int c_AddrInfo_GetProtocol(c_AddrInfo_t* self) {
|
||||||
|
if (!self || !self->addrinfo_p) return -1;
|
||||||
|
return self->addrinfo_p->ai_protocol;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /*INCLUDED_C_ADDRINFO_H*/
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#include "c_Test.h"
|
||||||
|
#include "c_AddrInfo.h"
|
||||||
|
|
||||||
|
TEST_CASE(test_addrinfo_lookup_and_getters){
|
||||||
|
c_Socket_Init();
|
||||||
|
|
||||||
|
c_AddrInfo_t info;
|
||||||
|
/* Look up local loopback configuration boundaries over a sample port string */
|
||||||
|
c_err_t err = c_AddrInfo_Init(&info, AF_INET, SOCK_STREAM, AI_PASSIVE, "127.0.0.1", "8080");
|
||||||
|
ASSERT_INT_EQ(C_ERR_OK, err);
|
||||||
|
ASSERT_TRUE(info.addrinfo_p != NULL);
|
||||||
|
|
||||||
|
/* Test getter properties mapping */
|
||||||
|
ASSERT_INT_EQ(AF_INET, c_AddrInfo_GetFamily(&info));
|
||||||
|
ASSERT_INT_EQ(SOCK_STREAM, c_AddrInfo_GetSockType(&info));
|
||||||
|
ASSERT_TRUE(c_AddrInfo_GetFlags(&info) != -1);
|
||||||
|
|
||||||
|
/* Test navigation list iterator lookups */
|
||||||
|
c_AddrInfo_t next_node;
|
||||||
|
err = c_AddrInfo_GetNext(&info, &next_node);
|
||||||
|
ASSERT_INT_EQ(C_ERR_OK, err);
|
||||||
|
|
||||||
|
c_AddrInfo_Destroy(&info);
|
||||||
|
|
||||||
|
c_Socket_Destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
TEST_START(AddrInfo_Resolution_Suite);
|
||||||
|
RUN_TEST(test_addrinfo_lookup_and_getters);
|
||||||
|
TEST_REPORT();
|
||||||
|
RETURN_TEST_STATUS;
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
#include <c_SockAddr.h>
|
||||||
|
|
||||||
|
c_err_t c_SockAddr_Init(c_SockAddr_t* self, int family, const char* ip, uint16_t port) {
|
||||||
|
if (!self ) return C_ERR_PARAM;
|
||||||
|
|
||||||
|
/* Wipe the target memory layout clear up front to prevent uninitialized garbage bits */
|
||||||
|
memset(self, 0, sizeof(c_SockAddr_t));
|
||||||
|
|
||||||
|
/* 1. Parse and initialize as an IPv4 Address */
|
||||||
|
if (family == AF_INET) {
|
||||||
|
self->ipv4.sin_family = AF_INET;
|
||||||
|
self->ipv4.sin_port = htons(port);
|
||||||
|
self->size = sizeof(self->ipv4);
|
||||||
|
if (ip) {
|
||||||
|
if (inet_pton(AF_INET, ip, &(self->ipv4.sin_addr)) == 1) {
|
||||||
|
return C_ERR_OK;
|
||||||
|
}else {
|
||||||
|
return C_ERR_FAIL; /* Invalid IPv4 string format */
|
||||||
|
}
|
||||||
|
}else {
|
||||||
|
self->ipv4.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||||
|
}
|
||||||
|
return C_ERR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2. Parse and initialize as an IPv6 Address */
|
||||||
|
if (family == AF_INET6) {
|
||||||
|
self->ipv6.sin6_family = AF_INET6;
|
||||||
|
self->ipv6.sin6_port = htons(port);
|
||||||
|
self->size = sizeof(self->ipv6);
|
||||||
|
|
||||||
|
if (ip) {
|
||||||
|
if (inet_pton(AF_INET6, ip, &(self->ipv6.sin6_addr)) == 1) {
|
||||||
|
return C_ERR_OK;
|
||||||
|
}else {
|
||||||
|
return C_ERR_FAIL;/* Invalid IPv6 string format */
|
||||||
|
}
|
||||||
|
}else {
|
||||||
|
self->ipv6.sin6_addr = in6addr_any;
|
||||||
|
}
|
||||||
|
|
||||||
|
return C_ERR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3. Return an parameter error if an unsupported address family parameter is requested */
|
||||||
|
return C_ERR_PARAM;
|
||||||
|
}
|
||||||
|
|
||||||
|
c_err_t c_SockAddr_ToString(const c_SockAddr_t* self, char* out_str, c_size_t str_cap) {
|
||||||
|
if (!self || !out_str || str_cap == 0) return C_ERR_PARAM;
|
||||||
|
|
||||||
|
const void* src_addr = NULL;
|
||||||
|
int family = self->ipv4.sin_family;
|
||||||
|
|
||||||
|
if (family == AF_INET) {
|
||||||
|
src_addr = &(self->ipv4.sin_addr);
|
||||||
|
} else if (family == AF_INET6) {
|
||||||
|
src_addr = &(self->ipv6.sin6_addr);
|
||||||
|
} else {
|
||||||
|
return C_ERR_FAIL; /* Unknown address family variant */
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inet_ntop(family, src_addr, out_str, (size_t)str_cap) == NULL) {
|
||||||
|
return C_ERR_OUTOFBOUND; /* String buffer capacity limit was too small */
|
||||||
|
}
|
||||||
|
|
||||||
|
return C_ERR_OK;
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
#ifndef INCLUDED_C_SOCKADDR_H
|
||||||
|
#define INCLUDED_C_SOCKADDR_H
|
||||||
|
|
||||||
|
#ifndef INCLUDED_C_SOCKET_H
|
||||||
|
#include <c_Socket.h>
|
||||||
|
#endif /*INCLUDED_C_SOCKET_H*/
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||||
|
/* */
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
union {
|
||||||
|
struct sockaddr_in ipv4;
|
||||||
|
struct sockaddr_in6 ipv6;
|
||||||
|
struct sockaddr_storage storage;
|
||||||
|
};
|
||||||
|
c_size_t size;
|
||||||
|
}c_SockAddr_t;
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||||
|
/* */
|
||||||
|
|
||||||
|
|
||||||
|
c_err_t c_SockAddr_Init(c_SockAddr_t* self, int family, const char* ip, uint16_t port) ;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the address family (AF_INET or AF_INET6) from the sockaddr variant.
|
||||||
|
* @return The address family integer, or -1 on parameter error.
|
||||||
|
*/
|
||||||
|
C_STATIC_FORCE_INLINE
|
||||||
|
int c_SockAddr_GetFamily(const c_SockAddr_t* self) {
|
||||||
|
if (!self) return -1;
|
||||||
|
/* Since both structure variations map the sa_family / sin_family field
|
||||||
|
at byte offset 0, reading from ipv4 is universally safe across families */
|
||||||
|
return (int)self->ipv4.sin_family;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Safely returns the port number (in host byte order) from the sockaddr variant.
|
||||||
|
* @return The port integer (0 to 65535), or 0 on error.
|
||||||
|
*/
|
||||||
|
C_STATIC_FORCE_INLINE
|
||||||
|
uint16_t c_SockAddr_GetPort(const c_SockAddr_t* self) {
|
||||||
|
if (!self) return 0;
|
||||||
|
if (self->ipv4.sin_family == AF_INET) {
|
||||||
|
return ntohs(self->ipv4.sin_port);
|
||||||
|
} else if (self->ipv4.sin_family == AF_INET6) {
|
||||||
|
return ntohs(self->ipv6.sin6_port);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Converts the embedded binary IP address to a safe human-readable text string.
|
||||||
|
* @param out_str Pre-allocated destination character string buffer.
|
||||||
|
* @param str_cap Capacity ceiling limit of out_str (Recommended min size: INET6_ADDRSTRLEN).
|
||||||
|
*/
|
||||||
|
c_err_t c_SockAddr_ToString(const c_SockAddr_t* self, char* out_str, c_size_t str_cap);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif /*INCLUDED_C_SOCKADDR_H*/
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
#include "c_Test.h"
|
||||||
|
#include "c_SockAddr.h"
|
||||||
|
#include "c_SockAddrUtil.h"
|
||||||
|
|
||||||
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
|
#pragma comment(lib, "ws2_32.lib")
|
||||||
|
#endif
|
||||||
|
|
||||||
|
TEST_CASE(test_sockaddr_variant_extraction_and_string) {
|
||||||
|
c_Socket_Init();
|
||||||
|
|
||||||
|
c_AddrInfo_t addr;
|
||||||
|
/* Look up explicit local target constraints loopback */
|
||||||
|
c_err_t err = c_AddrInfo_Init(&addr, AF_INET, SOCK_STREAM, 0, "127.0.0.1", "9090");
|
||||||
|
ASSERT_INT_EQ(C_ERR_OK, err);
|
||||||
|
|
||||||
|
/* 1. Extract raw byte structure layouts into our custom anonymous union container */
|
||||||
|
c_SockAddr_t sock_addr;
|
||||||
|
err = c_SockAddrUtil_FromAddrInfo(&sock_addr, &addr);
|
||||||
|
ASSERT_INT_EQ(C_ERR_OK, err);
|
||||||
|
|
||||||
|
/* 2. Validate getter transformations */
|
||||||
|
ASSERT_INT_EQ(AF_INET, c_SockAddr_GetFamily(&sock_addr));
|
||||||
|
ASSERT_INT_EQ(9090, (int)c_SockAddr_GetPort(&sock_addr));
|
||||||
|
|
||||||
|
/* 3. Re-render structural bits to string format */
|
||||||
|
char ip_str[64];
|
||||||
|
err = c_SockAddr_ToString(&sock_addr, ip_str, sizeof(ip_str));
|
||||||
|
ASSERT_INT_EQ(C_ERR_OK, err);
|
||||||
|
ASSERT_INT_EQ(0, strcmp("127.0.0.1", ip_str));
|
||||||
|
|
||||||
|
c_AddrInfo_Destroy(&addr);
|
||||||
|
|
||||||
|
c_Socket_Destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(test_sockaddr_explicit_family_initialization) {
|
||||||
|
c_Socket_Init();
|
||||||
|
|
||||||
|
c_SockAddr_t addr;
|
||||||
|
c_err_t err;
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------------------------------------------- */
|
||||||
|
/* Case A: Verify explicit IPv4 initialization and dynamic getters */
|
||||||
|
err = c_SockAddr_Init(&addr, AF_INET, "127.0.0.1", 8080);
|
||||||
|
ASSERT_INT_EQ(C_ERR_OK, err);
|
||||||
|
ASSERT_INT_EQ(AF_INET, c_SockAddr_GetFamily(&addr));
|
||||||
|
ASSERT_INT_EQ(8080, (int)c_SockAddr_GetPort(&addr));
|
||||||
|
|
||||||
|
char out_ip_v4[INET_ADDRSTRLEN];
|
||||||
|
err = c_SockAddr_ToString(&addr, out_ip_v4, sizeof(out_ip_v4));
|
||||||
|
ASSERT_INT_EQ(C_ERR_OK, err);
|
||||||
|
ASSERT_INT_EQ(0, strcmp("127.0.0.1", out_ip_v4));
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------------------------------------------- */
|
||||||
|
/* Case B: Verify explicit IPv6 initialization and loopback string decoding */
|
||||||
|
err = c_SockAddr_Init(&addr, AF_INET6, "::1", 9090);
|
||||||
|
ASSERT_INT_EQ(C_ERR_OK, err);
|
||||||
|
ASSERT_INT_EQ(AF_INET6, c_SockAddr_GetFamily(&addr));
|
||||||
|
ASSERT_INT_EQ(9090, (int)c_SockAddr_GetPort(&addr));
|
||||||
|
|
||||||
|
char out_ip_v6[INET6_ADDRSTRLEN];
|
||||||
|
err = c_SockAddr_ToString(&addr, out_ip_v6, sizeof(out_ip_v6));
|
||||||
|
ASSERT_INT_EQ(C_ERR_OK, err);
|
||||||
|
ASSERT_INT_EQ(0, strcmp("::1", out_ip_v6));
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------------------------------------------- */
|
||||||
|
/* Case C: Verify that incorrect cross-family inputs gracefully yield execution failures */
|
||||||
|
err = c_SockAddr_Init(&addr, AF_INET, "::1", 80); /* Passing IPv6 loopback string to IPv4 family parser */
|
||||||
|
ASSERT_INT_EQ(C_ERR_FAIL, err);
|
||||||
|
|
||||||
|
c_Socket_Destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
TEST_START(SockAddr_Variant_Suite);
|
||||||
|
RUN_TEST(test_sockaddr_variant_extraction_and_string);
|
||||||
|
RUN_TEST(test_sockaddr_explicit_family_initialization);
|
||||||
|
TEST_REPORT();
|
||||||
|
RETURN_TEST_STATUS;
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
#include <c_SockAddrUtil.h>
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
#ifndef INCLUDED_C_SOCKADDRUTIL_H
|
||||||
|
#define INCLUDED_C_SOCKADDRUTIL_H
|
||||||
|
|
||||||
|
#ifndef INCLUDED_C_SOCKADDR_H
|
||||||
|
#include <c_SockAddr.h>
|
||||||
|
#endif /*INCLUDED_C_SOCKADDR_H*/
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef INCLUDED_C_ADDRINFO_H
|
||||||
|
#include <c_AddrInfo.h>
|
||||||
|
#endif /*INCLUDED_C_ADDRINFO_H*/
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||||
|
/* */
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Extends extraction mapping from a resolved c_AddrInfo_t instance.
|
||||||
|
*/
|
||||||
|
C_STATIC_FORCE_INLINE c_err_t c_SockAddrUtil_FromAddrInfo(c_SockAddr_t* sock_addr, const c_AddrInfo_t* addr) {
|
||||||
|
if (!sock_addr || !addr || !addr->addrinfo_p) return C_ERR_PARAM;
|
||||||
|
|
||||||
|
size_t len = addr->addrinfo_p->ai_addrlen;
|
||||||
|
if (addr->addrinfo_p->ai_family == AF_INET && len <= sizeof(struct sockaddr_in)) {
|
||||||
|
memcpy(&sock_addr->ipv4, addr->addrinfo_p->ai_addr, len);
|
||||||
|
return C_ERR_OK;
|
||||||
|
} else if (addr->addrinfo_p->ai_family == AF_INET6 && len <= sizeof(struct sockaddr_in6)) {
|
||||||
|
memcpy(&sock_addr->ipv6, addr->addrinfo_p->ai_addr, len);
|
||||||
|
return C_ERR_OK;
|
||||||
|
}
|
||||||
|
return C_ERR_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /*INCLUDED_C_SOCKADDRUTIL_H*/
|
||||||
+122
-19
@@ -5,36 +5,139 @@
|
|||||||
#include <c_Types.h>
|
#include <c_Types.h>
|
||||||
#endif /*INCLUDED_C_TYPES_H*/
|
#endif /*INCLUDED_C_TYPES_H*/
|
||||||
|
|
||||||
|
#ifndef INCLUDED_STDIO_H
|
||||||
|
#define INCLUDED_STDIO_H
|
||||||
|
#include <stdio.h>
|
||||||
|
#endif /*INCLUDED_STDIO_H*/
|
||||||
|
|
||||||
|
#ifndef INCLUDED_STDLIB_H
|
||||||
|
#define INCLUDED_STDLIB_H
|
||||||
|
#include <stdlib.h>
|
||||||
|
#endif /*INCLUDED_STDLIB_H*/
|
||||||
|
|
||||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||||
/* */
|
/* */
|
||||||
|
|
||||||
|
|
||||||
#if defined(_WIN32) || defined(_WIN64)
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
#include <winsock2.h>
|
|
||||||
typedef SOCKET c_socket_t;
|
|
||||||
#define C_INVALID_SOCKET INVALID_SOCKET
|
|
||||||
#define C_SOCKET_ERROR SOCKET_ERROR
|
|
||||||
#define c_Socket_Close(x) closesocket(x)
|
|
||||||
|
|
||||||
C_STATIC_FORCE_INLINE
|
#ifndef _WIN32_WINNT
|
||||||
void c_Socket_Init(void) {
|
#define _WIN32_WINNT 0x0600
|
||||||
WSADATA wsa; WSAStartup(MAKEWORD(2,2), &wsa);
|
#endif
|
||||||
}
|
|
||||||
|
|
||||||
C_STATIC_FORCE_INLINE
|
#ifndef INCLUDED_WINSOCK2_H
|
||||||
void c_Socket_Destroy(void) {
|
#define INCLUDED_WINSOCK2_H
|
||||||
|
#include <winsock2.h>
|
||||||
|
#endif /*INCLUDED_WINSOCK2_H*/
|
||||||
|
|
||||||
|
#ifndef INCLUDED_IPHLPAPI_H
|
||||||
|
#define INCLUDED_IPHLPAPI_H
|
||||||
|
#include <iphlpapi.h>
|
||||||
|
#endif /*INCLUDED_IPHLPAPI_H*/
|
||||||
|
|
||||||
|
#ifndef INCLUDED_WS2TCPIP_H
|
||||||
|
#define INCLUDED_WS2TCPIP_H
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
#endif /*INCLUDED_WS2TCPIP_H*/
|
||||||
|
|
||||||
|
|
||||||
|
typedef SOCKET c_socket_t;
|
||||||
|
|
||||||
|
#define C_INVALID_SOCKET INVALID_SOCKET
|
||||||
|
#define C_SOCKET_ERROR SOCKET_ERROR
|
||||||
|
|
||||||
|
#define c_Socket_Close(x) (closesocket(x), (x)=C_INVALID_SOCKET)
|
||||||
|
#define c_Socket_IsValid(s) ((s)!=INVALID_SOCKET)
|
||||||
|
#define c_Socket_GetErrno() (WSAGetLastError())
|
||||||
|
|
||||||
|
C_STATIC_FORCE_INLINE
|
||||||
|
void c_Socket_Init(void) {
|
||||||
|
WSADATA wsa;
|
||||||
|
int err = WSAStartup(MAKEWORD(2,2), &wsa);
|
||||||
|
assert(err==0);
|
||||||
|
}
|
||||||
|
|
||||||
|
C_STATIC_FORCE_INLINE
|
||||||
|
void c_Socket_Destroy(void) {
|
||||||
WSACleanup();
|
WSACleanup();
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
typedef int c_socket_t;
|
#else /* other platform */
|
||||||
#define C_INVALID_SOCKET (-1)
|
|
||||||
#define C_SOCKET_ERROR (-1)
|
#ifndef INCLUDED_SYS_TYPES_H
|
||||||
#define c_Socket_Close(x) close(x)
|
#define INCLUDED_SYS_TYPES_H
|
||||||
#define c_Socket_Init()
|
#include <sys/types.h>
|
||||||
#define c_Socket_Destroy()
|
#endif /*INCLUDED_SYS_TYPES_H*/
|
||||||
|
|
||||||
|
#ifndef INCLUDED_SYS_SOCKET_H
|
||||||
|
#define INCLUDED_SYS_SOCKET_H
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#endif /*INCLUDED_SYS_SOCKET_H*/
|
||||||
|
|
||||||
|
#ifndef INCLUDED_NETDB_H
|
||||||
|
#define INCLUDED_NETDB_H
|
||||||
|
#include <netdb.h>
|
||||||
|
#endif /*INCLUDED_NETDB_H*/
|
||||||
|
|
||||||
|
#ifndef INCLUDED_NETINET_IN_H
|
||||||
|
#define INCLUDED_NETINET_IN_H
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#endif /*INCLUDED_NETINET_IN_H*/
|
||||||
|
|
||||||
|
#ifndef INCLUDED_ARPA_INET_H
|
||||||
|
#define INCLUDED_ARPA_INET_H
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#endif /*INCLUDED_ARPA_INET_H*/
|
||||||
|
|
||||||
|
#ifndef INCLUDED_UNISTD_H
|
||||||
|
#define INCLUDED_UNISTD_H
|
||||||
|
#include <unistd.h>
|
||||||
|
#endif /*INCLUDED_UNISTD_H*/
|
||||||
|
|
||||||
|
#ifndef INCLUDED_ERRNO_H
|
||||||
|
#define INCLUDED_ERRNO_H
|
||||||
|
#include <errno.h>
|
||||||
|
#endif /*INCLUDED_ERRNO_H*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
typedef int c_socket_t;
|
||||||
|
|
||||||
|
#define C_INVALID_SOCKET (-1)
|
||||||
|
#define C_SOCKET_ERROR (-1)
|
||||||
|
|
||||||
|
#define c_Socket_IsValid(s) ((s)>=0)
|
||||||
|
#define c_Socket_Close(x) (close(x), (x)=C_INVALID_SOCKET)
|
||||||
|
#define c_Socket_GetErrno() (errno)
|
||||||
|
#define c_Socket_Init()
|
||||||
|
#define c_Socket_Destroy()
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||||
|
/* */
|
||||||
|
|
||||||
|
C_STATIC_FORCE_INLINE
|
||||||
|
void c_Socket_SetReuseAddr(c_socket_t s, c_bool_t is_reuse) {
|
||||||
|
int reuse = is_reuse?1:0;
|
||||||
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
|
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse));
|
||||||
|
#else
|
||||||
|
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
C_STATIC_FORCE_INLINE
|
||||||
|
void c_Socket_SetIPV6Only(c_socket_t s, c_bool_t is_IPV6only) {
|
||||||
|
int ipv6only = is_IPV6only?1:0;
|
||||||
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
|
setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, (const char*)&ipv6only, sizeof(ipv6only));
|
||||||
|
#else
|
||||||
|
setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6only, sizeof(ipv6only));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#endif /*INCLUDED_C_SOCKET_H*/
|
#endif /*INCLUDED_C_SOCKET_H*/
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
#include <c_SocketUtil.h>
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
#ifndef INCLUDED_C_SOCKETUTIL_H
|
||||||
|
#define INCLUDED_C_SOCKETUTIL_H
|
||||||
|
|
||||||
|
#ifndef INCLUDED_C_SOCKET_H
|
||||||
|
#include <c_Socket.h>
|
||||||
|
#endif /*INCLUDED_C_SOCKET_H*/
|
||||||
|
|
||||||
|
#ifndef INCLUDED_C_ADDRINFO_H
|
||||||
|
#include <c_AddrInfo.h>
|
||||||
|
#endif /*INCLUDED_C_ADDRINFO_H*/
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef INCLUDED_C_SOCKADDR_H
|
||||||
|
#include <c_SockAddr.h>
|
||||||
|
#endif /*INCLUDED_C_SOCKADDR_H*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||||
|
/* */
|
||||||
|
|
||||||
|
C_STATIC_FORCE_INLINE
|
||||||
|
c_socket_t c_SocketUtil_CreateFromAddrInfo(c_AddrInfo_t* addr_info) {
|
||||||
|
if (!addr_info) return C_INVALID_SOCKET;
|
||||||
|
return socket(c_AddrInfo_GetFamily(addr_info),
|
||||||
|
c_AddrInfo_GetSockType(addr_info),
|
||||||
|
c_AddrInfo_GetProtocol(addr_info));
|
||||||
|
}
|
||||||
|
|
||||||
|
C_STATIC_FORCE_INLINE
|
||||||
|
c_err_t c_SocketUtil_BindWithAddrInfo(c_socket_t s, c_AddrInfo_t* addr_info) {
|
||||||
|
if (!c_Socket_IsValid(s) || !addr_info) return C_ERR_PARAM;
|
||||||
|
if (bind(s, addr_info->addrinfo_p->ai_addr, (int)addr_info->addrinfo_p->ai_addrlen) < 0) {
|
||||||
|
return C_ERR_FAIL;
|
||||||
|
}
|
||||||
|
return C_ERR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
C_STATIC_FORCE_INLINE
|
||||||
|
c_err_t c_SocketUtil_BindWithSockAddr(c_socket_t s, c_SockAddr_t* sock_addr) {
|
||||||
|
if (!c_Socket_IsValid(s) || !sock_addr) return C_ERR_PARAM;
|
||||||
|
if (bind(s, (struct sockaddr*)&sock_addr->storage, (socklen_t)sock_addr->size) < 0) {
|
||||||
|
return C_ERR_FAIL;
|
||||||
|
}
|
||||||
|
return C_ERR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
C_STATIC_FORCE_INLINE
|
||||||
|
c_socket_t c_SocketUtil_Accept(c_socket_t s, c_SockAddr_t* sock_addr) {
|
||||||
|
if (!c_Socket_IsValid(s) || !sock_addr) return C_INVALID_SOCKET;
|
||||||
|
memset(sock_addr, 0, sizeof(c_SockAddr_t));
|
||||||
|
socklen_t client_len = sizeof(sock_addr->storage);
|
||||||
|
c_socket_t socket_client = accept(s, (struct sockaddr*) &sock_addr->storage, &client_len);
|
||||||
|
if (!c_Socket_IsValid(socket_client)) return C_INVALID_SOCKET;
|
||||||
|
sock_addr->size = client_len;
|
||||||
|
return socket_client;
|
||||||
|
}
|
||||||
|
|
||||||
|
C_STATIC_FORCE_INLINE
|
||||||
|
c_err_t c_SocketUtil_GetNameInfoForSockAddr(c_SockAddr_t* sock_addr, char* host, c_size_t host_len, char* port, c_size_t port_len) {
|
||||||
|
if (!sock_addr) return C_ERR_PARAM;
|
||||||
|
|
||||||
|
if ((host && host_len > 0) && (!port || port_len==0)) {
|
||||||
|
int err = getnameinfo((struct sockaddr*)&sock_addr->storage, (socklen_t)sock_addr->size,
|
||||||
|
host, host_len, NULL, 0, NI_NUMERICHOST);
|
||||||
|
if (err!=0) {
|
||||||
|
return C_ERR_FAIL;
|
||||||
|
}
|
||||||
|
}else if ((!host || host_len == 0) && (port && port_len >0)) {
|
||||||
|
int err = getnameinfo((struct sockaddr*)&sock_addr->storage, (socklen_t)sock_addr->size
|
||||||
|
, NULL, 0,
|
||||||
|
port, port_len,
|
||||||
|
NI_NUMERICSERV);
|
||||||
|
if (err!=0) {
|
||||||
|
return C_ERR_FAIL;
|
||||||
|
}
|
||||||
|
}else {
|
||||||
|
int err = getnameinfo((struct sockaddr*)&sock_addr->storage, (socklen_t)sock_addr->size
|
||||||
|
, host, host_len
|
||||||
|
, port, port_len
|
||||||
|
, NI_NUMERICHOST|NI_NUMERICSERV);
|
||||||
|
if (err!=0) {
|
||||||
|
return C_ERR_FAIL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return C_ERR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
C_STATIC_FORCE_INLINE
|
||||||
|
c_err_t c_SocketUtil_Send(c_socket_t s, void* data, size_t data_len, c_size_t* send_size) {
|
||||||
|
if (!c_Socket_IsValid(s) || !data) return C_ERR_PARAM;
|
||||||
|
ssize_t err = send(s, (const char*)data, (int)data_len, 0);
|
||||||
|
if (err==0) return C_ERR_CLOSED;
|
||||||
|
if (err<0) return C_ERR_FAIL;
|
||||||
|
if (send_size) *send_size = err;
|
||||||
|
return C_ERR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
C_STATIC_FORCE_INLINE
|
||||||
|
c_err_t c_SocketUtil_GetNameInfoForSocket(c_socket_t s, char* host, c_size_t host_len
|
||||||
|
, char* port, c_size_t port_len) {
|
||||||
|
if (!c_Socket_IsValid(s) ) return C_ERR_PARAM;
|
||||||
|
|
||||||
|
struct sockaddr_storage peer_addr;
|
||||||
|
socklen_t addr_len = sizeof(peer_addr);
|
||||||
|
|
||||||
|
// 2. 获取对方的套接字地址信息
|
||||||
|
if (getpeername(s, (struct sockaddr*)&peer_addr, &addr_len) == -1) {
|
||||||
|
return C_ERR_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (host && host_len > 0 && (!port || port_len==0)) {
|
||||||
|
int err = getnameinfo((struct sockaddr*)&peer_addr, addr_len,
|
||||||
|
host, host_len, NULL, 0, NI_NUMERICHOST);
|
||||||
|
if (err!=0) {
|
||||||
|
return C_ERR_FAIL;
|
||||||
|
}
|
||||||
|
}else if (port && port_len > 0 && (!host || host_len==0)) {
|
||||||
|
int err = getnameinfo((struct sockaddr*)&peer_addr, addr_len,
|
||||||
|
NULL, 0,
|
||||||
|
port, port_len,
|
||||||
|
NI_NUMERICSERV);
|
||||||
|
if (err!=0) {
|
||||||
|
return C_ERR_FAIL;
|
||||||
|
}
|
||||||
|
}else {
|
||||||
|
int err = getnameinfo((struct sockaddr*)&peer_addr, addr_len,
|
||||||
|
host, host_len,
|
||||||
|
port, port_len,
|
||||||
|
NI_NUMERICHOST|NI_NUMERICSERV);
|
||||||
|
if (err!=0) {
|
||||||
|
return C_ERR_FAIL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return C_ERR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /*INCLUDED_C_SOCKETUTIL_H*/
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
#include "c_Socket.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "c_AddrInfo.h"
|
||||||
|
#include "c_SockAddrUtil.h"
|
||||||
|
#include "c_SocketUtil.h"
|
||||||
|
|
||||||
|
int main(int argc, char** argv){
|
||||||
|
c_Socket_Init();
|
||||||
|
|
||||||
|
printf("Configuring local address...\n");
|
||||||
|
|
||||||
|
c_AddrInfo_t bindAddr;
|
||||||
|
c_AddrInfo_Init(&bindAddr, AF_INET, SOCK_STREAM, AI_PASSIVE, 0, "8080");
|
||||||
|
|
||||||
|
|
||||||
|
printf("Creating socket...\n");
|
||||||
|
c_socket_t socket_listen = c_SocketUtil_CreateFromAddrInfo(&bindAddr);
|
||||||
|
|
||||||
|
if (!c_Socket_IsValid(socket_listen)) {
|
||||||
|
fprintf(stderr, "socket() failed. (%d)\n", c_Socket_GetErrno());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Binding socket to local address...\n");
|
||||||
|
if (c_SocketUtil_BindWithAddrInfo(socket_listen, &bindAddr) !=C_ERR_OK) {
|
||||||
|
fprintf(stderr, "bind() failed. (%d)\n", c_Socket_GetErrno());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
c_AddrInfo_Destroy(&bindAddr);
|
||||||
|
|
||||||
|
|
||||||
|
printf("Listening...\n");
|
||||||
|
if (listen(socket_listen, 10) < 0) {
|
||||||
|
fprintf(stderr, "listen() failed. (%d)\n", c_Socket_GetErrno());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Waiting for connection...\n");
|
||||||
|
c_SockAddr_t client_address;
|
||||||
|
c_socket_t socket_client = c_SocketUtil_Accept(socket_listen, &client_address);
|
||||||
|
if (!c_Socket_IsValid(socket_client)) {
|
||||||
|
fprintf(stderr, "accept() failed. (%d)\n", c_Socket_GetErrno());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Client is connected... ");
|
||||||
|
char address_buffer[100];
|
||||||
|
c_SocketUtil_GetNameInfoForSockAddr(&client_address, address_buffer, sizeof(address_buffer), 0, 0);
|
||||||
|
printf("%s\n", address_buffer);
|
||||||
|
|
||||||
|
printf("Reading request...\n");
|
||||||
|
char request[1024];
|
||||||
|
int bytes_received = recv(socket_client, request, 1024, 0);
|
||||||
|
printf("Received %d bytes.\n", bytes_received);
|
||||||
|
|
||||||
|
printf("Sending response...\n");
|
||||||
|
const char *response =
|
||||||
|
"HTTP/1.1 200 OK\r\n"
|
||||||
|
"Connection: close\r\n"
|
||||||
|
"Content-Type: text/plain\r\n\r\n"
|
||||||
|
"Local time is: ";
|
||||||
|
int bytes_sent = send(socket_client, response, strlen(response), 0);
|
||||||
|
printf("Sent %d of %d bytes.\n", bytes_sent, (int)strlen(response));
|
||||||
|
|
||||||
|
time_t timer;
|
||||||
|
time(&timer);
|
||||||
|
char *time_msg = ctime(&timer);
|
||||||
|
bytes_sent = send(socket_client, time_msg, strlen(time_msg), 0);
|
||||||
|
printf("Sent %d of %d bytes.\n", bytes_sent, (int)strlen(time_msg));
|
||||||
|
|
||||||
|
printf("Closing connection...\n");
|
||||||
|
c_Socket_Close(socket_client);
|
||||||
|
|
||||||
|
|
||||||
|
printf("Closing listening socket...\n");
|
||||||
|
c_Socket_Close(socket_listen);
|
||||||
|
|
||||||
|
c_Socket_Destroy();
|
||||||
|
|
||||||
|
printf("Finished.\n");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
#include <c_TcpPollReactor.h>
|
||||||
|
|
||||||
|
#if !defined(_WIN32) && !defined(_WIN64)
|
||||||
|
#include <poll.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
c_TcpReactor_t base;
|
||||||
|
struct pollfd* fds;
|
||||||
|
c_size_t count;
|
||||||
|
c_size_t capacity;
|
||||||
|
c_Allocator_t allocator;
|
||||||
|
} c_PollReactorImpl_t;
|
||||||
|
|
||||||
|
/* (Adheres to the O(1) packing allocations logic established previously) */
|
||||||
|
static c_err_t Poll_Add(c_TcpReactor_t* self, c_socket_t sock, uint32_t events) {
|
||||||
|
c_PollReactorImpl_t* impl = (c_PollReactorImpl_t*)self;
|
||||||
|
if (impl->count >= impl->capacity) {
|
||||||
|
c_size_t old_cap = impl->capacity;
|
||||||
|
c_size_t new_cap = old_cap == 0 ? 16 : 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 Poll_Remove(c_TcpReactor_t* self, c_socket_t sock) {
|
||||||
|
c_PollReactorImpl_t* impl = (c_PollReactorImpl_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 Poll_Poll(c_TcpReactor_t* self, long timeout_ms, c_TcpServer_t* server, void* args) {
|
||||||
|
c_PollReactorImpl_t* impl = (c_PollReactorImpl_t*)self;
|
||||||
|
if (impl->count == 0 || !server->is_running) return C_SUCCESS;
|
||||||
|
|
||||||
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
|
int ret = WSAPoll(impl->fds, (ULONG)impl->count, timeout_ms);
|
||||||
|
#else
|
||||||
|
int ret = poll(impl->fds, (nfds_t)impl->count, timeout_ms);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (ret < 0) {
|
||||||
|
if (server->fnOnError) {
|
||||||
|
server->fnOnError(server, server->listen_sock, C_TCPSERVER_ERR_ON_POLL, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ret == 0) return C_SUCCESS;
|
||||||
|
|
||||||
|
for (c_size_t i = impl->count; i > 0; --i) {
|
||||||
|
c_size_t idx = i - 1;
|
||||||
|
struct pollfd* current_fd = &impl->fds[idx];
|
||||||
|
|
||||||
|
if (current_fd->revents == 0) continue;
|
||||||
|
|
||||||
|
if (current_fd->fd == (int)server->listen_sock) {
|
||||||
|
if (current_fd->revents & POLLIN) {
|
||||||
|
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) {
|
||||||
|
Poll_Add(self, client, 1);
|
||||||
|
} else {
|
||||||
|
c_Socket_Close(client);
|
||||||
|
}
|
||||||
|
}else if (server->fnOnError) {
|
||||||
|
server->fnOnError(server, server->listen_sock, C_TCPSERVER_ERR_ON_ACCEPT, args);
|
||||||
|
}
|
||||||
|
}else if (current_fd->revents & (POLLERR | POLLHUP)) {
|
||||||
|
if (server->fnOnError) {
|
||||||
|
server->fnOnError(server, server->listen_sock, C_TCPSERVER_ERR_ON_POLLERR, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret--;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
c_socket_t client_sock = (c_socket_t)current_fd->fd;
|
||||||
|
|
||||||
|
if (current_fd->revents & (POLLIN | POLLHUP | POLLERR)) {
|
||||||
|
c_bool_t keep_alive = C_TRUE;
|
||||||
|
|
||||||
|
/* Trigger error logging if a structural socket error bit is present */
|
||||||
|
if (current_fd->revents & POLLERR) {
|
||||||
|
if (server->fnOnError) {
|
||||||
|
server->fnOnError(server, client_sock, C_TCPSERVER_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 (server->fnOnRequest) {
|
||||||
|
keep_alive = server->fnOnRequest(server, client_sock, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!keep_alive) {
|
||||||
|
Poll_Remove(self, client_sock);
|
||||||
|
|
||||||
|
/* Fire the explicitly realigned c_TcpServer_OnDisconnect_t call hook */
|
||||||
|
if (server->fnOnDisconnect) {
|
||||||
|
server->fnOnDisconnect(server, client_sock, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
c_Socket_Close(client_sock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret == 0) break;
|
||||||
|
}
|
||||||
|
return C_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (The rest of the driver functions stay identically compiled) */
|
||||||
|
static void Poll_Destroy(c_TcpReactor_t* self) {
|
||||||
|
c_PollReactorImpl_t* impl = (c_PollReactorImpl_t*)self;
|
||||||
|
c_Allocator_t alloc = impl->allocator;
|
||||||
|
for (c_size_t i = 0; i < impl->count; ++i) {
|
||||||
|
c_socket_t sock = (c_socket_t)impl->fds[i].fd;
|
||||||
|
c_Socket_Close(sock);
|
||||||
|
}
|
||||||
|
if (impl->fds) c_Allocator_Free(&alloc, impl->fds);
|
||||||
|
c_Allocator_Free(&alloc, impl);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const c_TcpReactorVtbl_t g_PollReactorVtbl = { Poll_Add, Poll_Remove, Poll_Poll, Poll_Destroy };
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||||
|
/* */
|
||||||
|
|
||||||
|
c_err_t c_TcpPollReactor_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_PollReactorImpl_t* impl = (c_PollReactorImpl_t*)c_Allocator_Calloc(&alloc, 1, sizeof(c_PollReactorImpl_t));
|
||||||
|
if (!impl) return C_ERR_NOMEM;
|
||||||
|
impl->base.vtbl = &g_PollReactorVtbl;
|
||||||
|
impl->allocator = alloc;
|
||||||
|
*out_reactor = &impl->base;
|
||||||
|
return C_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
#ifndef INCLUDED_C_TCPPOLLREACTOR_H
|
||||||
|
#define INCLUDED_C_TCPPOLLREACTOR_H
|
||||||
|
|
||||||
|
#ifndef INCLUDED_C_TCPSERVER_H
|
||||||
|
#include <c_TcpServer.h>
|
||||||
|
#endif /*INCLUDED_C_TCPSERVER_H*/
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||||
|
/* */
|
||||||
|
|
||||||
|
c_err_t c_TcpPollReactor_Create(c_TcpReactor_t** out_reactor, c_Allocator_t* allocator);
|
||||||
|
|
||||||
|
#endif /*INCLUDED_C_TCPPOLLREACTOR_H*/
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
#include "c_TcpPollReactor.h"
|
||||||
|
#include "c_Test.h"
|
||||||
|
#include "c_TcpServer.h"
|
||||||
|
|
||||||
|
|
||||||
|
static int g_tracking_counter = 0;
|
||||||
|
|
||||||
|
static bool OnConnectHandler(c_socket_t s, const c_SockAddr_t* addr, void* args) {
|
||||||
|
(void)addr; (void)args;
|
||||||
|
g_tracking_counter = 10;
|
||||||
|
|
||||||
|
char ip_buffer[NI_MAXHOST];
|
||||||
|
char port_buffer[NI_MAXSERV];
|
||||||
|
|
||||||
|
// 3. 将二进制地址转换为明文的 IP 和 端口字符串
|
||||||
|
// NI_NUMERICHOST: 以数字形式返回 IP
|
||||||
|
// NI_NUMERICSERV: 以数字形式返回端口号(而不是服务名如 http/ftp)
|
||||||
|
c_err_t err = c_SocketUtil_GetNameInfoForSocket(s, ip_buffer, NI_MAXHOST, port_buffer, NI_MAXSERV);
|
||||||
|
|
||||||
|
if (err == 0) {
|
||||||
|
printf("Connect from IP:PORT %s:%s\n", ip_buffer, port_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool OnDataHandler(c_socket_t s, void* args) {
|
||||||
|
(void)s; (void)args;
|
||||||
|
|
||||||
|
char ip_buffer[NI_MAXHOST];
|
||||||
|
char port_buffer[NI_MAXSERV];
|
||||||
|
|
||||||
|
// 3. 将二进制地址转换为明文的 IP 和 端口字符串
|
||||||
|
// NI_NUMERICHOST: 以数字形式返回 IP
|
||||||
|
// NI_NUMERICSERV: 以数字形式返回端口号(而不是服务名如 http/ftp)
|
||||||
|
c_err_t err = c_SocketUtil_GetNameInfoForSocket(s, ip_buffer, NI_MAXHOST, port_buffer, NI_MAXSERV);
|
||||||
|
|
||||||
|
if (err == 0) {
|
||||||
|
printf("Receive data from IP:PORT %s:%s\n", ip_buffer, port_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false; /* Instantly signal termination to verify disconnect loops */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Disconnect handler callback matching your exact specification naming format */
|
||||||
|
static void OnDisconnectHandler(c_socket_t s, void* args) {
|
||||||
|
(void)s; (void)args;
|
||||||
|
g_tracking_counter = 0; /* Clear metrics register */
|
||||||
|
|
||||||
|
char ip_buffer[NI_MAXHOST];
|
||||||
|
char port_buffer[NI_MAXSERV];
|
||||||
|
|
||||||
|
// 3. 将二进制地址转换为明文的 IP 和 端口字符串
|
||||||
|
// NI_NUMERICHOST: 以数字形式返回 IP
|
||||||
|
// NI_NUMERICSERV: 以数字形式返回端口号(而不是服务名如 http/ftp)
|
||||||
|
c_err_t err = c_SocketUtil_GetNameInfoForSocket(s, ip_buffer, NI_MAXHOST, port_buffer, NI_MAXSERV);
|
||||||
|
|
||||||
|
if (err == 0) {
|
||||||
|
printf("Client Closed IP:PORT %s:%s\n", ip_buffer, port_buffer);
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "getnameinfo 失败: %s\n", gai_strerror(c_Socket_GetErrno()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(test_multiplex_reactor_server_polling) {
|
||||||
|
c_Socket_Init();
|
||||||
|
|
||||||
|
c_TcpServer_t server;
|
||||||
|
c_err_t err = c_TcpServer_Init(&server, AF_INET, 8888, 512, NULL);
|
||||||
|
ASSERT_INT_EQ(C_SUCCESS, err);
|
||||||
|
|
||||||
|
/* Hook callbacks using the realigned layout names */
|
||||||
|
c_TcpServer_SetCallbacks(&server, OnConnectHandler, OnDataHandler, OnDisconnectHandler);
|
||||||
|
|
||||||
|
|
||||||
|
/* 1. Instantiate the poll driver subclass instance */
|
||||||
|
c_TcpReactor_t* poll_backend = NULL;
|
||||||
|
err = c_TcpPollReactor_Create(&poll_backend, &server.allocator);
|
||||||
|
ASSERT_INT_EQ(C_SUCCESS, err);
|
||||||
|
|
||||||
|
/* 2. Bind the engine onto the polymorphic execution loop layer */
|
||||||
|
err = c_TcpServer_SetReactor(&server, poll_backend);
|
||||||
|
ASSERT_INT_EQ(C_SUCCESS, err);
|
||||||
|
|
||||||
|
/* 3. Execute a non-blocking test loop pass with a quick 50ms timeout threshold */
|
||||||
|
while (server.is_running) {
|
||||||
|
err = c_TcpServer_Run(&server, 60000, NULL);
|
||||||
|
ASSERT_INT_EQ(C_SUCCESS, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
c_TcpServer_Destroy(&server);
|
||||||
|
c_Socket_Destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
TEST_START(TcpServer_MultiplexReactor_Suite);
|
||||||
|
RUN_TEST(test_multiplex_reactor_server_polling);
|
||||||
|
TEST_REPORT();
|
||||||
|
RETURN_TEST_STATUS;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "c_TcpServer.h"
|
||||||
|
#include "c_SocketStream.h"
|
||||||
|
#include "c_SockAddr.h"
|
||||||
|
#include "c_TcpPollReactor.h"
|
||||||
|
|
||||||
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
|
#pragma comment(lib, "ws2_32.lib")
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reactor connection event callback.
|
||||||
|
* Fired automatically by c_TcpPollReactor whenever an inbound client connects.
|
||||||
|
*/
|
||||||
|
static bool OnPollTimeServerConnect(c_TcpServer_t* server, c_socket_t client_sock, const c_SockAddr_t* peer_addr, void* args) {
|
||||||
|
(void)args;
|
||||||
|
char ip_str[INET6_ADDRSTRLEN] = {0};
|
||||||
|
uint16_t port = c_SockAddr_GetPort(peer_addr);
|
||||||
|
|
||||||
|
if (c_SockAddr_ToString(peer_addr, ip_str, sizeof(ip_str)) == C_ERR_OK) {
|
||||||
|
printf("[POLL INFO] Accepted client connection from: %s:%d\n", ip_str, (int)port);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool OnDataRequest(c_TcpServer_t* server, c_socket_t s , void* args) {
|
||||||
|
(void)args;
|
||||||
|
char host[32]={0};
|
||||||
|
char port[9]={0};
|
||||||
|
|
||||||
|
c_SocketUtil_GetNameInfoForSocket(s, host, sizeof(host), port, sizeof(port));
|
||||||
|
printf("Request from %s:%s\n", host, port);
|
||||||
|
|
||||||
|
char buffer[1024] = {0};
|
||||||
|
int err = recv(s, buffer, sizeof(buffer), 0);
|
||||||
|
|
||||||
|
printf("body: %.*s\n", err, buffer);
|
||||||
|
/* 1. Wrap raw client socket inside polymorphic Output Stream pipeline */
|
||||||
|
time_t raw_time = time(NULL);
|
||||||
|
struct tm* time_info = localtime(&raw_time);
|
||||||
|
|
||||||
|
char time_buffer[64] = {0};
|
||||||
|
strftime(time_buffer, sizeof(time_buffer), "%Y-%m-%d %H:%M:%S\n", time_info);
|
||||||
|
|
||||||
|
/* 3. Flush plaintext string down into the polymorphic socket driver channel */
|
||||||
|
c_size_t bytes_written = 0;
|
||||||
|
c_SocketUtil_Send(s, time_buffer, strlen(time_buffer), &bytes_written);
|
||||||
|
|
||||||
|
if (strncmp(buffer, "STOP", 4)==0) {
|
||||||
|
server->is_running = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static void OnClose(c_TcpServer_t* server, c_socket_t s, void* args) {
|
||||||
|
(void)args;
|
||||||
|
char host[32]={0};
|
||||||
|
char port[9]={0};
|
||||||
|
|
||||||
|
c_SocketUtil_GetNameInfoForSocket(s, host, sizeof(host), port, sizeof(port));
|
||||||
|
printf("Client Closed from %s:%s\n", host, port);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void OnError(c_TcpServer_t* server, c_socket_t s, int err, void* args) {
|
||||||
|
(void)args;
|
||||||
|
char host[32]={0};
|
||||||
|
char port[9]={0};
|
||||||
|
|
||||||
|
c_SocketUtil_GetNameInfoForSocket(s, host, sizeof(host), port, sizeof(port));
|
||||||
|
printf("[ERROR] Server %s:%s ON %s\n", host, port, c_TcpServer_GetErrorString(err));
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
c_Socket_Init();
|
||||||
|
uint16_t server_port = 1313;
|
||||||
|
c_TcpServer_t server;
|
||||||
|
|
||||||
|
printf("[SERVER] Initializing Poll-driven Time Server on port %d...\n", server_port);
|
||||||
|
|
||||||
|
/* 1. Spin up master server listener instance with your explicit flags signature layout */
|
||||||
|
c_err_t err = c_TcpServer_Init(&server, AF_INET, server_port, 512, NULL);
|
||||||
|
if (err != C_SUCCESS) {
|
||||||
|
fprintf(stderr, "[ERROR] Failed to open master server listening socket\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
c_TcpServer_SetCallbacks(&server, OnPollTimeServerConnect, OnDataRequest, OnClose, OnError);
|
||||||
|
|
||||||
|
/* 2. Instantiate the Polymorphic POSIX poll Reactor multiplexer backend */
|
||||||
|
c_TcpReactor_t* poll_reactor = NULL;
|
||||||
|
err = c_TcpPollReactor_Create(&poll_reactor, &server.allocator);
|
||||||
|
if (err != C_SUCCESS) {
|
||||||
|
fprintf(stderr, "[ERROR] Failed to instantiate c_TcpPollReactor driver context\n");
|
||||||
|
c_TcpServer_Destroy(&server);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3. Bind poll multiplexer backend to server execution dispatcher context */
|
||||||
|
err = c_TcpServer_SetReactor(&server, poll_reactor);
|
||||||
|
if (err != C_SUCCESS) {
|
||||||
|
fprintf(stderr, "[ERROR] Failed to wire reactor driver onto dispatcher context\n");
|
||||||
|
c_TcpServer_Destroy(&server);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("[SERVER] Multiplexed Time Server is fully running via c_TcpPollReactor...\n");
|
||||||
|
|
||||||
|
/* 4. Non-recursive event loop dispatcher loop (Polled with 1000ms heartbeat intervals) */
|
||||||
|
while (server.is_running) {
|
||||||
|
c_TcpServer_Run(&server, 1000, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 5. Complete cleanup resource teardowns */
|
||||||
|
c_TcpServer_Destroy(&server);
|
||||||
|
printf("[SERVER] Time Server cleanly terminated.\n");
|
||||||
|
|
||||||
|
c_Socket_Destroy();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
#ifndef INCLUDED_C_TCPSELECTREACTOR_H
|
||||||
|
#define INCLUDED_C_TCPSELECTREACTOR_H
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef INCLUDED_C_TCPSERVER_H
|
||||||
|
#include <c_TcpServer.h>
|
||||||
|
#endif /*INCLUDED_C_TCPSERVER_H*/
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||||
|
/* */
|
||||||
|
|
||||||
|
c_err_t c_TcpSelectReactor_Create(c_TcpReactor_t** out_reactor, c_Allocator_t* allocator);
|
||||||
|
|
||||||
|
|
||||||
|
#endif /*INCLUDED_C_TCPSELECTREACTOR_H*/
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
#include "c_TcpSelectReactor.h"
|
||||||
|
#include "c_Test.h"
|
||||||
|
#include "c_TcpServer.h"
|
||||||
|
|
||||||
|
static void OnSelectClientConnected(c_socket_t client_sock, const c_SockAddr_t* peer_addr, void* args) {
|
||||||
|
(void)args;
|
||||||
|
char ip_str[64];
|
||||||
|
if (c_SockAddr_ToString(peer_addr, ip_str, sizeof(ip_str)) == C_SUCCESS) {
|
||||||
|
printf(" " COLOR_GREEN "[SELECT EVENT] Connection Intercepted: %s:%d" COLOR_RESET "\n",
|
||||||
|
ip_str, (int)c_SockAddr_GetPort(peer_addr));
|
||||||
|
}
|
||||||
|
c_Socket_Close(client_sock);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(test_select_reactor_server_lifecycle) {
|
||||||
|
c_Socket_Init();
|
||||||
|
|
||||||
|
c_TcpServer_t server;
|
||||||
|
/* Initialize an unweighted IPv4 TCP Stream Server on Port 8585 */
|
||||||
|
c_err_t err = c_TcpServer_Init(&server, AF_INET, 8585, 512, NULL);
|
||||||
|
ASSERT_INT_EQ(C_SUCCESS, err);
|
||||||
|
|
||||||
|
/* 1. Instantiate the select driver backend */
|
||||||
|
c_TcpReactor_t* select_backend = NULL;
|
||||||
|
err = c_TcpSelectReactor_Create(&select_backend, 0);
|
||||||
|
ASSERT_INT_EQ(C_SUCCESS, err);
|
||||||
|
|
||||||
|
/* 2. Bind the driver onto the polymorphic loop interface */
|
||||||
|
err = c_TcpServer_SetReactor(&server, select_backend);
|
||||||
|
ASSERT_INT_EQ(C_SUCCESS, err);
|
||||||
|
|
||||||
|
/* 3. Execute a fast non-blocking single-pass polling check (10ms timeout) */
|
||||||
|
err = c_TcpServer_Run(&server, 60000, OnSelectClientConnected, NULL);
|
||||||
|
ASSERT_INT_EQ(C_SUCCESS, err);
|
||||||
|
|
||||||
|
/* 4. Complete teardown */
|
||||||
|
c_TcpServer_Destroy(&server);
|
||||||
|
|
||||||
|
c_Socket_Destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
TEST_START(TcpServer_SelectReactor_Suite);
|
||||||
|
RUN_TEST(test_select_reactor_server_lifecycle);
|
||||||
|
TEST_REPORT();
|
||||||
|
RETURN_TEST_STATUS;
|
||||||
|
}
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "c_TcpServer.h"
|
||||||
|
#include "c_SocketStream.h"
|
||||||
|
#include "c_SockAddr.h"
|
||||||
|
#include "c_TcpSelectReactor.h"
|
||||||
|
|
||||||
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
|
#pragma comment(lib, "ws2_32.lib")
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reactor connection event callback.
|
||||||
|
* Fired automatically by c_TcpPollReactor whenever an inbound client connects.
|
||||||
|
*/
|
||||||
|
static bool OnConnect(c_TcpServer_t* server, c_socket_t client_sock, const c_SockAddr_t* peer_addr, void* args) {
|
||||||
|
(void)args;
|
||||||
|
char ip_str[INET6_ADDRSTRLEN] = {0};
|
||||||
|
uint16_t port = c_SockAddr_GetPort(peer_addr);
|
||||||
|
|
||||||
|
if (c_SockAddr_ToString(peer_addr, ip_str, sizeof(ip_str)) == C_ERR_OK) {
|
||||||
|
printf("[POLL INFO] Accepted client connection from: %s:%d\n", ip_str, (int)port);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool OnDataRequest(c_TcpServer_t* server, c_socket_t s , void* args) {
|
||||||
|
(void)args;
|
||||||
|
char host[32]={0};
|
||||||
|
char port[9]={0};
|
||||||
|
|
||||||
|
c_SocketUtil_GetNameInfoForSocket(s, host, sizeof(host), port, sizeof(port));
|
||||||
|
printf("Request from %s:%s\n", host, port);
|
||||||
|
|
||||||
|
char buffer[1024] = {0};
|
||||||
|
int err = recv(s, buffer, sizeof(buffer), 0);
|
||||||
|
|
||||||
|
printf("body: %.*s\n", err, buffer);
|
||||||
|
/* 1. Wrap raw client socket inside polymorphic Output Stream pipeline */
|
||||||
|
time_t raw_time = time(NULL);
|
||||||
|
struct tm* time_info = localtime(&raw_time);
|
||||||
|
|
||||||
|
char time_buffer[64] = {0};
|
||||||
|
strftime(time_buffer, sizeof(time_buffer), "%Y-%m-%d %H:%M:%S\n", time_info);
|
||||||
|
|
||||||
|
/* 3. Flush plaintext string down into the polymorphic socket driver channel */
|
||||||
|
c_size_t bytes_written = 0;
|
||||||
|
c_SocketUtil_Send(s, time_buffer, strlen(time_buffer), &bytes_written);
|
||||||
|
|
||||||
|
if (strncmp(buffer, "STOP", 4)==0) {
|
||||||
|
server->is_running = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static void OnClose(c_TcpServer_t* server, c_socket_t s, void* args) {
|
||||||
|
(void)args;
|
||||||
|
char host[32]={0};
|
||||||
|
char port[9]={0};
|
||||||
|
|
||||||
|
c_SocketUtil_GetNameInfoForSocket(s, host, sizeof(host), port, sizeof(port));
|
||||||
|
printf("Client Closed from %s:%s\n", host, port);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void OnError(c_TcpServer_t* server, c_socket_t s, int err, void* args) {
|
||||||
|
(void)args;
|
||||||
|
char host[32]={0};
|
||||||
|
char port[9]={0};
|
||||||
|
|
||||||
|
c_SocketUtil_GetNameInfoForSocket(s, host, sizeof(host), port, sizeof(port));
|
||||||
|
printf("[ERROR] Server %s:%s ON %s\n", host, port, c_TcpServer_GetErrorString(err));
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
c_Socket_Init();
|
||||||
|
uint16_t server_port = 1313;
|
||||||
|
c_TcpServer_t server;
|
||||||
|
|
||||||
|
printf("[SERVER] Initializing Poll-driven Time Server on port %d...\n", server_port);
|
||||||
|
|
||||||
|
/* 1. Spin up master server listener instance with your explicit flags signature layout */
|
||||||
|
c_err_t err = c_TcpServer_Init(&server, AF_INET, server_port, 512, NULL);
|
||||||
|
if (err != C_SUCCESS) {
|
||||||
|
fprintf(stderr, "[ERROR] Failed to open master server listening socket\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
c_TcpServer_SetCallbacks(&server, OnConnect, OnDataRequest, OnClose, OnError);
|
||||||
|
|
||||||
|
/* 2. Instantiate the Polymorphic POSIX poll Reactor multiplexer backend */
|
||||||
|
c_TcpReactor_t* reactor = NULL;
|
||||||
|
err = c_TcpSelectReactor_Create(&reactor, &server.allocator);
|
||||||
|
if (err != C_SUCCESS) {
|
||||||
|
fprintf(stderr, "[ERROR] Failed to instantiate c_TcpSelectReactor driver context\n");
|
||||||
|
c_TcpServer_Destroy(&server);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3. Bind poll multiplexer backend to server execution dispatcher context */
|
||||||
|
err = c_TcpServer_SetReactor(&server, reactor);
|
||||||
|
if (err != C_SUCCESS) {
|
||||||
|
fprintf(stderr, "[ERROR] Failed to wire reactor driver onto dispatcher context\n");
|
||||||
|
c_TcpServer_Destroy(&server);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("[SERVER] Multiplexed Time Server is fully running via c_TcpPollReactor...\n");
|
||||||
|
|
||||||
|
/* 4. Non-recursive event loop dispatcher loop (Polled with 1000ms heartbeat intervals) */
|
||||||
|
while (server.is_running) {
|
||||||
|
c_TcpServer_Run(&server, 1000, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 5. Complete cleanup resource teardowns */
|
||||||
|
c_TcpServer_Destroy(&server);
|
||||||
|
printf("[SERVER] Time Server cleanly terminated.\n");
|
||||||
|
|
||||||
|
c_Socket_Destroy();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
#include <c_TcpServer.h>
|
||||||
|
|
||||||
|
#include "c_SocketStream.h"
|
||||||
|
|
||||||
|
c_err_t c_TcpServer_Init(c_TcpServer_t* self, int family, uint16_t port, int backlog, c_Allocator_t* allocator) {
|
||||||
|
if (!self) return C_ERR_PARAM;
|
||||||
|
|
||||||
|
self->allocator = allocator ? *allocator : c_DefaultAllocator;
|
||||||
|
self->listen_sock = C_INVALID_SOCKET;
|
||||||
|
self->reactor = NULL;
|
||||||
|
self->is_running = C_FALSE;
|
||||||
|
|
||||||
|
/* 2. Instantiate the listening socket descriptor using the explicit parameter map */
|
||||||
|
self->listen_sock = socket(family, SOCK_STREAM, IPPROTO_IP);
|
||||||
|
if (self->listen_sock == C_INVALID_SOCKET) return C_ERR_FAIL;
|
||||||
|
|
||||||
|
/* 3. Enable SO_REUSEADDR to avoid socket bind crashes on rapid restarts */
|
||||||
|
c_Socket_SetReuseAddr(self->listen_sock, true);
|
||||||
|
|
||||||
|
/* 4. Configure dual-stack sockaddr union variants based on the address family */
|
||||||
|
c_SockAddr_t bind_addr;
|
||||||
|
if (c_SockAddr_Init(&bind_addr, family, 0, port)!=C_ERR_OK) {
|
||||||
|
c_TcpServer_Destroy(self);
|
||||||
|
return C_ERR_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (family == AF_INET6) {
|
||||||
|
/* Optional optimization: allow dual-stack IPv4 mapping on the IPv6 socket if supported */
|
||||||
|
c_Socket_SetIPV6Only(self->listen_sock, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 6. Bind the structural endpoint socket down to the network stack */
|
||||||
|
if (c_SocketUtil_BindWithSockAddr(self->listen_sock, &bind_addr) != C_ERR_OK) {
|
||||||
|
c_TcpServer_Destroy(self);
|
||||||
|
return C_ERR_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 7. Switch the socket handle to listener mode with a generous backlog size */
|
||||||
|
if (listen(self->listen_sock, backlog) == C_SOCKET_ERROR) {
|
||||||
|
c_TcpServer_Destroy(self);
|
||||||
|
return C_ERR_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
self->is_running = C_TRUE;
|
||||||
|
return C_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
c_err_t c_TcpServer_SetReactor(c_TcpServer_t* self, c_TcpReactor_t* reactor) {
|
||||||
|
if (!self || !reactor) return C_ERR_PARAM;
|
||||||
|
self->reactor = reactor;
|
||||||
|
/* Automatically add our master listener socket to watch for read/inbound events */
|
||||||
|
return c_TcpReactor_Add(self->reactor, self->listen_sock, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
c_err_t c_TcpServer_Run(c_TcpServer_t* self, long timeout_ms, void* args) {
|
||||||
|
if (!self || !self->reactor || !self->is_running) return C_ERR_PARAM;
|
||||||
|
return c_TcpReactor_Poll(self->reactor, timeout_ms, self, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
void c_TcpServer_Destroy(c_TcpServer_t* self) {
|
||||||
|
if (!self) return;
|
||||||
|
self->is_running = C_FALSE;
|
||||||
|
if (self->reactor) {
|
||||||
|
c_TcpReactor_Destroy(self->reactor);
|
||||||
|
self->reactor = NULL;
|
||||||
|
}
|
||||||
|
if (self->listen_sock != C_INVALID_SOCKET) {
|
||||||
|
c_Socket_Close(self->listen_sock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* c_TcpServer_GetErrorString(int code) {
|
||||||
|
switch (code) {
|
||||||
|
case C_ERR_OK: {
|
||||||
|
return "OK";
|
||||||
|
}
|
||||||
|
case C_TCPSERVER_ERR_ON_ACCEPT: {
|
||||||
|
return "ACCEPT";
|
||||||
|
}
|
||||||
|
case C_TCPSERVER_ERR_ON_POLL: {
|
||||||
|
return "POLL";
|
||||||
|
}
|
||||||
|
case C_TCPSERVER_ERR_ON_POLLERR: {
|
||||||
|
return "POLLERR";
|
||||||
|
}
|
||||||
|
case C_TCPSERVER_ERR_ON_RECV: {
|
||||||
|
return "RECV";
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
#ifndef INCLUDED_C_TCPSERVER_H
|
||||||
|
#define INCLUDED_C_TCPSERVER_H
|
||||||
|
|
||||||
|
#ifndef INCLUDED_C_TYPES_H
|
||||||
|
#include <c_Types.h>
|
||||||
|
#endif /*INCLUDED_C_TYPES_H*/
|
||||||
|
|
||||||
|
#ifndef INCLUDED_C_ALLOCATOR_H
|
||||||
|
#include <c_Allocator.h>
|
||||||
|
#endif /*INCLUDED_C_ALLOCATOR_H*/
|
||||||
|
|
||||||
|
#ifndef INCLUDED_C_INSTREAM_H
|
||||||
|
#include <c_InStream.h>
|
||||||
|
#endif /*INCLUDED_C_INSTREAM_H*/
|
||||||
|
|
||||||
|
#ifndef INCLUDED_C_OUTSTREAM_H
|
||||||
|
#include <c_OutStream.h>
|
||||||
|
#endif /*INCLUDED_C_OUTSTREAM_H*/
|
||||||
|
|
||||||
|
#ifndef INCLUDED_C_SOCKET_H
|
||||||
|
#include <c_Socket.h>
|
||||||
|
#endif /*INCLUDED_C_SOCKET_H*/
|
||||||
|
|
||||||
|
#ifndef INCLUDED_C_SOCKETUTIL_H
|
||||||
|
#include <c_SocketUtil.h>
|
||||||
|
#endif /*INCLUDED_C_SOCKETUTIL_H*/
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||||
|
/* */
|
||||||
|
|
||||||
|
#define C_TCPSERVER_ERR_ON_POLL 1001
|
||||||
|
#define C_TCPSERVER_ERR_ON_ACCEPT 1002
|
||||||
|
#define C_TCPSERVER_ERR_ON_POLLERR 1003
|
||||||
|
#define C_TCPSERVER_ERR_ON_RECV 1004
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||||
|
/* */
|
||||||
|
|
||||||
|
typedef struct c_TcpServer_t c_TcpServer_t;
|
||||||
|
typedef struct c_TcpReactor_t c_TcpReactor_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Unified event-driven user callback signature.
|
||||||
|
* @param client_sock The accepted socket descriptor for the client.
|
||||||
|
* @param peer_addr Unified dual-stack address structure containing client peer details.
|
||||||
|
*/
|
||||||
|
typedef bool (*c_TcpServer_OnConnect_t)(c_TcpServer_t* server, c_socket_t client_sock, const c_SockAddr_t* peer_addr, void* args);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Event-driven user data processing callback matched exactly to your layout signature.
|
||||||
|
* @param s The raw socket descriptor exhibiting unread data availability.
|
||||||
|
* @return C_TRUE to keep monitoring the client; C_FALSE to break and close the connection.
|
||||||
|
*/
|
||||||
|
typedef bool (*c_TcpServer_OnRequest_t)(c_TcpServer_t* server, c_socket_t s, void* args);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Event-driven user callback fired whenever an active client disconnects or encounters an error.
|
||||||
|
* @param s The raw socket descriptor of the client being terminated.
|
||||||
|
* @param args Custom user context parameters pointer.
|
||||||
|
*/
|
||||||
|
typedef void (*c_TcpServer_OnDisconnect_t)(c_TcpServer_t* server, c_socket_t s, void* args);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Event-driven user callback fired whenever an operational network error is caught.
|
||||||
|
* @param s The socket descriptor associated with the failure (Can be server->listen_sock or a client handle).
|
||||||
|
* @param error_code Internal unified framework error mapping code.
|
||||||
|
* @param args Custom user context parameters pointer.
|
||||||
|
*/
|
||||||
|
typedef void (*c_TcpServer_OnError_t)(c_TcpServer_t* server, c_socket_t s, c_err_t error_code, void* args);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Virtual Interface Table for I/O Multiplexing Engines */
|
||||||
|
typedef struct {
|
||||||
|
c_err_t (*add)(c_TcpReactor_t* self, c_socket_t sock, uint32_t events);
|
||||||
|
c_err_t (*remove)(c_TcpReactor_t* self, c_socket_t sock);
|
||||||
|
c_err_t (*poll)(c_TcpReactor_t* self, long timeout_ms, c_TcpServer_t* server, void* args);
|
||||||
|
void (*destroy)(c_TcpReactor_t* self);
|
||||||
|
} c_TcpReactorVtbl_t;
|
||||||
|
|
||||||
|
struct c_TcpReactor_t {
|
||||||
|
const c_TcpReactorVtbl_t* vtbl;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct c_TcpServer_t {
|
||||||
|
c_socket_t listen_sock; /* Master server listening socket descriptor */
|
||||||
|
c_TcpReactor_t* reactor; /* Polymorphic multiplexing backend driver link */
|
||||||
|
c_TcpServer_OnConnect_t fnOnConnect; /* Connection accept callback hook */
|
||||||
|
c_TcpServer_OnRequest_t fnOnRequest; /* Data incoming available callback hook */
|
||||||
|
c_TcpServer_OnDisconnect_t fnOnDisconnect; /* Disconnect termination 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_STATIC_FORCE_INLINE
|
||||||
|
c_err_t c_TcpReactor_Add(c_TcpReactor_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_TcpReactor_Remove(c_TcpReactor_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_TcpReactor_Poll(c_TcpReactor_t* self, long timeout_ms, c_TcpServer_t* server, void* args) {
|
||||||
|
if (!self || !self->vtbl || !self->vtbl->poll || !server) return C_ERR_PARAM;
|
||||||
|
return self->vtbl->poll(self, timeout_ms, server, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
C_STATIC_FORCE_INLINE
|
||||||
|
void c_TcpReactor_Destroy(c_TcpReactor_t* self) {
|
||||||
|
if (!self || !self->vtbl || !self->vtbl->destroy) return;
|
||||||
|
self->vtbl->destroy(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||||
|
/* */
|
||||||
|
|
||||||
|
/* Core Lifecycle Primitives */
|
||||||
|
c_err_t c_TcpServer_Init(c_TcpServer_t* self, int family, uint16_t port, int backlog, c_Allocator_t* allocator) ;
|
||||||
|
void c_TcpServer_Destroy(c_TcpServer_t* self);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Explicitly binds a specific I/O multiplexing reactor back-end driver.
|
||||||
|
*/
|
||||||
|
c_err_t c_TcpServer_SetReactor(c_TcpServer_t* self, c_TcpReactor_t* reactor);
|
||||||
|
|
||||||
|
|
||||||
|
C_STATIC_FORCE_INLINE
|
||||||
|
c_err_t c_TcpServer_SetCallbacks(c_TcpServer_t* self
|
||||||
|
, c_TcpServer_OnConnect_t on_connect
|
||||||
|
, c_TcpServer_OnRequest_t on_data
|
||||||
|
, c_TcpServer_OnDisconnect_t on_disconnect
|
||||||
|
, c_TcpServer_OnError_t on_error)
|
||||||
|
{
|
||||||
|
if (!self) return C_ERR_PARAM;
|
||||||
|
self->fnOnConnect = on_connect;
|
||||||
|
self->fnOnRequest = on_data;
|
||||||
|
self->fnOnDisconnect = on_disconnect;
|
||||||
|
self->fnOnError = on_error;
|
||||||
|
return C_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
C_STATIC_FORCE_INLINE
|
||||||
|
void c_TcpServer_SetOnConnect(c_TcpServer_t* self, c_TcpServer_OnConnect_t on_connect) {
|
||||||
|
if (!self) return;
|
||||||
|
self->fnOnConnect = on_connect;
|
||||||
|
}
|
||||||
|
|
||||||
|
C_STATIC_FORCE_INLINE
|
||||||
|
void c_TcpServer_SetOnData(c_TcpServer_t* self, c_TcpServer_OnRequest_t on_data) {
|
||||||
|
if (!self) return;
|
||||||
|
self->fnOnRequest = on_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
C_STATIC_FORCE_INLINE
|
||||||
|
void c_TcpServer_SetOnDisconnect(c_TcpServer_t* self, c_TcpServer_OnDisconnect_t on_disconnect) {
|
||||||
|
if (!self) return;
|
||||||
|
self->fnOnDisconnect = on_disconnect;
|
||||||
|
}
|
||||||
|
|
||||||
|
C_STATIC_FORCE_INLINE
|
||||||
|
void c_TcpServer_SetOnError(c_TcpServer_t* self, c_TcpServer_OnError_t on_error) {
|
||||||
|
if (!self) return;
|
||||||
|
self->fnOnError = on_error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Non-recursively runs the multiplexed event polling dispatcher loop layer.
|
||||||
|
*/
|
||||||
|
c_err_t c_TcpServer_Run(c_TcpServer_t* self, long timeout_ms, void* args);
|
||||||
|
|
||||||
|
|
||||||
|
const char* c_TcpServer_GetErrorString(int code);
|
||||||
|
|
||||||
|
#endif /*INCLUDED_C_TCPSERVER_H*/
|
||||||
Reference in New Issue
Block a user