diff --git a/examples/echoz.c b/examples/echoz.c new file mode 100644 index 0000000..053ff59 --- /dev/null +++ b/examples/echoz.c @@ -0,0 +1,98 @@ +///////////////////////////////////////////////////////////////////////////////////////// +// +// This is a quick and dirty implementation of an echo client/server. +// It isn't written to reflect best practices! +// + +#include +#include +#include + +#define ZED_NET_IMPLEMENTATION +#include "zed_net.h" + +int run_server(unsigned short port) { + zed_net_init(); + + zed_net_udp_socket_t *socket = zed_net_udp_socket_open(port, 0); + printf("Running echo server on port %d!\n", port); + + char buffer[256]; + + while (strcmp(buffer, "done") != 0) { + zed_net_address_t sender; + + int bytes_read = zed_net_udp_socket_receive(socket, &sender, &buffer, sizeof(buffer)); + if (bytes_read) { + printf("Received %d bytes from %s:%d: %s\n", bytes_read, zed_net_host_to_str(sender.host), sender.port, buffer); + + printf("Echoing...\n"); + zed_net_udp_socket_send(socket, sender, buffer, sizeof(buffer)); + } + } + + printf("Done!\n"); + zed_net_udp_socket_close(socket); + + zed_net_shutdown(); + + return 0; +} + +int run_client(const char *host, unsigned short port) { + zed_net_init(); + + zed_net_udp_socket_t *socket = zed_net_udp_socket_open(0, 0); + + + zed_net_address_t address; + if (zed_net_get_address(&address, host, port) != 0) { + printf("Error: %s\n", zed_net_get_error()); + + zed_net_udp_socket_close(socket); + zed_net_shutdown(); + + return -1; + } + + printf("Running client! Type \"done\" to exit.\n"); + + char buffer[256]; + + while (strcmp(buffer, "done") != 0) { + printf("Message: "); + + fgets(buffer, 256, stdin); + buffer[strlen(buffer) - 1] = '\0'; + + printf("Sending...\n"); + zed_net_udp_socket_send(socket, address, buffer, sizeof(buffer)); + + zed_net_address_t sender; + + int bytes_read = zed_net_udp_socket_receive(socket, &sender, &buffer, sizeof(buffer)); + if (bytes_read) { + printf("Received %d bytes from %s:%d: %s\n", bytes_read, zed_net_host_to_str(sender.host), sender.port, buffer); + } + } + + printf("Done!\n"); + zed_net_udp_socket_close(socket); + + zed_net_shutdown(); + + return 0; +} + +int main(int argc, char **argv) { + if (argc == 3 && strcmp(argv[1], "-server") == 0) { + return run_server((unsigned short) atoi(argv[2])); + } else if (argc == 4 && strcmp(argv[1], "-client") == 0) { + return run_client(argv[2], (unsigned short) atoi(argv[3])); + } + + printf("Usage: echoz [-server [port] or -client [host] [port]\n"); + + return 0; +} + diff --git a/zed_net.h b/zed_net.h new file mode 100644 index 0000000..6290226 --- /dev/null +++ b/zed_net.h @@ -0,0 +1,307 @@ +///////////////////////////////////////////////////////////////////////////////////////// +// +// zed_net - v0.18 - public domain networking library +// (inspired by the excellent stb libraries: https://github.com/nothings/stb) +// +// This library is intended primarily for use in games and provides a simple wrapper +// around BSD sockets (Winsock 2.2 on Windows). Sockets can be set to be blocking or +// non-blocking. +// +// Only UDP sockets are supported at this time, but this may later expand to include TCP. +// +// VERSION HISTORY +// +// 0.18 (9/13/2015) minor polishing +// 0.17 (8/8/2015) initial release +// +// LICENSE +// +// This software is in the public domain. Where that dedication is not recognized, you +// are granted a perpetual, irrevocable license to copy, distribute, and modify this +// file as you see fit. +// +// USAGE +// +// #define the symbol ZED_NET_IMPLEMENTATION in *one* C/C++ file before the #include +// of this file; the implementation will be generated in that file. +// +// If you define the symbol ZED_NET_STATIC, then the implementation will be private to +// that file. +// +// Immediately after this block comment is the "header file" section. This section +// includes documentation for each API function. +// + +#ifndef INCLUDE_ZED_NET_H +#define INCLUDE_ZED_NET_H + +#ifdef ZED_NET_STATIC +#define ZED_NET_DEF static +#else +#define ZED_NET_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +///////////////////////////////////////////////////////////////////////////////////////// +// +// INITIALIZATION AND SHUTDOWN +// + +// Get a brief reason for failure +ZED_NET_DEF const char *zed_net_get_error(void); + +// Perform platform-specific socket initialization; +// *must* be called before using any other function +// +// Returns 0 on success, -1 otherwise (call 'zed_net_get_error' for more info) +ZED_NET_DEF int zed_net_init(void); + +// Perform platform-specific socket de-initialization; +// *must* be called when finished using the other functions +ZED_NET_DEF void zed_net_shutdown(void); + +///////////////////////////////////////////////////////////////////////////////////////// +// +// INTERNET ADDRESS API +// + +// Represents an internet address usable by sockets +typedef struct { + unsigned int host; + unsigned short port; +} zed_net_address_t; + +// Obtain an address from a host name and a port +// +// 'host' may contain a decimal formatted IP (such as "127.0.0.1"), a human readable +// name (such as "localhost"), or NULL for the default address +// +// Returns 0 on success, -1 otherwise (call 'zed_net_get_error' for more info) +ZED_NET_DEF int zed_net_get_address(zed_net_address_t *address, const char *host, unsigned short port); + +// Converts an address's host name into a decimal formatted string +// +// Returns NULL on failure (call 'zed_net_get_error' for more info) +ZED_NET_DEF const char *zed_net_host_to_str(unsigned int host); + +///////////////////////////////////////////////////////////////////////////////////////// +// +// UDP SOCKETS API +// + +// Wraps the system handle for a UDP socket +typedef struct { + int handle; +} zed_net_udp_socket_t; + +// Opens a UDP socket and binds it to a specified port +// (use 0 to select a random open port) +// +// Socket will not block if 'non-blocking' is non-zero +// +// Returns NULL on failure (call 'zed_net_get_error' for more info) +ZED_NET_DEF zed_net_udp_socket_t *zed_net_udp_socket_open(unsigned int port, int non_blocking); + +// Closes a previously opened socket +ZED_NET_DEF void zed_net_udp_socket_close(zed_net_udp_socket_t *socket); + +// Sends a specific amount of data to 'destination' +// +// Returns 0 on success, -1 otherwise (call 'zed_net_get_error' for more info) +ZED_NET_DEF int zed_net_udp_socket_send(zed_net_udp_socket_t *socket, zed_net_address_t destination, const void *data, int size); + +// Receives a specific amount of data from 'sender' +// +// Returns the number of bytes received, -1 otherwise (call 'zed_net_get_error' for more info) +ZED_NET_DEF int zed_net_udp_socket_receive(zed_net_udp_socket_t *socket, zed_net_address_t *sender, void *data, int size); + +#ifdef __cplusplus +} +#endif + +#endif // INCLUDE_ZED_NET_H + +#ifdef ZED_NET_IMPLEMENTATION + +#include +#include + +#ifdef _WIN32 +#include +#pragma comment(lib, "wsock32.lib") +#else +#include +#include +#include +#include +#include +#include +#endif + +static const char *zed_net__g_error; + +static int zed_net__error(const char *message) { + zed_net__g_error = message; + + return -1; +} + +ZED_NET_DEF const char *zed_net_get_error(void) { + return zed_net__g_error; +} + +ZED_NET_DEF int zed_net_init(void) { +#ifdef _WIN32 + WSADATA wsa_data; + if (WSAStartup(MAKEWORD(2, 2), &wsa_data) != 0) + { + return zed_net__error("Windows Sockets failed to start"); + } + + return 0; +#else + return 0; +#endif +} + +ZED_NET_DEF void zed_net_shutdown(void) { +#ifdef _WIN32 + WSACleanup(); +#endif +} + +ZED_NET_DEF int zed_net_get_address(zed_net_address_t *address, const char *host, unsigned short port) { + if (host == NULL) { + address->host = INADDR_ANY; + } else { + address->host = inet_addr(host); + if (address->host == INADDR_NONE) { + struct hostent *hostent = gethostbyname(host); + if (hostent) { + memcpy(&address->host, hostent->h_addr, hostent->h_length); + } else { + return zed_net__error("Invalid host name"); + } + } + } + + address->port = port; + + return 0; +} + +ZED_NET_DEF const char *zed_net_host_to_str(unsigned int host) { + struct in_addr in; + in.s_addr = host; + + return inet_ntoa(in); +} + +ZED_NET_DEF zed_net_udp_socket_t *zed_net_udp_socket_open(unsigned int port, int non_blocking) { + zed_net_udp_socket_t *sock = malloc(sizeof(zed_net_udp_socket_t)); + if (!sock) { + zed_net__error("Out of memory"); + return NULL; + } + + // Create the socket + sock->handle = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (sock->handle <= 0) { + zed_net_udp_socket_close(sock); + zed_net__error("Failed to create socket"); + return NULL; + } + + // Bind the socket to the port + struct sockaddr_in address; + address.sin_family = AF_INET; + address.sin_addr.s_addr = INADDR_ANY; + address.sin_port = htons(port); + + if (bind(sock->handle, (const struct sockaddr *) &address, sizeof(struct sockaddr_in)) != 0) { + zed_net_udp_socket_close(sock); + zed_net__error("Failed to bind socket"); + return NULL; + } + + // Set the socket to non-blocking if neccessary + if (non_blocking) { +#ifdef _WIN32 + if (ioctlsocket(sock->handle, FIONBIO, &non_blocking) != 0) { + zed_net_udp_socket_close(sock); + zed_net__error("Failed to set socket to non-blocking"); + return NULL; + } +#else + if (fcntl(sock->handle, F_SETFL, O_NONBLOCK, non_blocking) != 0) { + zed_net_udp_socket_close(sock); + zed_net__error("Failed to set socket to non-blocking"); + return NULL; + } +#endif + } + + return sock; +} + +ZED_NET_DEF void zed_net_udp_socket_close(zed_net_udp_socket_t *socket) { + if (!socket) { + return; + } + + if (socket->handle) { +#ifdef _WIN32 + closesocket(socket->handle); +#else + close(socket->handle); +#endif + } + + free(socket); +} + +ZED_NET_DEF int zed_net_udp_socket_send(zed_net_udp_socket_t *socket, zed_net_address_t destination, const void *data, int size) { + if (!socket) { + return zed_net__error("Socket is NULL"); + } + + struct sockaddr_in address; + address.sin_family = AF_INET; + address.sin_addr.s_addr = destination.host; + address.sin_port = htons(destination.port); + + int sent_bytes = sendto(socket->handle, (const char *) data, size, 0, (const struct sockaddr *) &address, sizeof(struct sockaddr_in)); + if (sent_bytes != size) { + return zed_net__error("Failed to send data"); + } + + return 0; +} + +ZED_NET_DEF int zed_net_udp_socket_receive(zed_net_udp_socket_t *socket, zed_net_address_t *sender, void *data, int size) { + if (!socket) { + return zed_net__error("Socket is NULL"); + } + +#ifdef _WIN32 + typedef int socklen_t; +#endif + + struct sockaddr_in from; + socklen_t from_length = sizeof(from); + + int received_bytes = recvfrom(socket->handle, (char *) data, size, 0, (struct sockaddr *) &from, &from_length); + if (received_bytes <= 0) { + return 0; + } + + sender->host = from.sin_addr.s_addr; + sender->port = ntohs(from.sin_port); + + return received_bytes; +} + +#endif // ZED_NET_IMPLEMENTATION