blob: 2ebd9c864202c07e1413f95bb0f354bf5f8ce53d [file] [log] [blame] [raw]
/*
copyright: Boaz segev, 2015
license: MIT
Feel free to copy, use and enjoy according to the license provided.
*/
#ifndef LIBREACT_H
#define LIBREACT_H
#define LIBREACT_VERSION 0.1.0
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <sys/time.h>
#include <sys/types.h>
#if !defined(__unix__) && !defined(__linux__) && !defined(__APPLE__) && \
!defined(__CYGWIN__)
#error This library currently supports only Unix based systems (i.e. Linux and BSD)
#endif
#define reactor_start_with(...) \
{ \
struct ReactorSettings __the_actual_protocol_object_ = \
(struct ReactorSettings){__VA_ARGS__}; \
reactor_start(&__the_actual_protocol_object_); \
}
/*****************************/ /**
This small library implementing a reactor pattern using callbacks.
Here are the supported events and their callbacks:
- File Descriptor events:
- Ready to Read (`on_data` callback).
- Ready to Write (`on_ready` callback).
- Closed (`on_close` callback).
- Reactor Exit (`on_shutdown` callback - will be called before the file
descriptor is closed and an `on_close` callback is fired).
- Global events:
- "tick" (`on_tick` callback), whenever a cycle occurs.
- Idle (`on_idle` callback), whenever a cycle with no events has occured.
Here's a full example:
```
#include "libreact.h"
// we're writing a small server, many included files...
#include <sys/socket.h>
#include <sys/types.h>
#include <netdb.h>
#include <string.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
// to make it simple, we'll have a global object.
struct ReactorSettings *global_reactor;
// this will initialize the global object.
void on_init(struct ReactorSettings *reactor) { global_reactor = reactor; };
// this will accept connections, send data and close the connection.
void on_data(struct ReactorSettings *reactor, int fd);
// this will handle the exit signal (^C).
void on_sigint(int sig);
int main(int argc, char const *argv[]) {
printf("starting up a rude server on port 3000\n");
// setup the exit signal
signal(SIGINT, on_sigint);
// create a server socket... this will take a moment...
int srvfd;
char *port = "3000";
// setup the address
struct addrinfo hints;
struct addrinfo *servinfo; // will point to the results
memset(&hints, 0, sizeof hints); // make sure the struct is empty
hints.ai_family = AF_UNSPEC; // don't care IPv4 or IPv6
hints.ai_socktype = SOCK_STREAM; // TCP stream sockets
hints.ai_flags = AI_PASSIVE; // fill in my IP for me
if (getaddrinfo(NULL, port, &hints, &servinfo)) {
perror("addr err");
return -1;
}
// get the file descriptor
srvfd =
socket(servinfo->ai_family, servinfo->ai_socktype,
servinfo->ai_protocol);
if (srvfd <= 0) {
perror("addr err");
return -1;
}
// bind the address to the socket
if (bind(srvfd, servinfo->ai_addr, servinfo->ai_addrlen) < 0) {
perror("bind err");
return -1;
}
// make sure the socket is non-blocking
static int flags;
if (-1 == (flags = fcntl(srvfd, F_GETFL, 0)))
flags = 0;
fcntl(srvfd, F_SETFL, flags | O_NONBLOCK);
// listen in
listen(srvfd, SOMAXCONN);
if (errno)
perror("starting. last error was");
// now that everything is ready, call the reactor library...
enter_reactor_loop(.on_data = on_data, .first = srvfd, .on_init = on_init);
if (errno)
perror("\nFinished. last error was");
}
void on_data(struct ReactorSettings *reactor, int fd) {
int client = 0;
unsigned int len = 0;
if (fd == reactor->first) {
// yes, this is our listening socket...
// we can add more using:
// reactor->add(reactor, new_socket);
while ((client = accept(fd, NULL, &len)) > 0) {
printf("fire in the hole, throw them out!\n");
send(client, "Goodbye!\n", 9, 0);
close(client);
} // reactor is edge triggered, we need to clear the cache.
}
}
void on_sigint(int sig) {
// stop the reactor
if (global_reactor)
global_reactor->stop(global_reactor);
}
```
As you can see, entering an event based reactor pattern is easy... but writing
the server code and all the protocol dynamics (no to mention, parsing) that's
difficult.
To make things even simpler, check out the lib-server library with it's dynamic
protocol and multi-threading capabilities. This will leave you with simply
writing the actual network protocol, and you can usually find an existing
library for that :-)
*/
struct ReactorSettings {
// File Descriptor Callbacks
/// called when the file descriptor has incoming data. This is edge triggered
/// and will not be called again unless all the previous data was consumed.
void (*on_data)(struct ReactorSettings* reactor, int fd);
/// called when the file descriptor is ready to send data (outgoing).
void (*on_ready)(struct ReactorSettings* reactor, int fd);
/// called for any open file descriptors when the reactor is shutting down.
void (*on_shutdown)(struct ReactorSettings* reactor, int fd);
/// called when a file descriptor was closed (either locally or by the other
/// party, when it's a socket or a pipe).
void (*on_close)(struct ReactorSettings* reactor, int fd);
// global callbacks
/// called after the event loop was created but before any cycling takes
/// place, allowing for further initialization (such as adding a server
/// socket, setting up timers, etc').
void (*on_init)(struct ReactorSettings* reactor);
/// called whenever an event loop had cycled (a "tick").
void (*on_tick)(struct ReactorSettings* reactor);
/// called if an event loop cycled with no pending events.
void (*on_idle)(struct ReactorSettings* reactor);
// global data and settings
/// the maximum amount of milliseconds to wait for events before `on_idle` is
/// called. If you're using ticks or idle events to handle timers or timout
/// events, it should be set to a shorted amount of time (i.e. 1000
/// milliseconds). defaults to 1 second (1000 milli). set to -1 for an infinit
/// wait.
int tick;
// the time (seconds since epoch) of the last "tick" (event cycle)
time_t last_tick;
/// what's the point of entering an event loop reactor if we're not listening
/// to ANY events?! ... well, this will be the first event we'll listen to,
/// allowing us to easily listen a server socket without resorting to an
/// `on_init` callback. Use `on_init` to setup more than one.
int first;
/// the maximum value for a file descriptor that the reactor will be required
/// to handle. defaults to the maximum capacity for the process.
int last;
/// The maximum events to be handled during a single event-loop cycle.
/// Defaults to 64 events.
int event_per_cycle;
/// opaque user data.
void* udata;
// helper functions
/// an instance function that will gracefully shut down the reactor.
void (*stop)(struct ReactorSettings* reactor);
/// adds a file descriptor to the reactor. returns -1 on error and 0 on
/// sucess. review the error using errno.
int (*add)(struct ReactorSettings* reactor, int fd);
/// returns the file descriptor if it's mapped as active, otherwise returns 0.
int (*exists)(struct ReactorSettings* reactor, int fd);
/// adds a special file descriptor to the reactor, setting it up as a timer on
/// kqueue/epoll. might fail if future versions support `select`.
///
/// this might require (on linux) a `timerfd_create` call.
int (*add_as_timer)(struct ReactorSettings* reactor,
int fd,
long milliseconds);
/// epoll requires the timer to be "reset" before repeating. Kqueue requires
/// no such thing. This method promises that the timer will be repeated when
/// running on epoll. This method is redundent on kqueue.
void (*reset_timer)(struct ReactorSettings* reactor, int fd);
/// removes a file descriptor from the reactor without calling and callbacks.
/// returns -1 on error and 0 on sucess. review the error using errno.
/// Remember to close any file descriptors you hijack.
int (*hijack)(struct ReactorSettings* reactor, int fd);
/// returns a new file descriptor that can be used for setting up a timer.
/// remember to close the new file descriptor when you're done with it.
int (*open_timer_fd)(void);
// set by the system.
/// the inner data used by the reactor
struct _Reactor_Data_ {
/// The file descriptor designated by kqueue / epoll.
int const reactor_fd;
/// the event loop flag. setting it to false will stop the loop after next
/// "tick".
int run;
/// a map for all the active file descriptors that were added to the
/// reactor.
char* map;
} private;
};
/// The main library function. Takes a ReactorSettings structure and initiates a
/// reactor pattern loop using kqueue/epoll. This method will block your code.
int reactor_start(struct ReactorSettings*);
#endif /* end of include guard: LIBREACT_H */