Skip to content

eddieavd/ugalib

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ugalib

It's a rainy day, you're stuck in your cave and the loneliness is starting to eat away at your mental health?
The mammoths are getting smarter and you have to coordinate your hunts more strategically, but you haven't developed language yet?
Fear no more, cause with ugalib your friends are only an uga_send away!

contents

getting started

installation

install with:

git clone https://github.com/eddieavd/ugalib
cd ugalib

make         # builds library, examples and tests, runs tests
# or
make libonly # pretty self-explanatory
# or
make test    # builds library and tests, runs tests

make install

installs headers to /usr/include/ugalib and library to /usr/lib/libugalib.a
(nothing is configurable at the moment, feel free to change things in the Makefile)

uninstall with:

make clean    # removes local build files
# or
make cleanall # removes build files and any installed files

usage

// main.c

#include <uganet.h>


void handle_connection ( uga_socket client ) ;

int main ( void )
{
    uga_socket socket = uga_sock_create_and_listen( "8080", 1 ) ;
    uga_print_abort_if_err() ;

    uga_socket client = uga_accept( socket ) ;
    uga_print_abort_if_err() ;

    handle_connection( client ) ;

    uga_sock_close( socket ) ;

    return 0 ;
}
cc main.c -o server -lugalib
./server

the library

overview

ugalib aims to provide higher level utilities similar to ones found in the c++ stl. Just don't forget to call the destructors (I really miss RAII).
ugalib networking closely follows Beej's Guide to Network Programming and additionally provides centralized error handling and other luxuries like configurable logging and multithreading.

core

types

the library provides some basic types (eg. sized integers) and wrappers for low level sockets API structs (eg. uga_addrinfo).

err

After a call to a library function, you can check for errors by calling uga_had_errs(). You can also handle errors automatically by calling one of uga_print_if_err(), uga_abort_if_err() or uga_print_abort_if_err() after a library call.

uga_socket = uga_sock_create_and_listen( "8080", 16 ) ;
uga_print_abort_if_err() ;

To get more details on the error, you can fetch the error data with uga_current_error(). The returned uga_error struct will contain uga_errtype and uga_err_category. For a basic error string, you can call uga_strerror( uga_errtype ) or simply uga_print_errtype(). A detailed description can be printed with uga_print_error() or obtained by casting the uga_error to the corresponding error struct according to the uga_err_category.

uga_string string = uga_str_create_from_1( "string" ) ; // size == 6

char val = uga_str_at( &string, 10 ) ; // index out of bounds

if( uga_current_error().cat == UGA_CAT_MEM )
{
    uga_error_mem const * errdata = uga_get_mem_errdata() ;

    // errdata->size is the size of the allocated block
    // errdata->pos  is the index we tried accessing
}

In the above example, calling uga_print_error() after uga_str_at() would print:

:: 20:21:31:861 : 281473276694560 : UGA::ERR  : mem error: block size is 6, bad access at 10

log

Before using ugalib's logging facilities, it's recommended to run uga_log_init() which checks the underlying terminal and enables colors if available.
Several log levels are provided by the library, but can also be extended by the user. Keep in mind that any custom log level has to have a lower priority than the provided ones (log level > UGA_LVL_DBG).
Compile with -DUGA_LOG_LVL=level to set a custom log level for the library. Otherwise the default is used (UGA_LOG_LVL_DBG).

i32_t some_val = 0 ;

UGA_DBG_S (     "worker", "value is %d", ++some_val ) ;
UGA_INFO_S(     "worker", "value is %d", ++some_val ) ;
UGA_WARN_S(     "worker", "value is %d", ++some_val ) ;
UGA_ERR_S (     "worker", "value is %d", ++some_val ) ;
UGA_LOG_S ( 10, "worker", "value is %d", ++some_val ) ;

produces:

:: 19:16:47:939 : 281473251115040 : UGA::DBG  : worker : value is 1
:: 19:16:47:940 : 281473251115040 : UGA::INFO : worker : value is 2
:: 19:16:47:940 : 281473251115040 : UGA::WARN : worker : value is 3
:: 19:16:47:940 : 281473251115040 : UGA::ERR  : worker : value is 4
:: 19:16:47:940 : 281473251115040 : UGA::0010 : worker : value is 5
   ^-|current     ^-|thread         ^-|log      ^-|scope
     |time          |id               |level

string

the uga_string can be created from an existing c string, a pointer and a length, or default, copy or move constructed from another uga_string (kinda like c++'s special member funcs).

uga_string str = uga_str_create_from_1( "cstring" ) ;

uga_string copy = uga_str_copy( &str ) ;
uga_string move = uga_str_move( &str ) ;

assert( uga_str_empty( &str ) ) ;

uga_str_append( &copy, &move ) ;

printf( STR_FMT, STR_ARG( copy ) ) ; // prints 'cstringcstring'

string_view

a tiny string_view that comes in handy for parsing stuff

...
uga_string source ; // holds a bunch of source code

uga_string_view view = uga_sv_create_from( &source ) ;

while( !uga_sv_empty( &view ) )
{
    uga_string_view line = uga_sv_chop_to_delimiter( &view, '\n' ) ;

    if( !parse_line( line ) )
    {
        uga_sv_unchop_left( &view, line.size ) ;
    }
}
...

vector

The uga_vector is a generic contiguous container with dynamic memory management.

uga_vector vector = uga_vec_create_0( i32_t ) ;

for( i32_t i = 0; i < 10; ++i )
{
    uga_vec_push_back( &vector, &i ) ;
}
...
for( i32_t i = 0; i < vec.size; ++i )
{
    i32_t * val = uga_vec_at( &vector, i ) ;
    ...
}
...
uga_vec_destroy( &vec ) ;

You can also store types which have custom destructors:

uga_vector vector = uga_vec_create_d_0( uga_string, uga_str_destroy_void ) ;

for( i32_t i = 0; i < 10; ++i )
{
    uga_string str = get_some_string() ;
    uga_vec_push_back( &vector, &str ) ;
}
...
uga_vec_destroy( &vec ) ; // calls string destructors before deallocating

list

ugalib provides singly and doubly linked list implementations:

uga_sl_list slist = uga_sl_list_create( i32_t ) ;
uga_dl_list dlist = uga_dl_list_create( i32_t ) ;

for( i32_t i = 0; i < 10; ++i )
{
    i32_t val = get_some_val() ;
    uga_sl_list_push_back ( &slist, &val ) ;
    uga_dl_list_push_front( &dlist, &val ) ;
}
...
uga_sl_list_destroy( &slist ) ;
uga_dl_list_destroy( &dlist ) ;

fs

uga_fs.h provides some basic filesystem utilities for checking filesizes, reading from and writing to files.

uga_string_view filename = uga_sv_create_1( "somefile.txt" ) ;

uga_string data = uga_fs_read_file( filename ) ;

// do stuff to contents

uga_fs_write_file( filename, uga_sv_create_from( &data ) ) ;

thread

uga_thread.h provides low level wrappers for thread.h utilities.
Use uga_tasks to define the job and arguments passed to the thread.
During the call to uga_thread_do_task(), the newly created thread will make a deep copy of the task's data string so each thread gets their own copy of the data.
This copied string will be referenced in the returned uga_thread's task field. This can be used as as in-out parameter to the thread forcing the caller to clean up after the threads or as an in parameter in which case the thread destroys its copy once it's done using it.

i32_t work ( void * arg )
{
    uga_string * data = ( uga_string * ) arg ;
    ...do some work...
    uga_str_destroy( data ) ; // if we don't need to pass any results back to the caller thread
    return 0 ;
}

int main ( void )
{
    ...
    uga_string data = get_data() ;
    uga_task   task = { data, work } ;

    uga_thread worker = uga_thread_do_task( task ) ;
    uga_str_destroy( &data ) ;
    ...
    uga_thread_join( &worker ) ;
    ...
    uga_str_destroy( &worker.task.data ) ; // if the thread needed to return data to the caller
    ...
}

For mutual exclusion you can either use thread.h's mtx and cnd through ugalib's wrappers, or ugalib's counting semaphore.
The following example has the main thread waiting for data from a third party and pushing all of it into a vector (global here for simplicity, could also be passed to the thread via the data string).
Once new data is available, the main thread locks the data_mtx mutex, pushes all the new data into the vector and calls uga_sem_release_n( sem, n ). This call increases the semaphore's internal counter by n allowing up to n threads to be woken up and try using the data.

uga_vector global_data ;
uga_mtx_t     data_mtx ;
uga_semaphore data_sem ;

i32_t work ( void * arg )
{
    while( true )
    {
        uga_sem_acquire( &data_sem ) ;
        uga_mtx_acquire( &data_mtx ) ;
        ...do stuff with the data vector...
        uga_mtx_release( &data_mtx ) ;
        ...do stuff with the data...
    }
}

int main ( void )
{
    uga_mtx_init( &data_mtx ) ;
    uga_sem_init( &data_sem ) ;

    uga_task   task   = { uga_str_create_0(), work } ;
    uga_thread worker = uga_thread_do_task( task ) ;

    while( true )
    {
        if( new_data_available() )
        {
            uga_mtx_acquire( &data_mtx ) ;
            push_new_data( &global_data ) ;
            uga_sem_release_n( &data_sem, global_data.size ) ;
            uga_mtx_release( &data_mtx ) ;
        }
    }
    uga_sem_destroy( &data_sem ) ;
    uga_mtx_destroy( &data_mtx ) ;
}

thread pool

ugalib also provides a simple thread pool implementation with FIFO task scheduling.

uga_task task_1 = { data_1, work_1 } ;
uga_task task_2 = { data_2, work_2 } ;
uga_task task_3 = { data_3, work_3 } ;
uga_task task_4 = { data_4, work_4 } ;

uga_thread_pool * pool = uga_pool_create_new( NUM_THREADS ) ;

uga_pool_add_task( pool, task_1 ) ;
uga_pool_add_task( pool, task_2 ) ;
uga_pool_add_task( pool, task_3 ) ;
uga_pool_add_task( pool, task_4 ) ;

uga_pool_destroy( pool ) ;

net

socket

creating a new socket can be done like this:

uga_socket sock = uga_sock_create( "www.github.com", "443" ) ;

connecting and listening is also straightforward:

uga_socket connected = uga_sock_create_and_connect( "www.github.com", "https" ) ;
...
uga_socket srv = uga_sock_create_and_listen( "8080", 16 ) ;

ugalib uses a global uga_sock_conf for socket configuration and can be updated like this:

uga_sock_conf config = uga_sock_get_config() ;

config.family = UGA_SFAM_IPv4   ;
config.type   = UGA_SOCK_STREAM ;
config.flags  = UGA_CANONNAME   ;

uga_sock_set_config( config ) ;

You can also pass in a config on each call to avoid having to update the global config all the time:

uga_sock_conf config = { UGA_SFAM_UNSPEC, UGA_SOCK_DGRAM, 0 } ;

uga_socket sock = uga_sock_create_configured( hostname, servname, &config ) ;

talk

Once you have your sockets connected, sending and receiving is similar to the sockets API, but you can additinally use uga_strings instead of raw buffers:

// server.c

int main ( void )
{
    uga_socket socket = uga_sock_create_and_listen( "8372", 16 ) ;
    uga_socket client = uga_sock_accept( socket ) ;

    uga_string message = uga_recv_str( &client, 0 ) ;

    printf( "received message: " STR_FMT, STR_ARG( message ) ) ;

    return 0 ;
}
// client.c

int main ( void )
{
    uga_socket sock = uga_sock_create_and_connect( "127.0.0.1", "8372" ) ;

    uga_send( &sock, "hello", 5, 0 ) ;

    return 0 ;
}

async

(this will be overhauled soon)

In situations where you need to monitor multiple sockets for incoming data, uga_async.h provides some basic solutions.
The following example creates two sockets, one TCP and the other UDP, both waiting for incoming connections on their own ports.
We also "handcraft" a socket for stdin.

bool tcp_callback ( uga_callback * callback_data ) ;
bool udp_callback ( uga_callback * callback_data ) ;
bool std_callback ( uga_callback * callback_data ) ;

int main ( void )
{
    ...
    uga_string shared_data ;

    uga_socket tcp_sock = uga_sock_create_and_listen( tcp_port, backlog ) ;
    uga_socket udp_sock = uga_sock_create_and_bind( localhost, udp_port ) ;
    uga_socket std_sock = { 0 } ;
    std_sock.sockfd = fileno( stdin ) ;

    uga_callback tcp_cb = { &tcp_sock, &shared_data, tcp_callback } ;
    uga_callback udp_cb = { &udp_sock, &shared_data, udp_callback } ;
    uga_callback std_cb = { &std_sock, &shared_data, std_callback } ;

    uga_handler_list handlers = { 0 } ;

    uga_async_add_handler( &handlers, &tcp_cb ) ;
    uga_async_add_handler( &handlers, &udp_cb ) ;
    uga_async_add_handler( &handlers, &std_cb ) ;

    return uga_async_run( &handlers ) ;
}

After calling uga_asnyc_run(), the calling thread will block until a socket has data ready to be read. Once a socket becomes ready, the corresponding callback function is called and the wait continues.
uga_async_run() only returns if a callback returns false.

You can also periodically check if there is any data available by calling uga_async_has_ready( &handlers ) which will return the ready socket or an empty socket (with sockfd == -1) if no sockets are ready.

About

a standard library for cavemen

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published