blob: b57d80f32aae3731bea2ebe3bb167285bd944603 [file] [log] [blame] [view] [raw]
# The Non-Blocking POSIX Sockets Library
`libsock` is a non-blocking socket helper library, providing a user level buffer, non-blocking
sockets and some helper functions.
This library is great when using it alongside `libreact`, since `libreact` will automatically call `sock_flush` for every socket that was added to the reactor and is ready to send data. Also, `libsock` can automatically add sockets to the reactor when `sock_accept` or `sock_connect` are provided with a reactor object's pointer.
`libsock` requires only the following two files from this repository: [`src/libsock.h`](../src/libsock.h) and [`src/libsock.c`](../src/libsock.c).
If you're looking to utilize `lib-server` the only helpful section in this file is the section regarding the Read / Write Hooks that can be used to implement TLS or other intermediary protocols.
## The Socket object
In order to allow `libsock` to be used with any existing libraries, the socket object is kept in an internal library buffer that is thread-safe.
Sockets data is accessed using their file descriptor (int), just like any POISIX socket. For further information, look over the `static struct FDData` declaration in the `libsock.c` file.
To initialize this data storage (an array of `FDData` objects), the library command `init_socklib` is used. If the storage isn't initialized up front, it will be initialized (and reinitialized) dynamically as needed, at the expense of performance.
### `init_socklib(size_t max_fd)`
Initializes the library up to a `max_fd` value for a file descriptor.
For maximum capacity, it is recommended that you call:
```c
init_socklib( sock_max_capacity() );
```
This call will actually attempt to extend the limits of the running process to the maximum limits allowed by the OS.
## The `libsock` API
The `libsock` API can be divided into a few different categories:
- General helper functions
- Accepting connections and opening new sockets.
- Sending and receiving data.
- Direct user level buffer API.
- Read/Write Hooks.
It should be noted that the library was built with convenience and safety in mind, so it incurs a performance cost related to these safety and convenience features (i.e. mutex locking protects the user-land buffer at the cost of performance).
### General helper functions
The following helper functions are for common tasks when socket programming. These functions are independent from the `libsock` state machine.
#### `int sock_set_non_block(int fd)`
Sets a socket to non blocking state.
This function is called automatically for the new socket, when using
`sock_accept` or `sock_connect`.
#### `ssize_t sock_max_capacity(void)`
Gets the maximum number of file descriptors this process can be allowed to
access.
If the "soft" limit is lower then the "hard" limit, the process's limits will be
extended to the allowed "hard" limit.
#### `int sock_listen(char* address, char* port)`
Opens a listening non-blocking socket. Return's the socket's file descriptor.
Returns -1 on error or the listening socket's `fd` on success.
### Accepting connections and opening new sockets
Accepting connections, initiating connections and opening sockets - as well as attaching open sockets to the `libsock` state machine - are required actions so that we can use sockets with the `libsock` API.
#### `int sock_accept(struct Reactor* owner, int server_fd)`
`sock_accept` accepts a new socket connection from the listening socket
`server_fd`, allowing the use of `sock_` functions with this new file
descriptor.
#### `int sock_connect(struct Reactor* owner, char* address, char* port)`
`sock_connect` is similar to `sock_accept` but should be used to initiate a
client connection to the address requested.
Returns the new file descriptor fd. Returns -1 on error.
#### `int sock_open(struct Reactor* owner, int fd)`
`sock_open` takes an existing file decriptor `fd` and initializes it's status as
open and available for `sock_API` calls.
This will reinitialize the data (user buffer etc') for the file descriptor
provided.
If a reactor pointer `owner` is provided, the `fd` will be attached to the
reactor.
Returns -1 on error and 0 on success.
#### `int sock_attach(struct Reactor* owner, int fd)`
`sock_attach` sets the reactor owner for a socket and attaches the socket to the
reactor.
Use this function when the socket was already opened with no reactor association
and it's data (buffer etc') is already initialized.
This is useful for a delayed attachment, where some more testing is required
before attaching a socket to a reactor.
Returns -1 on error and 0 on success.
#### `void sock_clear(int fd)`
Clears a socket state data and buffer.
Use this function after the socket was closed remotely or without using the
`sock_API`.
This function does **not** effect the state of the socket at the reactor. Call
`reactor_add` / `reactor_close` / `reactor_remove` for those purposes.
If the socket is owned by the reactor, it is unnecessary to call this function
for remote close events or after a `reactor_close` call.
#### `int sock_status(int fd)`
Returns the state of the socket, similar to calling `fcntl(fd, F_GETFL)`.
Returns -1 if the connection is closed.
The macro `sock_is_closed(fd)` translates to `(sock_status((fd)) < 0)`
### Sending and receiving data
Reading and writing data to a socket, using the `libsock` API, allows easy access to a user level buffer and file streaming capabilities, as well as read write hooks that simplify the task of implementing layered protocols (i.e. TLS).
#### `ssize_t sock_read(int fd, void* buf, size_t count)`
`sock_read` attempts to read up to count bytes from file descriptor fd into
the
buffer starting at buf.
It's behavior should conform to the native `read` implementations, except some
data might be available in the `fd`'s buffer while it is not available to be
read using sock_read (i.e., when using a transport layer, such as TLS).
Also, some internal buffering will be used in cases where the transport layer
data available is larger then the data requested.
#### `sock_write(sockfd, buf, count)`
`sock_write` writes up to count bytes from the buffer pointed `buf` to the buffer associated with the file descriptor `fd`.
The data isn't necessarily written to the socket and multiple calls to `sock_flush` might be required for the data to be actually sent.
On error, -1 will be returned. Otherwise returns 0. All the bytes are transferred to the socket's user level buffer.
**Note** this is actually a specific case of `sock_write2` and this macro actually calls `sock_write2`.
```c
#define sock_write(sockfd, buf, count) \
sock_write2(.fd = (sockfd), .buffer = (buf), .length = (count))
```
#### `ssize_t sock_write2(...)`
Translates as: `ssize_t sock_write2_fn(sock_write_info_s options)`
These are the basic options (named arguments) available:
```c
typedef struct {
/** The fd for sending data. */
int fd;
/** The data to be sent. This can be either a byte strteam or a file pointer
* (`FILE *`). */
const void* buffer;
/** The length (size) of the buffer. irrelevant for file pointers. */
size_t length;
/** The user land buffer will receive ownership of the buffer (forced as
* TRUE
* when `file` is set). */
unsigned move : 1;
/** The packet will be sent as soon as possible. */
unsigned urgent : 1;
/** The buffer points to a file pointer: `FILE *` */
unsigned file : 1;
/** The buffer contains the value of a file descriptor int - casting, not
* pointing, i.e.: `.buffer = (void*)fd;` */
unsigned is_fd : 1;
/** for internal use */
unsigned rsv : 1;
/**/
} sock_write_info_s;
```
`sock_write2_fn` is the actual function behind the macro `sock_write2`.
`sock_write2` is similar to `sock_write`, except special properties can be set.
On error, -1 will be returned. Otherwise returns 0. All the bytes are transferred to the socket's user level buffer.
#### `ssize_t sock_flush(int fd)`
`sock_flush` writes the data in the internal buffer to the file descriptor fd and closes the fd once it's marked for closure (and all the data was sent).
The number of bytes actually written to the fd will be returned. 0 will be returned when no data is written and -1 will be returned on an error or when the connection is closed.
**Please Note**: when using `libreact`, the `sock_flush` will be called automatically when the socket is ready.
#### `void sock_close(struct Reactor* reactor, int fd)`
`sock_close` marks the connection for disconnection once all the data was sent.
The actual disconnection will be managed by the `sock_flush` function.
`sock_flash` will automatically be called.
If a reactor pointer is provided, the reactor API will be used and the `on_close` callback should be called once the socket is closed.
#### `void sock_force_close(struct Reactor* reactor, int fd)`
`sock_force_close` closes the connection immediately, without adhering to any
protocol restrictions and without sending any remaining data in the connection
buffer.
If a reactor pointer is provided, the reactor API will be used and the `on_close` callback should be called as expected.
### Direct user level buffer API.
The user land buffer is constructed from pre-allocated Packet objects, each containing BUFFER_PACKET_SIZE (~17Kb) memory size dedicated for message data.
```c
#define BUFFER_PACKET_SIZE (1024 * 32)
```
Buffer packets - can be used for directly writing individual or multiple packets to the buffer instead of using the `sock_write(2)` helper functions / macros.
Unused Packets that were checked out using the `sock_checkout_packet` function, should never be freed using `free` and should always use the `sock_free_packet` function.
The data structure for a packet object provides detailed data about the packet's state and properties.
```c
typedef struct sock_packet_s {
ssize_t length;
void* buffer;
/** Metadata about the packet. */
struct {
/** allows the linking of a number of packets together. */
struct sock_packet_s* next;
/** sets whether a packet can be inserted before this packet without
* interrupting the communication flow. */
unsigned can_interrupt : 1;
/** sets whether a packet's buffer contains a file descriptor - casting, not
* pointing, i.e.: `packet->buffer = (void*)fd;` */
unsigned is_fd : 1;
/** sets whether a packet's buffer is of type `FILE *`. */
unsigned is_file : 1;
/** Keeps the `FILE *` or fd open - avoids automatically closing the file.
*/
unsigned keep_open : 1;
/** sets whether a packet's buffer is pre-allocated (references the
* `internal_memory`) or whether the data is allocated using `malloc` and
* should be freed. */
unsigned external : 1;
/** sets whether this packet (or packet chain) should be inserted in before
* the first `can_interrupt` packet, or at the end of the queu. */
unsigned urgent : 1;
/** Reserved for internal use - (memory shifting flag)*/
unsigned internal_flag : 1;
/** Reserved for future use. */
unsigned rsrv : 1;
/**/
} metadata;
} sock_packet_s;
```
#### `sock_packet_s* sock_checkout_packet(void)`
Checks out a `sock_packet_s` from the packet pool, transferring the ownership of the memory to the calling function. returns NULL if both the pool was empty and memory allocation had failed.
#### `ssize_t sock_send_packet(int fd, sock_packet_s* packet)`
Attaches a packet to a socket's output buffer and calls `sock_flush` for the
socket.
The packet's memory is **always** handled by the `sock_send_packet` function (even on error). If the memory isn't part of the packet pool, it will be released using `free` after closing any files and freeing any memory associated with the packet.
Returns -1 on error. Returns 0 on success.
#### `void sock_free_packet(sock_packet_s* packet)`
Use `sock_free_packet` to free unused packets (including packet chains) that were checked-out using `sock_checkout_packet`.
NEVER use `free`, for any packet checked out using the pool management function `sock_checkout_packet`.
Passing a single packet will free also any packet it references (the `next` packet is also freed).
### RW Hooks.
The following struct is used for setting a the read/write hooks that will replace the default system calls to `recv` and `write`.
```c
typedef struct sock_rw_hook_s {
/** Implement reading from a file descriptor. Should behave like the file
* system `read` call, including the setup or errno to EAGAIN / EWOULDBLOCK.*/
ssize_t (*read)(int fd, void* buf, size_t count);
/** Implement writing to a file descriptor. Should behave like the file system
* `write` call.*/
ssize_t (*write)(int fd, const void* buf, size_t count);
/** The `on_clear` callback is called when the socket data is cleared, ideally
* when the connection is closed, allowing for dynamic sock_rw_hook_s memory
* management.
*
* The `on_clear` callback is called within the socket's lock (mutex),
* providing a small measure of thread safety. This means that `sock_API`
* shouldn't be called from within this function (at least not in regards to
* the specific `fd`). */
void (*on_clear)(int fd, struct sock_rw_hook_s* rw_hook);
} sock_rw_hook_s;
```
#### `struct sock_rw_hook_s* sock_rw_hook_get(int fd)`
Gets a socket hook state (a pointer to the struct).
#### `int sock_rw_hook_set(int fd, struct sock_rw_hook_s* rw_hooks)`
Sets a socket hook state (a pointer to the struct).
## A Quick Example
The following example isn't very interesting, but it's good enough to demonstrate the API:
```c
#include "libreact.h"
#include "libsock.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>
// this will accept connections,
// it will be a simple HTTP hello world. (with keep alive)
void on_data(struct Reactor* reactor, int fd);
struct Reactor r = {.on_data = on_data, .maxfd = 1024};
int srvfd; // to make it simple, we'll have a global object
// this will handle the exit signal (^C).
void on_sigint(int sig) {
reactor_stop(&r);
}
int main(int argc, char const* argv[]) {
printf("starting up an http hello world example on port 3000\n");
// setup the exit signal
signal(SIGINT, on_sigint);
// create a server socket... this will take a moment...
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;
}
srvfd = // get the file descriptor
socket(servinfo->ai_family, servinfo->ai_socktype,
servinfo->ai_protocol);
if (srvfd <= 0) {
perror("addr err");
freeaddrinfo(servinfo);
return -1;
}
{ // avoid the "address taken"
int optval = 1;
setsockopt(srvfd, SOL_SOCKET, SO_REUSEADDR, &optval,
sizeof(optval));
}
// bind the address to the socket
if (bind(srvfd, servinfo->ai_addr, servinfo->ai_addrlen) < 0) {
perror("bind err");
freeaddrinfo(servinfo);
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");
freeaddrinfo(servinfo); // free the address data memory
// now that everything is ready, call the reactor library...
reactor_init(&r);
reactor_add(&r, srvfd);
while (reactor_review(&r) >= 0)
;
if (errno)
perror("\nFinished. last error was");
}
void on_data(struct Reactor* reactor, int fd) {
if (fd == srvfd) { // yes, this is our listening socket...
int client = 0;
unsigned int len = 0;
while ((client = accept(fd, NULL, &len)) > 0) {
reactor_add(&r, client);
} // reactor is edge triggered, we need to clear the cache.
} else {
char buff[8094];
ssize_t len;
static char response[] =
"HTTP/1.1 200 OK\r\n"
"Content-Length: 12\r\n"
"Connection: keep-alive\r\n"
"Keep-Alive: timeout=2\r\n"
"\r\n"
"Hello World!\r\n";
if ((len = recv(fd, buff, 8094, 0)) > 0) {
len = write(fd, response, strlen(response));
}
}
}
```