From 76418d79bbc69d72e49f012d13da7587fd6e541c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Madar=C3=A1sz?= Date: Fri, 14 Jul 2023 16:26:28 +0200 Subject: [PATCH] socket: new socket api cross-platform layer (#112) --- CHANGELOG | 1 + code/apps/examples/sockets.c | 35 +++++++ code/header/socket.h | 146 ++++++++++++++++++++++++++ code/source/socket.c | 195 +++++++++++++++++++++++++++++++++++ code/zpl.h | 18 ++++ 5 files changed, 395 insertions(+) create mode 100644 code/apps/examples/sockets.c create mode 100644 code/header/socket.h create mode 100644 code/source/socket.c diff --git a/CHANGELOG b/CHANGELOG index 7e71a9a..f122a00 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,4 @@ +19.3.0 - socket: new socket api cross-platform layer 19.2.0 - file: add macros for standard i/o wrappers (ZPL_STDIO_IN, ...) 19.1.1 - thread: return error values for POSIX calls 19.1.0 - parser: introduce a new URI parser module diff --git a/code/apps/examples/sockets.c b/code/apps/examples/sockets.c new file mode 100644 index 0000000..6e101ad --- /dev/null +++ b/code/apps/examples/sockets.c @@ -0,0 +1,35 @@ +#define ZPL_IMPL +#define ZPL_NANO +#define ZPL_ENABLE_SOCKET +#include + +int main(void) { + zpl_socket_init(); + + zpl_socket sock = zpl_socket_create(ZPL_SOCKET_UDP, ZPL_SOCKET_BIND, 0, "127.0.0.1", "7800"); + if (sock == -1) { + zpl_printf_err("Failed to create socket\n"); + return 1; + } + + zpl_socket connect = zpl_socket_create(ZPL_SOCKET_UDP, ZPL_SOCKET_CONNECT, 0, "127.0.0.1", "7800"); + if (connect == -1) { + zpl_printf_err("Failed to create socket\n"); + return 1; + } + + char buffer[64] = {0}; + zpl_snprintf(buffer, zpl_size_of(buffer), "%s", "Hello, World!\n"); + zpl_isize len = zpl_socket_send(connect, buffer, zpl_size_of(buffer)); + zpl_printf("Sent %d bytes\n", len); + zpl_socket_close(connect); + + zpl_zero_item(buffer); + len = zpl_socket_receive(sock, buffer, zpl_size_of(buffer)); + zpl_printf("Received %d bytes, data: %s", len, buffer); + + zpl_socket_close(sock); + + zpl_socket_terminate(); + return 0; +} diff --git a/code/header/socket.h b/code/header/socket.h new file mode 100644 index 0000000..e15e9b3 --- /dev/null +++ b/code/header/socket.h @@ -0,0 +1,146 @@ +// file: header/socket.h + +ZPL_BEGIN_C_DECLS + +typedef int zpl_socket; + +typedef struct zpl_socket_addr { + char data[128]; +} zpl_socket_addr; + +typedef enum zpl_socket_protocol { + ZPL_SOCKET_TCP = 0, + ZPL_SOCKET_UDP = 1, +} zpl_socket_protocol; + +typedef enum zpl_socket_mode { + ZPL_SOCKET_BIND = 0, + ZPL_SOCKET_CONNECT = 1, +} zpl_socket_mode; + +typedef enum zpl_socket_flags { + ZPL_SOCKET_DEFAULT = 0, + ZPL_SOCKET_NON_BLOCKING = 0x01, + ZPL_SOCKET_NO_DELAY = 0x02, +} zpl_socket_flags; + +/** + * Initializes socket functionality + * @return 0 on success + */ +ZPL_DEF zpl_i32 zpl_socket_init(void); + +/** + * Creates a new socket configured according to the given parameters + * @param protocol Protocol of the socket, either ZPL_SOCKET_TCP or ZPL_SOCKET_UDP for TCP or UDP respectively + * @param mode Mode of the socket (bind or connect), either ZPL_SOCKET_BIND or ZPL_SOCKET_CONNECT + * @param flags Configuration flags, either ZPL_SOCKET_DEFAULT or a bitwise combination of flags + * @param host Host/address as a string, can be IPv4, IPv6, etc... + * @param service Service/port as a string, e.g. "1728" or "http" + * @return socket handle, or -1 on failure + */ +ZPL_DEF zpl_socket zpl_socket_create(zpl_i32 protocol, zpl_i32 mode, char flags, const char *host, const char *service); + +/** + * Closes the given socket + * @param socket Socket handle + */ +ZPL_DEF void zpl_socket_close(zpl_socket socket); + +/** + * Terminates socket functionality + */ +ZPL_DEF void zpl_socket_terminate(void); + +/** + * Configures the given socket (must be ZPL_SOCKET_TCP + ZPL_SOCKET_BIND) to listen for new connections with the given backlog + * @param socket Socket handle + * @param backlog Size of the backlog + * @return 0 on success, non-zero on failure + */ +ZPL_DEF zpl_i32 zpl_socket_listen(zpl_socket socket, zpl_i32 backlog); + +/** + * Uses the given socket (must be zpl_socket_listen) to accept a new incoming connection, optionally returning its address + * @param socket Socket handle + * @param addr Pointer to zpl_socket_addr to store the address + * @return a socket handle for the new connection, or -1 on failure (e.g. if there are no new connections) + */ +ZPL_DEF zpl_socket zpl_socket_accept(zpl_socket socket, zpl_socket_addr *addr); + +/** + * Writes the address the given socket is bound to into the given address pointer, useful when automatically assigning a port + * @param socket Socket handle + * @param addr Pointer to zpl_socket_addr to store the address + * @return 0 on success, non-zero on failure + */ +ZPL_DEF zpl_i32 zpl_socket_get_address(zpl_socket socket, zpl_socket_addr *addr); + +/** + * Writes the host/address and service/port of the given address into given buffers (pointer + size), one buffer may be NULL + * @param addr Pointer to zpl_socket_addr containing the address + * @param host Buffer to store the host/address string + * @param host_size Size of the host buffer + * @param service Buffer to store the service/port string + * @param service_size Size of the service buffer + * @return 0 on success, non-zero on failure + */ +ZPL_DEF zpl_i32 zpl_socket_get_address_info(zpl_socket_addr *addr, char *host, zpl_i32 host_size, char *service, zpl_i32 service_size); + +/** + * Uses the given socket (either ZPL_SOCKET_CONNECT or returned by zpl_socket_accept) to send the given data + * @param socket Socket handle + * @param data Pointer to the data to be sent + * @param size Size of the data + * @return how much data was actually sent (may be less than data size), or -1 on failure + */ +ZPL_DEF zpl_i32 zpl_socket_send(zpl_socket socket, const char *data, zpl_i32 size); + +/** + * Receives data using the given socket (either ZPL_SOCKET_CONNECT or returned by zpl_socket_accept) into the given buffer + * @param socket Socket handle + * @param buffer Pointer to the buffer to receive the data + * @param size Size of the buffer + * @return the number of bytes received, 0 on orderly shutdown, or -1 on failure (e.g. no data to receive) + */ +ZPL_DEF zpl_i32 zpl_socket_receive(zpl_socket socket, char *buffer, zpl_i32 size); + +/** + * Uses the given socket to send the given data to the given zpl_socket_addr + * @param socket Socket handle + * @param addr Pointer to zpl_socket_addr containing the address to send the data to + * @param data Pointer to the data to be sent + * @param size Size of the data + * @return how much data was actually sent (may be less than data size), or -1 on failure + */ +ZPL_DEF zpl_i32 zpl_socket_send_to(zpl_socket socket, zpl_socket_addr *addr, const char *data, zpl_i32 size); + +/** + * Receives data using the given socket into the given buffer, optionally returning the sender's address + * @param socket Socket handle + * @param addr Pointer to zpl_socket_addr to store the sender's address + * @param buffer Pointer to the buffer to receive the data + * @param size Size of the buffer + * @return the number of bytes received, 0 on orderly shutdown, or -1 on failure (e.g. no data to receive) + */ +ZPL_DEF zpl_i32 zpl_socket_receive_from(zpl_socket socket, zpl_socket_addr *addr, char *buffer, zpl_i32 size); + +/** + * Waits either until the given socket has new data to receive or the given time (in seconds) has passed + * @param socket Socket handle + * @param time Time to wait in seconds + * @return 1 if new data is available, 0 if timeout was reached, and -1 on error + */ +ZPL_DEF zpl_i32 zpl_socket_select(zpl_socket socket, zpl_f64 time); + +/** + * Waits either until a socket in the given list has new data to receive or the given time (in seconds) has passed + * @param sockets Array of socket handles + * @param count Number of sockets in the array + * @param time Time to wait in seconds + * @return 1 or more if new data is available, 0 if timeout was reached, and -1 on error + */ +ZPL_DEF zpl_i32 zpl_socket_multi_select(zpl_socket *sockets, zpl_i32 count, zpl_f64 time); + +ZPL_END_C_DECLS + diff --git a/code/source/socket.c b/code/source/socket.c new file mode 100644 index 0000000..c40695f --- /dev/null +++ b/code/source/socket.c @@ -0,0 +1,195 @@ +// file: source/socket.c + +ZPL_BEGIN_C_DECLS + +#if defined(ZPL_SYSTEM_WINDOWS) +# include +# pragma comment(lib, "ws2_32.lib") +typedef int socklen_t; +#else //unix +# include +# include +# include +# include + #ifndef TCP_NODELAY +# include +# include +# endif +#endif + +ZPL_DEF zpl_socket zpl_socket_init(void) { +# if defined(ZPL_SYSTEM_WINDOWS) + WSADATA winsock_data = {0}; + return WSAStartup(MAKEWORD(2, 2), &winsock_data) != NO_ERROR; +# endif + return 0; +} + +ZPL_DEF zpl_socket zpl_socket_create(zpl_i32 protocol, zpl_i32 mode, char flags, const char *host, const char *service) { + struct addrinfo *result, hints = { + (mode == ZPL_SOCKET_BIND) ? AI_PASSIVE : 0, + AF_UNSPEC, + (protocol == ZPL_SOCKET_UDP) ? SOCK_DGRAM : SOCK_STREAM, + 0, 0, 0, 0, 0 + }; + + if (getaddrinfo(host, service, &hints, &result) != 0) { + return -1; + } +# if defined(ZPL_SYSTEM_WINDOWS) + zpl_socket sock = (zpl_socket)socket(result->ai_family, result->ai_socktype, result->ai_protocol); + if (sock == INVALID_SOCKET) { + freeaddrinfo(result); + return -1; + } + if (sock > INT_MAX) { + closesocket(sock); + freeaddrinfo(result); + return -1; + } +# else + zpl_socket sock = socket(result->ai_family, result->ai_socktype, result->ai_protocol); + if (sock == -1) { + freeaddrinfo(result); + return -1; + } +# endif + + if (result->ai_family == AF_INET6) { + zpl_i32 no = 0; + setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&no, sizeof(no)); + } + + if (protocol == ZPL_SOCKET_TCP) { + int nodelay = (flags & ZPL_SOCKET_NO_DELAY); + setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *)&nodelay, sizeof(nodelay)); + } + + if (mode == ZPL_SOCKET_BIND) { + if (bind(sock, result->ai_addr, (int)result->ai_addrlen) != 0) { + freeaddrinfo(result); + return -1; + } + } + + if (flags & ZPL_SOCKET_NON_BLOCKING) { +# if defined(ZPL_SYSTEM_WINDOWS) + DWORD non_blocking = 1; + if (ioctlsocket(sock, FIONBIO, &non_blocking)) { + freeaddrinfo(result); + return -1; + } +# else + if (fcntl(sock, F_SETFL, O_NONBLOCK) == -1) { + freeaddrinfo(result); + return -1; + } +# endif + } + + if (mode == ZPL_SOCKET_CONNECT) { + if (connect(sock, result->ai_addr, (int)result->ai_addrlen) != 0 && !(flags & ZPL_SOCKET_NON_BLOCKING)) { + freeaddrinfo(result); + return -1; + } + } + + freeaddrinfo(result); + return sock; +} + +ZPL_DEF void zpl_socket_close(zpl_socket socket) { +# if defined(ZPL_SYSTEM_WINDOWS) + closesocket(socket); +# else + close(socket); +# endif +} + +ZPL_DEF void zpl_socket_terminate(void) { +# if defined(ZPL_SYSTEM_WINDOWS) + WSACleanup(); +# endif +} + +ZPL_DEF zpl_i32 zpl_socket_listen(zpl_socket socket, zpl_i32 backlog) { + return listen(socket, backlog); +} + +ZPL_DEF zpl_socket zpl_socket_accept(zpl_socket socket, zpl_socket_addr *addr) { +# if defined(ZPL_SYSTEM_WINDOWS) + int len = sizeof(*addr); + zpl_socket sock = (zpl_socket)accept(socket, (struct sockaddr *)addr, &len); + if (sock == INVALID_SOCKET) { + return -1; + } + if (sock > INT_MAX) { + closesocket(sock); + return -1; + } +# else + socklen_t len = sizeof(*addr); + zpl_socket sock = accept(socket, (struct sockaddr *)addr, &len); + if (sock == -1) { + return -1; + } +# endif + return sock; +} + +ZPL_DEF zpl_i32 zpl_socket_get_address(zpl_socket socket, zpl_socket_addr *addr) { + return getsockname(socket, (struct sockaddr *)addr, (socklen_t *)sizeof(*addr)); +} + +ZPL_DEF zpl_i32 zpl_socket_get_address_info(zpl_socket_addr *addr, char *host, zpl_i32 host_size, char *service, zpl_i32 service_size) { + return getnameinfo((struct sockaddr *)addr, (socklen_t)sizeof(*addr), host, host_size, service, service_size, 0); +} + +ZPL_DEF zpl_i32 zpl_socket_send(zpl_socket socket, const char *data, zpl_i32 size) { + return send(socket, data, size, 0); +} + +ZPL_DEF zpl_i32 zpl_socket_receive(zpl_socket socket, char *buffer, zpl_i32 size) { + return recv(socket, buffer, size, 0); +} + +ZPL_DEF zpl_i32 zpl_socket_send_to(zpl_socket socket, zpl_socket_addr *addr, const char *data, zpl_i32 size) { + return sendto(socket, data, size, 0, (struct sockaddr *)addr, (socklen_t)sizeof(*addr)); +} + +ZPL_DEF zpl_i32 zpl_socket_receive_from(zpl_socket socket, zpl_socket_addr *addr, char *buffer, zpl_i32 size) { + return recvfrom(socket, buffer, size, 0, (struct sockaddr *)addr, (socklen_t *)sizeof(*addr)); +} + +ZPL_DEF zpl_i32 zpl_socket_select(zpl_socket socket, zpl_f64 time) { + fd_set fds; + struct timeval tv; + + FD_ZERO(&fds); + if (socket > -1) FD_SET(socket, &fds); + + tv.tv_sec = (long)time; + tv.tv_usec = (long)((time - tv.tv_sec) * 1000000); + + return select(socket + 1, &fds, 0, 0, &tv); +} + +ZPL_DEF zpl_i32 zpl_socket_multi_select(zpl_socket *sockets, zpl_i32 count, zpl_f64 time) { + fd_set fds; + struct timeval tv; + zpl_i32 i, max = -1; + + FD_ZERO(&fds); + for (i = 0; i < count; ++i) { + if (sockets[i] > max) max = sockets[i]; + if (sockets[i] > -1) FD_SET(sockets[i], &fds); + } + + tv.tv_sec = (long)time; + tv.tv_usec = (long)((time - tv.tv_sec) * 1000000); + + return select(max + 1, &fds, 0, 0, &tv); +} + +ZPL_END_C_DECLS + diff --git a/code/zpl.h b/code/zpl.h index e0d21e4..d42b01d 100644 --- a/code/zpl.h +++ b/code/zpl.h @@ -119,6 +119,7 @@ Version History: # define ZPL_MODULE_THREADING # define ZPL_MODULE_JOBS # define ZPL_MODULE_PARSER +# define ZPL_MODULE_SOCKET /* zpl nano distribution */ # if defined(ZPL_NANO) || defined(ZPL_PICO) @@ -133,6 +134,7 @@ Version History: # undef ZPL_MODULE_THREADING # undef ZPL_MODULE_JOBS # undef ZPL_MODULE_PARSER +# undef ZPL_MODULE_SOCKET # endif # if defined(ZPL_PICO) @@ -172,6 +174,9 @@ Version History: # endif # if defined(ZPL_ENABLE_PARSER) && !defined(ZPL_MODULE_PARSER) # define ZPL_MODULE_PARSER +# endif +# if defined(ZPL_ENABLE_SOCKET) && !defined(ZPL_MODULE_SOCKET) +# define ZPL_MODULE_SOCKET # endif /* module disabling overrides */ @@ -208,6 +213,9 @@ Version History: # if defined(ZPL_DISABLE_PARSER) && defined(ZPL_MODULE_PARSER) # undef ZPL_MODULE_PARSER # endif +# if defined(ZPL_DISABLE_SOCKET) && defined(ZPL_MODULE_SOCKET) +# undef ZPL_MODULE_SOCKET +# endif #endif #if defined(__GCC__) || defined(__GNUC__) || defined(__clang__) @@ -295,6 +303,10 @@ Version History: # include "header/parsers/uri.h" #endif +#if defined(ZPL_MODULE_SOCKET) +# include "header/socket.h" +#endif + #if defined(ZPL_MODULE_THREADING) # if defined(ZPL_SYSTEM_UNIX) || defined(ZPL_SYSTEM_MACOS) # include @@ -472,6 +484,10 @@ Version History: # include "source/parsers/uri.c" #endif +#if defined(ZPL_MODULE_SOCKET) +# include "source/socket.c" +#endif + #if defined(ZPL_COMPILER_MSVC) # pragma warning(pop) #endif @@ -555,6 +571,7 @@ Version History: // header/core/time.h // header/hashing.h // header/regex.h +// header/socket.h // source/hashing.c // source/adt.c // source/process.c @@ -588,3 +605,4 @@ Version History: // source/core/file_tar.c // source/opts.c // source/math.c +// source/socket.c