Skip to content

Commit

Permalink
socket: new socket api cross-platform layer (#112)
Browse files Browse the repository at this point in the history
  • Loading branch information
zpl-zak committed Jul 14, 2023
1 parent 0a8b7d0 commit 76418d7
Show file tree
Hide file tree
Showing 5 changed files with 395 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -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
Expand Down
35 changes: 35 additions & 0 deletions code/apps/examples/sockets.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#define ZPL_IMPL
#define ZPL_NANO
#define ZPL_ENABLE_SOCKET
#include <zpl.h>

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;
}
146 changes: 146 additions & 0 deletions code/header/socket.h
Original file line number Diff line number Diff line change
@@ -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

195 changes: 195 additions & 0 deletions code/source/socket.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
// file: source/socket.c

ZPL_BEGIN_C_DECLS

#if defined(ZPL_SYSTEM_WINDOWS)
# include <ws2tcpip.h>
# pragma comment(lib, "ws2_32.lib")
typedef int socklen_t;
#else //unix
# include <sys/socket.h>
# include <netdb.h>
# include <fcntl.h>
# include <unistd.h>
#ifndef TCP_NODELAY
# include <netinet/in.h>
# include <netinet/tcp.h>
# 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

Loading

0 comments on commit 76418d7

Please sign in to comment.