| /* |
| copyright: Boaz segev, 2016 |
| license: MIT |
| |
| Feel free to copy, use and enjoy according to the license provided. |
| */ |
| #ifndef LIB_SERVER |
| #define LIB_SERVER "0.4.0" |
| |
| #ifndef _GNU_SOURCE |
| #define _GNU_SOURCE |
| #endif |
| |
| #include <stdint.h> |
| #include <unistd.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| |
| /* lib server is based off and requires the following libraries: */ |
| #include "libreact.h" |
| #include "libasync.h" |
| #include "libsock.h" |
| |
| #ifndef SERVER_PRINT_STATE |
| /** |
| When SERVER_PRINT_STATE is set to 1, the server API will print out common |
| messages regarding the server state (start / finish / listen messages). |
| */ |
| #define SERVER_PRINT_STATE 1 |
| #endif |
| |
| #if LIB_ASYNC_VERSION_MINOR != 4 || LIB_REACT_VERSION_MINOR != 3 || \ |
| LIB_SOCK_VERSION_MINOR != 1 |
| #warning Lib-Server dependency versions are not in sync. Please review API versions. |
| #endif |
| |
| #ifndef __unused |
| #define __unused __attribute__((unused)) |
| #endif |
| |
| /** \file |
| ## LibServer - a dynamic protocol network services library |
| |
| * Library (not a framework): meanning, closer to the metal, abstracting only |
| what is required for API simplicity, error protection and performance. |
| |
| * Dynamic Protocol: meanning a service can change protocols mid-stream. Example |
| usecase: Websockets (HTTP Upgrade). |
| |
| * Network services: meanning multiple listenning network ports, each with it's |
| own logic. |
| |
| `libserver` utilizes `libreact`, `libasync` and `libsock` to create a simple |
| API wrapper around these minimalistic libraries and managing the "glue" that |
| makes them work together. |
| |
| It's simple and it's awesome :-) |
| |
| Here's a simple example that emulates an HTTP hello world server. This example |
| will count the number of client and messages using the server and demonstrates |
| some recommended implementation techniques, such as protocol inheritance. |
| |
| #include "libserver.h" // the reactor library |
| #include <stdatomic.h> |
| |
| #define THREADS 4 |
| #define PROCESSES 4 |
| |
| void on_close(protocol_s* protocol); |
| void on_shutdown(intptr_t sock, protocol_s* protocol); |
| void on_data(intptr_t sock, protocol_s* protocol); |
| protocol_s* demo_on_open(intptr_t fd, void* udata); |
| |
| // Our demo protocol object uses "C" style inheritance, |
| // where pointers location are the same so that a simple cast |
| // from one object to the other, allows us to access more data. |
| struct DemoProtocol { |
| protocol_s protocol; // must be first for C style inheritance |
| size_t _Atomic opened; |
| size_t _Atomic closed; |
| size_t _Atomic shutdown; |
| size_t _Atomic messages; |
| } demo_protocol = { |
| .protocol.service = "Demo", // This allows us to ID the protocol type. |
| .protocol.on_data = on_data, |
| .protocol.on_shutdown = on_shutdown, |
| .protocol.on_close = on_close, |
| }; |
| |
| // type-casting helper |
| #define pr2demo(protocol) ((struct DemoProtocol*)(protocol)) |
| |
| // A simple Hello World HTTP response emulation |
| char hello_message[] = |
| "HTTP/1.1 200 OK\r\n" |
| "Content-Length: 12\r\n" |
| "Connection: keep-alive\r\n" |
| "Keep-Alive: 1;timeout=5\r\n" |
| "\r\n" |
| "Hello World!"; |
| |
| protocol_s* on_open(intptr_t fd, void* udata) { |
| // Count events |
| atomic_fetch_add(&demo_protocol.opened, 1); |
| // Set timeout |
| server_set_timeout(fd, 5); |
| // * return pointer to the protocol. |
| // * This is the same as `(protocol_s *)&demo_protocol` |
| return &demo_protocol.protocol; |
| } |
| |
| void on_data(intptr_t sock, protocol_s* protocol) { |
| // read data |
| char data[1024]; |
| if (sock_read(sock, data, 1024)) { |
| // Count event |
| atomic_fetch_add(&pr2demo(protocol)->messages, 1); |
| // send reply |
| sock_write(sock, hello_message, sizeof(hello_message) - 1); |
| } |
| } |
| |
| void on_close(protocol_s* protocol) { |
| // Count event |
| atomic_fetch_add(&pr2demo(protocol)->closed, 1); |
| } |
| |
| void on_shutdown(intptr_t sock, protocol_s* protocol) { |
| // Count event |
| atomic_fetch_add(&pr2demo(protocol)->shutdown, 1); |
| } |
| |
| void on_idle(void) { |
| // idle event example |
| fprintf(stderr, "Server was idle, with %lu connections.\n", |
| server_count(NULL)); |
| } |
| |
| int main() { |
| // this isn't required normally, |
| // but some systems require atomics to be initialized. |
| atomic_store(&demo_protocol.opened, 0); |
| atomic_store(&demo_protocol.closed, 0); |
| atomic_store(&demo_protocol.shutdown, 0); |
| atomic_store(&demo_protocol.messages, 0); |
| // run the server |
| server_listen(.port = "3000", .on_open = on_open, .udata = NULL); |
| server_run(.threads = THREADS, .processes = PROCESSES, |
| .on_idle = on_idle); |
| // print results |
| fprintf(stderr, |
| "** Server returned\n" |
| "** %lu clients connected.\n" |
| "** %lu clients disconnected.\n" |
| "** %lu clients were connected when shutdown was called.\n" |
| "** %lu messages were sent\n", |
| atomic_load(&demo_protocol.opened), |
| atomic_load(&demo_protocol.closed), |
| atomic_load(&demo_protocol.shutdown), |
| atomic_load(&demo_protocol.messages)); |
| } |
| |
| |
| */ |
| |
| /**************************************************************************/ /** |
| * General info |
| */ |
| |
| /* The following types are defined for the userspace of this library: */ |
| |
| // struct Server; /** used internally. no public data exposed */ |
| struct ServerSettings; /** sets up the server's behavior */ |
| struct ServerServiceSettings; /** sets up a listenning socket's behavior */ |
| typedef struct Protocol protocol_s; /** controls connection events */ |
| |
| /**************************************************************************/ /** |
| * The Protocol |
| |
| The Protocol struct defines the callbacks used for the connection and sets the |
| behaviour for the connection's protocol. |
| |
| All the callbacks recieve a unique connection ID (a semi UUID) that can be |
| converted to the original file descriptor if in need. |
| |
| This allows the Server API to prevent old connection handles from sending data |
| to new connections after a file descriptor is "recycled" by the OS. |
| */ |
| struct Protocol { |
| /** |
| * A string to identify the protocol's service (i.e. "http"). |
| * |
| * The string should be a global constant, only a pointer comparison will be |
| * made (not `strcmp`). |
| */ |
| const char* service; |
| /** called when a data is available, but will not run concurrently */ |
| void (*on_data)(intptr_t fduuid, protocol_s* protocol); |
| /** called when the socket is ready to be written to. */ |
| void (*on_ready)(intptr_t fduuid, protocol_s* protocol); |
| /** called when the server is shutting down, |
| * but before closing the connection. */ |
| void (*on_shutdown)(intptr_t fduuid, protocol_s* protocol); |
| /** called when the connection was closed, but will not run concurrently */ |
| void (*on_close)(protocol_s* protocol); |
| /** called when a connection's timeout was reached */ |
| void (*ping)(intptr_t fduuid, protocol_s* protocol); |
| /** private metadata stored by `libserver`, usualy for object protection */ |
| uintptr_t _state_; |
| }; |
| |
| /**************************************************************************/ /** |
| * The Service Settings |
| |
| These settings will be used to setup listenning sockets. |
| */ |
| struct ServerServiceSettings { |
| /** Called whenever a new connection is accepted. Should return a pointer to |
| * the connection's protocol. */ |
| protocol_s* (*on_open)(intptr_t fduuid, void* udata); |
| /** The network service / port. Defaults to "3000". */ |
| char* port; |
| /** The socket binding address. Defaults to the recommended NULL. */ |
| char* address; |
| /** Opaque user data. */ |
| void* udata; |
| /** |
| * Called when the server starts, allowing for further initialization, such as |
| * timed event scheduling. |
| * |
| * This will be called seperately for every process. */ |
| void (*on_start)(void* udata); |
| /** called when the server is done, to clean up any leftovers. */ |
| void (*on_finish)(void* udata); |
| }; |
| |
| /**************************************************************************/ /** |
| * The Server Settings |
| |
| These settings will be used to setup server behaviour. missing settings will be |
| filled in with default values. |
| */ |
| struct ServerSettings { |
| /** called if the event loop in cycled with no pending events. */ |
| void (*on_idle)(void); |
| /** |
| * Called when the server starts, allowing for further initialization, such as |
| * timed event scheduling. |
| * |
| * This will be called seperately for every process. */ |
| void (*on_init)(void); |
| /** called when the server is done, to clean up any leftovers. */ |
| void (*on_finish)(void); |
| /** |
| Sets the amount of threads to be created for the server's thread-pool. |
| Defaults to 1 - the reactor and all callbacks will work using a single working |
| thread, allowing for an evented single threaded design. |
| */ |
| size_t threads; |
| /** Sets the amount of processes to be used (processes will be forked). |
| Defaults to 1 working processes (no forking). */ |
| size_t processes; |
| }; |
| |
| /* ***************************************************************************** |
| * The Server API |
| * (and helper functions) |
| */ |
| |
| /* ***************************************************************************** |
| * Server actions |
| */ |
| |
| /** |
| Listens to a server with any of the following server settings: |
| |
| * `.threads` the number of threads to initiate in the server's thread pool. |
| |
| * `.processes` the number of processes to use (a value of more then 1 will |
| initiate a `fork`). |
| |
| * `.on_init` an on initialization callback (for every process forked). |
| `void (*callback)(void);`` |
| |
| * `.on_finish` a post run callback (for every process forked). |
| `void (*callback)(void);`` |
| |
| * `.on_idle` an idle server callback (for every process forked). |
| `void (*callback)(void);`` |
| |
| This method blocks the current thread until the server is stopped when a |
| SIGINT/SIGTERM is received. |
| |
| To kill the server use the `kill` function with a SIGINT. |
| */ |
| int server_listen(struct ServerServiceSettings); |
| #define server_listen(...) \ |
| server_listen((struct ServerServiceSettings){__VA_ARGS__}) |
| /** runs the server, hanging the current process and thread. */ |
| ssize_t server_run(struct ServerSettings); |
| #define server_run(...) server_run((struct ServerSettings){__VA_ARGS__}) |
| /** Stops the server, shouldn't be called unless int's impossible to send an |
| * INTR signal. */ |
| void server_stop(void); |
| /** |
| Returns the last time the server reviewed any pending IO events. |
| */ |
| time_t server_last_tick(void); |
| /**************************************************************************** |
| * Socket actions |
| */ |
| |
| /** |
| Gets the active protocol object for the requested file descriptor. |
| |
| Returns NULL on error (i.e. connection closed), otherwise returns a `protocol_s` |
| pointer. |
| */ |
| protocol_s* server_get_protocol(intptr_t uuid); |
| /** |
| Sets a new active protocol object for the requested file descriptor. |
| |
| Returns -1 on error (i.e. connection closed), otherwise returns 0. |
| */ |
| ssize_t server_set_protocol(intptr_t uuid, protocol_s* new_protocol); |
| /** |
| Sets a connection's timeout. |
| |
| Returns -1 on error (i.e. connection closed), otherwise returns 0. |
| */ |
| void server_set_timeout(intptr_t uuid, uint8_t timeout); |
| |
| /** Attaches an existing connection (fd) to the server's reactor and protocol |
| management system, so that the server can be used also to manage connection |
| based resources asynchronously (i.e. database resources etc'). |
| |
| On failure the fduuid_u.data.fd value will be -1. |
| */ |
| intptr_t server_attach(int fd, protocol_s* protocol); |
| /** Hijack a socket (file descriptor) from the server, clearing up it's |
| resources. The control of hte socket is totally relinquished. |
| |
| This method will block until all the data in the buffer is sent before |
| releasing control of the socket. |
| |
| The returned value is the fd for the socket, or -1 on error. |
| */ |
| int server_hijack(intptr_t uuid); |
| /** Counts the number of connections for the specified protocol (NULL = all |
| protocols). */ |
| long server_count(char* service); |
| |
| /**************************************************************************** |
| * Read and Write |
| * |
| * Simpley use `libsock` API for read/write. |
| */ |
| |
| /** |
| Sends data from a file as if it were a single atomic packet (sends up to |
| length bytes or until EOF is reached). |
| |
| Once the file was sent, the `source_fd` will be closed using `close`. |
| |
| The file will be buffered to the socket chunk by chunk, so that memory |
| consumption is capped. The system's `sendfile` might be used if conditions |
| permit. |
| |
| `offset` dictates the starting point for te data to be sent and length sets |
| the maximum amount of data to be sent. |
| |
| Returns -1 and closes the file on error. Returns 0 on success. |
| */ |
| __unused static inline ssize_t sock_sendfile(intptr_t uuid, |
| int source_fd, |
| off_t offset, |
| size_t length) { |
| return sock_write2(.fduuid = uuid, .buffer = (void*)((intptr_t)source_fd), |
| .length = length, .is_fd = 1, .offset = offset); |
| } |
| |
| /**************************************************************************** |
| * Tasks + Async |
| */ |
| |
| /** |
| Schedules a specific task to run asyncronously for each connection (except the |
| origin connection). |
| a NULL service identifier == all connections (all protocols). |
| |
| The task is performed within each target connection's busy "lock", meanning no |
| two tasks (or `on_data` events) should be performed at the same time |
| (concurrency will be avoided within the context of each connection, except for |
| `on_shutdown`, `on_close` and `ping`). |
| |
| The `on_finish` callback will be called once the task is finished and it will |
| receive the originating connection's UUID (could be 0). The originating |
| connection might have been closed by that time. |
| |
| The `service` string (pointer) identifier MUST be a constant string object OR |
| a string that will persist until the `on_finish` callback is called. In other |
| words, either hardcode the string or use `malloc` to allocate it before |
| calling `each` and `free` to release the string from within the `on_finish` |
| callback. |
| |
| It is recommended the `on_finish` callback is only used to perform any |
| resource cleanup necessary. |
| */ |
| void server_each(intptr_t origin_uuid, |
| const char* service, |
| void (*task)(intptr_t uuid, protocol_s* protocol, void* arg), |
| void* arg, |
| void (*on_finish)(intptr_t origin_uuid, |
| protocol_s* protocol, |
| void* arg)); |
| /** Schedules a specific task to run asyncronously for a specific connection. |
| |
| returns -1 on failure, 0 on success (success being scheduling the task). |
| |
| If a connection was terminated before performing their sceduled tasks, the |
| `fallback` task will be performed instead. |
| |
| It is recommended to perform any resource cleanup within the fallback function |
| and call the fallback function from within the main task, but other designes |
| are valid as well. |
| */ |
| void server_task(intptr_t uuid, |
| void (*task)(intptr_t uuid, protocol_s* protocol, void* arg), |
| void* arg, |
| void (*fallback)(intptr_t uuid, void* arg)); |
| /** Creates a system timer (at the cost of 1 file descriptor) and pushes the |
| timer to the reactor. The task will repeat `repetitions` times. if |
| `repetitions` is set to 0, task will repeat forever. Returns -1 on error |
| or the new file descriptor on succeess. |
| */ |
| int server_run_every(size_t milliseconds, |
| size_t repetitions, |
| void (*task)(void*), |
| void* arg, |
| void (*on_finish)(void*)); |
| |
| /** Creates a system timer (at the cost of 1 file descriptor) and pushes the |
| timer to the reactor. The task will NOT repeat. Returns -1 on error or the |
| new file descriptor on succeess. */ |
| __unused static inline int server_run_after(size_t milliseconds, |
| void task(void*), |
| void* arg) { |
| return server_run_every(milliseconds, 1, task, arg, NULL); |
| } |
| |
| #endif |