ivykis(3)
π ivykis(3) β the ivykis programmer's manual π
okay bestie, welcome to the ivykis programmer's manual β¨ we're kicking things off with examples because honestly? examples ate, they're the whole vibe, and they're gonna make everything click. let's go π
πΈ NAME πΈ
iv_examplesβ ivykis examples π¦
π EXAMPLE π
okay so first things first, ivykis gets initialised by calling iv_init(3). this is literally the first function you call when you're dealing with ivykis β no cap β and it has to be called before you register file descriptors or timers. that's the rule. periodt. π«Ά
then the ivykis main event loop gets going with iv_main(3). and here's the tea: this function generally does not return... like, at all. the only exception is when iv_quit(3) gets called somewhere during execution of the program. π
so how does an application ask ivykis to monitor a certain file descriptor? you fill out a structure of type struct iv_fd with a file descriptor number and a callback function, and then you call iv_fd_register. that's it, that's the whole thing. π
the first example program waits for data from standard input, and writes a message to standard out whenever something is received:
#include <stdio.h>
#include <stdlib.h>
#include <iv.h>
struct iv_fd fd_stdin;
static void callback(void *dummy)
{
char buf[1024];
int len;
len = read(fd_stdin.fd, buf, sizeof(buf));
if (len <= 0) {
if (len < 0) {
if (errno == EAGAIN)
return;
perror("read");
}
exit(1);
}
printf("read %d bytes of data from stdin\n", len);
}
int main()
{
iv_init();
IV_FD_INIT(&fd_stdin);
fd_stdin.fd = 0;
fd_stdin.handler_in = callback;
iv_fd_register(&fd_stdin);
iv_main();
iv_deinit();
return 0;
}
now, the application is the one responsible for memory management of the struct iv_fds that get passed to ivykis. like, don't be freeing memory that contains those structures while they're still registered with ivykis (i.e. nobody has called iv_fd_unregister on them yet). that's on you, bestie. π
also, iv_fd_register does you a lil solid: it transparently sets the file descriptor you passed in to nonblocking mode, in anticipation of its future usage. iconic behaviour honestly. π
file descriptor callbacks are called in a level-triggered fashion. so the way we deal with fd_stdin in that example callback function is totally safe, no stress. if data arrives in the gap between read and detecting EAGAIN, ivykis will re-call the callback function right after it returns. and if there's more than 1024 bytes waiting in the input buffer, ivykis will keep re-calling the callback function until all the data from stdin has been drained. obsessed. π
π¦ EXAMPLE 2 π¦
the second example accepts connections on TCP port 6667, and waits on each of the connections for data. when data is received on any connection, a message gets printed to standard out. main character energy fr fr. π
#include <stdio.h>
#include <stdlib.h>
#include <iv.h>
#include <netinet/in.h>
struct connection
{
struct iv_fd fd;
/* other per-connection data goes here */
};
struct listening_socket
{
struct iv_fd fd;
/* other per-listening socket data goes here */
};
static void connection_handler(void *_conn)
{
struct connection *conn = (struct connection *)_conn;
char buf[1024];
int len;
len = read(conn->fd.fd, buf, sizeof(buf));
if (len <= 0) {
if (len < 0 && errno == EAGAIN)
return;
iv_fd_unregister(&conn->fd);
close(conn->fd.fd);
free(conn);
return;
}
printf("got %d bytes of data from %p\n", len, conn);
}
static void listening_socket_handler(void *_sock)
{
struct listening_socket *sock = (struct listening_socket *)_sock;
struct sockaddr_in addr;
socklen_t addrlen;
struct connection *conn;
int fd;
addrlen = sizeof(addr);
fd = accept(sock->fd.fd, (struct sockaddr *)&addr, &addrlen);
if (fd < 0) {
if (errno == EAGAIN)
return;
perror("accept");
exit(1);
}
conn = malloc(sizeof(*conn));
if (conn == NULL) {
fprintf(stderr, "listening_socket_handler: memory allocation error, dropping connection");
close(fd);
return;
}
IV_FD_INIT(&conn->fd);
conn->fd.fd = fd;
conn->fd.cookie = (void *)conn;
conn->fd.handler_in = connection_handler;
iv_fd_register(&conn->fd);
}
int main()
{
struct listening_socket s;
struct sockaddr_in addr;
int fd;
fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
perror("socket");
exit(1);
}
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(6667);
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("bind");
exit(1);
}
if (listen(fd, 4) < 0) {
perror("listen");
exit(1);
}
iv_init();
IV_FD_INIT(&s.fd);
s.fd.fd = fd;
s.fd.cookie = (void *)&s;
s.fd.handler_in = listening_socket_handler;
iv_fd_register(&s.fd);
iv_main();
iv_deinit();
return 0;
}
as illustrated, it is possible to pass cookies into callback functions. this is useful for conveying information on which higher-level entity (such as a connection or a listening socket) generated the event for which the callback was called. iykyk π
and note how it's possible to unregister and even free a struct iv_fd in its own callback function. there is logic in ivykis specifically to deal with this case. she thought of everything, truly. π
β¨ EXAMPLE 3 β¨
Okay bestie, this one is example 2 but make it extra π
β we're adding a per-connection timer that yeets the client out the door after it's been too quiet for too long. Any line that wasn't in example 2, or is different from example 2, gets a little //XXXX in the right-hand margin so you can spot the glow-up. π¦
#include <stdio.h>
#include <stdlib.h>
#include <iv.h>
#include <netinet/in.h>
#define CONNECTION_TIMEOUT (10)
struct connection
{
struct iv_fd fd;
struct iv_timer disconnect_timeout; //XXXX
/* other per-connection data goes here */
};
struct listening_socket
{
struct iv_fd fd;
/* other per-listening socket data goes here */
};
static void connection_handler(void *_conn)
{
struct connection *conn = (struct connection *)_conn;
char buf[1024];
int len;
len = read(conn->fd.fd, buf, sizeof(buf));
if (len <= 0) {
if (len < 0 && errno == EAGAIN)
return;
iv_timer_unregister(&conn->disconnect_timeout); //XXXX
iv_fd_unregister(&conn->fd);
close(conn->fd.fd);
free(conn);
return;
}
printf("got %d bytes of data from %p\n", len, conn);
iv_timer_unregister(&conn->disconnect_timeout); //XXXX
iv_validate_now(); //XXXX
conn->disconnect_timeout.expires = iv_now; //XXXX
conn->disconnect_timeout.expires.tv_sec += CONNECTION_TIMEOUT;//XXXX
iv_timer_register(&conn->disconnect_timeout); //XXXX
}
static void disconnect_timeout_expired(void *_conn) //XXXX
{ //XXXX
struct connection *conn = (struct connection *)_conn; //XXXX
iv_fd_unregister(&conn->fd); //XXXX
close(conn->fd.fd); //XXXX
free(conn); //XXXX
} //XXXX
static void listening_socket_handler(void *_sock)
{
struct listening_socket *sock = (struct listening_socket *)_sock;
struct sockaddr_in addr;
socklen_t addrlen;
struct connection *conn;
int fd;
addrlen = sizeof(addr);
fd = accept(sock->fd.fd, (struct sockaddr *)&addr, &addrlen);
if (fd < 0) {
if (errno == EAGAIN)
return;
perror("accept");
exit(1);
}
conn = malloc(sizeof(*conn));
if (conn == NULL) {
fprintf(stderr, "listening_socket_handler: memory allocation error, dropping connection");
close(fd);
return;
}
IV_FD_INIT(&conn->fd);
conn->fd.fd = fd;
conn->fd.cookie = (void *)conn;
conn->fd.handler_in = connection_handler;
iv_fd_register(&conn->fd);
IV_TIMER_INIT(&conn->disconnect_timeout); //XXXX
iv_validate_now(); //XXXX
conn->disconnect_timeout.cookie = (void *)conn; //XXXX
conn->disconnect_timeout.handler = disconnect_timeout_expired;//XXXX
conn->disconnect_timeout.expires = iv_now; //XXXX
conn->disconnect_timeout.expires.tv_sec += CONNECTION_TIMEOUT;//XXXX
iv_timer_register(&conn->disconnect_timeout); //XXXX
}
int main()
{
struct listening_socket s;
struct sockaddr_in addr;
int fd;
fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
perror("socket");
exit(1);
}
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(6667);
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("bind");
exit(1);
}
if (listen(fd, 4) < 0) {
perror("listen");
exit(1);
}
iv_init();
IV_FD_INIT(&s.fd);
s.fd.fd = fd;
s.fd.cookie = (void *)&s;
s.fd.handler_in = listening_socket_handler;
iv_fd_register(&s.fd);
iv_main();
iv_deinit();
return 0;
}
β οΈ important lore alert β οΈ The global variableiv_nowis a monotonic timer. BUT β and this is the part everyone forgets β it gets updated lazily, so its contents might be straight-up stale at any given moment. π Before you use it, you must calliv_validate_now(3). No skipping. Not negotiable. Periodt. π
π EXAMPLE 4 π
Bestie, this one is a glow-up on the fatal-error situation β¨ β it shows you how to plug in a custom fatal error handler that does not write the message to syslog. Your message, your rules, your aesthetic. π
#include <stdio.h>
#include <iv.h>
static void fatal_error(const char *msg)
{
fprintf(stderr, "ivykis: FATAL ERROR: %s\n", msg);
}
int main()
{
iv_init();
iv_set_fatal_msg_handler(fatal_error);
iv_fatal("Programmatically triggered fatal error %d.", 42);
printf("This code is never reached.\n");
iv_deinit();
return 0;
}
So this program will abort immediately, with the error message printed straight to the standard error stream. That printf line? never reached. Not even once. π It's giving "we don't make it to the second verse".
π SEE ALSO π
Go read these next, fr fr β they're all iconic:
And that's a wrap on the whole page, bestie π«Ά This one came from the ivykis manual, dated 2003-03-29, and it's the ivykis(3) page itself. Slay, you read the whole thing. Now go write some event loops. πΈπ¦β¨
π¦ where this page comes from
- alpine 3.22: ivykis-doc 0.43.2-r0 Β· LGPL-2.1-or-later
- alpine 3.23: ivykis-doc 0.43.2-r0 Β· LGPL-2.1-or-later
- alpine 3.24: ivykis-doc 0.43.2-r0 Β· LGPL-2.1-or-later
- debian bookworm: libivykis-dev 0.42.4-1
- debian trixie: libivykis-dev 0.43.2-1+b1
- epel 10: ivykis-devel 0.43.2-2.el10_0 Β· LicenseRef-Callaway-LGPLv2+
- epel 9: ivykis-devel 0.42.4-7.el9 Β· LGPLv2+
- fedora 43: ivykis-devel 0.43.2-4.fc43 Β· LicenseRef-Callaway-LGPLv2+
- fedora 43: ivykis-devel 0.43.2-4.fc43 Β· LicenseRef-Callaway-LGPLv2+
- fedora 44: ivykis-devel 0.43.2-5.fc44 Β· LicenseRef-Callaway-LGPLv2+
- fedora 44: ivykis-devel 0.43.2-5.fc44 Β· LicenseRef-Callaway-LGPLv2+
- nixpkgs unstable: libivykis 0.43.2
- opensuse-leap 16.0: ivykis-devel 0.43-bp160.1.12 Β· LGPL-2.1-only
- opensuse-leap 16.0: ivykis-devel 0.43-bp160.1.12 Β· LGPL-2.1-only
- opensuse-leap 16.0: ivykis-devel 0.43-bp160.1.12 Β· LGPL-2.1-only
- opensuse-leap 16.0: ivykis-devel 0.43-bp160.1.12 Β· LGPL-2.1-only
- ubuntu jammy: libivykis-dev 0.42.4-1
- ubuntu noble: libivykis-dev 0.43-1build1
- ubuntu questing: libivykis-dev 0.43.2-1
- ubuntu resolute: libivykis-dev 0.43.2-1build1
- ubuntu stonking: libivykis-dev 0.43.2-1build1
- Copyright (C) 2003 Lennert Buytenhek
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms