evtid - The API for events driven network programming
#include "evtid.h"
evti_t tmr;
evtid_init(); evti_init(&tmr); tmr.action = my_timer_action; // call my_timer_action after 2 seconds evti_start_s(&tmr, 2); // dispatch the events evtid_dispatcher(); evtid_fini();
This small (and fairly simple) library is an attempt to provide simplified interface to select, poll, epoll found on *nix systems (and probably on some others :)
The library allows to handle timed events and read/write readinnes of file descriptors. It does not use any trees to store data, so it is very light-weight. Generally it provides the same functionality as libevent library. The latter is more fundamental but probably slower when it comes to timer handling. See more on this in GENERAL NOTES later.
evtid_init(void)
The function initiates necessary structures for dispatching events. The function should be called before any other function. If there's some problem with resources (which should normally never happen), then the function shall abort execution describing the problem
evtid_fini(void)
The function releases any resources allocated for events dispatching. Should be called as the last function.
This function is responsible for managing events distribution. It does not return as long as there are some users of events (timers or file descriptors). So before calling this function either a descriptor or timer shall be registered with the system, otherwise it shall exit immidiately
If some system error happens inside of this function (which normally shall never happen) the execution shall be aborted.
The timers are structures of type evti_t. The definition of this structure is
typedef void (*evti_handler_t)(evti_t *); typedef struct evti_t evti_t; struct evti_t { evti_handler_t action; evit_internal_t _data; };
Here are the functions that work with this structure
This function shall be used to initate the timer structure. This structure has only one field that is interesting for the user. This field is ``action''. It shall contain pointer to the callback for this timer. This field is not touched. The function initiates the _data field. This function must be called before any other functions working with evti_t.
The function is used to start a timer. When specified amount of milliseconds expires, then action handler for it will be called. The handler receives as pointer the same value as is passed to this function.
The same as evti_start_ms but the time shall be specified in microseconds.
The same as evti_start_ms but the time shall be specified in seconds.
This one is just slightly faster alternative to the above ones in the situations when the time is 0.
The function returns non-zero if the timer is started. Zero otherwise.
The function stops specified timer. The timer must be running, otherwise the assertion will fail and program will be terminated.
In certain cituations it might be desired to check how much time has elapsed since certain moment of time. Here's small set of functions that can be used for this. They work with the structure evti_mark_t. The content of this structure is not important to the user.
Use this function to mark the moment of time
Use this function to add some milliseconds value to the mark.
This function shall tell you how many milliseconds expired since the time was marked by evti_delay_mark. It automatically marks the time again, so after the call the mark will correspond to the moment when the difference was taken.
The file descriptor events are distributed using the following structure
typedef void (*evd_handler_t)(evd_t*); typedef struct evd_t evd_t; struct evd_t{ int d; evd_handler raction; evd_handler waction; evd_internal_t _data; };
Here, field ``d'' is the descriptor number, fields ``raction'' and ``waction'' define handlers that shall be called when descriptor is considered to be read-ready and write-ready.
This function is used to initiate evd_t._data field. The rest of fields are not touched. This function must be called before calling other functions.
The function registers file descriptor with the system. It must be called before the requests for specific checks are done.
The function notifies the system, that it is OK to call the raction method when the descriptor is read-ready.
The function notifies the system, that it shouldn't call raction method even when the descriptor is read-ready.
This function is special. In reality, the system considers the descriptor to be read-ready untill this function is called. When this function is called, then read-readinnes is cleared and the system checks if there's really some data available for reading. Untill the data arrives, the read-readinnes is not set. Normally, you should use non-blocking descriptors and call this function when the read attempt returns EAGAIN (or EWOULDBLOCK) error.
Normally, the writing is always ready, but sometimes, the outgoing buffer becomes full and the sender has to wait. This function shall be used to request the system to check the write-readinnes and call the handler when the descriptor is ready to write. The handler shall be called only once. So, you have to request the check every time when you need it.
This function requests, that the waction handler is called again without check for the write-readinnes. It might be used in the situations, when the data to be written is large and the user desires to give chance others to work.
This function returns non-zero if the structure is registered with the system. Zero otherwise.
There are no void pointers in this implementation. Both evd_t and evti_t structures have internal fields that allow them to be linked into library's structures. Of course, usually the handlers need additional information for processing events. This information is easy to access if evd_t or evti_t is part of some bigger structure. For example:
struct my_test{ evd_t evd; int my_id; };
Here pointer to evd is exactly the same as pointer to struct my_test. So simple cast is needed to access my_id field. In more complex cases, the ``pointed'' structure might be in the middle of ``containig'' structure. Then, to obtain pointer to the container, offset of that field has to be substracted from pointer to the field. There's macro ``offsetof'' defined in stddef.h (on linux), or alternatively macro ``predok'' defined in this library, that may help with this ariphmetic. So one can write
struct my_test *ptr = predok(evd_ptr, struct my_test, evd);
This will calculate pointer to struct my_test from the pointer to field evd of that structure.
The library converts notifications about errors into read/write readinnes. Anyway, the errors are usually obtained via reading/writing functions.
This library does not attempt to handle signals. And it shouldn't really. The following piece of code can convert asynchronous signals into synchronous events. Well, provide error checking etc.
static int sig_pipe; static evd_t sig_evd;
static void signal_arrived(evd_t * evd) { // actions for handling the signals; }
static void piper(int signo) { int err = errno; // write to sig_pipe your message write(sig_pipe, &err, sizeof(err)); errno = err; }
static void setup_sighandler(void) { struct sigaction act; int pp[2];
pipe(pp); sig_pipe = pp[1]; evd_init(&sig_evd); sig_evd.d = pp[0]; sig_evd.raction = signal_arrived; sig_evd.waction = NULL; evd_watch(&sig_evd); evd_raction_active(&sig_evd); evd_input_exhausted(&sig_evd);
act.sa_handler = piper; act.sa_flags = 0; sigemptyset(&act.sa_mask); sigaction(SIGHUP, &act, NULL); }
Newer linux distributions provide function clock_gettime. This function reports how much time has elapsed since certain moment of time. If this function is available, then it shall be used for tracing elapsed time. Otherwise, define flag EVTI_WITH_ITIMER and the itimer functions shall be used for it. Which means that ITIMER_REAL is used up and is not available for other purposes. (Why would you need it anyway, when you have timers). Subsequently, the SIGALRM is also handled by the library. In return, the library is independent from the system time, which can be changed at any moment.
Currently, the smallest time resolution is 1 ms. That's what poll allows. Anyway, smaller times can't be handled correctly on vanilla linux and most other OS. Even 1 ms is somewhat questionable.
The implementation does not use any trees to store timers. Since in most of the applications (I had chance to write) the timers are needed as a time-out for some event, and these time-outs rarely expire, I use the approach they have in Linux kernel (and probably other places). So, there are slots for first 64 ms, and there are 64 more slots for groups of length 64 ms each. All other times are placed in an unsorted linked list. So once in 64 ms I may run thru the small group that contains timers expiring in next 64 ms, and ones in 4.096 seconds I run thru the longer list that contains all other timers and rearrange them into approprate place within groups.
To use the library, simply compile evtid.c and link it with your code. When EVD_WITH_EPOLL is defined, then the epoll shall be used for checking readinnes. Defining EVD_WITH_SELECT results in using select, otherwise poll is used.
I've run it on NetBSD and Linux. No other platforms are available to me.
Andrei A. Voropaev <voropaev.andrey@gmail.com>