Picoev

A tiny, lightning fast event loop for network applications

History

Picoev was created as the event loop for MyCached (a memcached daemon for mysql servers). It is tiny, fast, plain C and crossplatform. It uses epoll, poll, select or kqueue, depending on the platform.

FAQ

What is it?

eventloop.

How cool is it?

Very.

Competitors?

libevent, libev, libuv

Where is it?

https://github.com/kazuho/picoev/

When to use it?

When you want to have a blazing fast eventloop with some abstraction for kqueue, epoll, poll, select.

Is it dead?

Development is stopped, it seems that the author is focussed on H2O.

How to use it

Download the repository

file: 1_download.sh

#!/bin/bash

mkdir -p tmp
cd tmp
git clone https://github.com/kazuho/picoev.git
cd picoev
make CC=gcc LINUX_BUILD=1 CC_RELEASE_FLAGS=-O2 CC_DEBUG_FLAGS=-g

Init

In this example, we create a listening socket, but a client connection works more or less the same.

file: nonblocking.c

#include "picoev.h"

#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>


static void setup_sock(int fd) {
    int on = 1, r;

    r = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on));
    if (r == 0) {
        r = fcntl(fd, F_SETFL, O_NONBLOCK);
    }
}

static void close_sock(picoev_loop* loop, int fd) {
    picoev_del(loop, fd);
    close(fd);
}

Add the rw_callback

file: rw_callback.c

#define TIMEOUT_SECS 10

static void rw_callback(picoev_loop* loop, int fd, int events, void* cb_arg) {
    if ((events & PICOEV_TIMEOUT) != 0) {
        close_sock(loop, fd);
    } else if ((events & PICOEV_READ) != 0) {
        char buf[1024];
        ssize_t r;

        picoev_set_timeout(loop, fd, TIMEOUT_SECS);

        r = read(fd, buf, sizeof(buf));
        switch (r) {
        case 0:
            /* connection closed by peer */
            close_sock(loop, fd);
            break;
        case -1: /* error */
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                /* try again later */
                break;
            } else { /* fatal error */
                close_sock(loop, fd);
            }
            break;
        default: /* got some data, send back */
            if (write(fd, buf, r) != r) {
                close_sock(loop, fd); /* failed to send all data at once, close */
            }
            break;
        }
    }
}

For writing or read-and-write the same principles could have been used.

start listinging

file: start_listening.c

#define HOST 0 /* 0x7f000001 for localhost */
#define PORT 23456

int start_listening() {
    int listen_sock, flag;

    if ((listen_sock = socket(AF_INET, SOCK_STREAM, 0)) != -1) {
        flag = 1;
        if (setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)) == 0) {
            struct sockaddr_in listen_addr;

            listen_addr.sin_family = AF_INET;
            listen_addr.sin_port = htons(PORT);
            listen_addr.sin_addr.s_addr = htonl(HOST);
            if (bind(listen_sock, (struct sockaddr*)&listen_addr, sizeof(listen_addr)) == 0) {
                if (listen(listen_sock, 5) == 0) {
                    setup_sock(listen_sock);
                }
            }
        }
    }
    return listen_sock;
}

Add the accept_callback

The accept callback takes a new filedescriptor and adds a rw_callback to it.

When the listening socket is connected to a client, we'll set the new filedescriptor to non blocking. And we add this new filedescriptor also to the eventloop, in this case for reading.

file: accept_callback.c

static void accept_callback(picoev_loop* loop, int fd, int events, void* cb_arg) {
    int newfd = accept(fd, NULL, NULL);

    if (newfd != -1) {
        setup_sock(newfd);
        picoev_add(loop, newfd, PICOEV_READ, TIMEOUT_SECS, rw_callback, NULL);
    }
}

Loop

file: start_loop.c

void start_loop(picoev_loop * loop) {
    int listen_sock = start_listening();
    picoev_add(loop, listen_sock, PICOEV_READ, 0, accept_callback, NULL);
}

file: run_loop.c

void run_loop(picoev_loop * loop) {
    while (1) {
        picoev_loop_once(loop, 10);
    }
}

file: main.c

#define MAX_FDS 1024

int main(const int argc, const char ** argv ) {
    picoev_init(MAX_FDS);

    picoev_loop* loop = picoev_create_loop(60);

    start_loop(loop);
    run_loop(loop);

    picoev_destroy_loop(loop);

    picoev_deinit();
    return 0;
}

The full file would look like this:

file: example_full.c

#include "picoev.h"

#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>


static void setup_sock(int fd) {
    int on = 1, r;

    r = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on));
    if (r == 0) {
        r = fcntl(fd, F_SETFL, O_NONBLOCK);
    }
}

static void close_sock(picoev_loop* loop, int fd) {
    picoev_del(loop, fd);
    close(fd);
}

#define TIMEOUT_SECS 10

static void rw_callback(picoev_loop* loop, int fd, int events, void* cb_arg) {
    if ((events & PICOEV_TIMEOUT) != 0) {
        close_sock(loop, fd);
    } else if ((events & PICOEV_READ) != 0) {
        char buf[1024];
        ssize_t r;

        picoev_set_timeout(loop, fd, TIMEOUT_SECS);

        r = read(fd, buf, sizeof(buf));
        switch (r) {
        case 0:
            /* connection closed by peer */
            close_sock(loop, fd);
            break;
        case -1: /* error */
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                /* try again later */
                break;
            } else { /* fatal error */
                close_sock(loop, fd);
            }
            break;
        default: /* got some data, send back */
            if (write(fd, buf, r) != r) {
                close_sock(loop, fd); /* failed to send all data at once, close */
            }
            break;
        }
    }
}

#define HOST 0 /* 0x7f000001 for localhost */
#define PORT 23456

int start_listening() {
    int listen_sock, flag;

    if ((listen_sock = socket(AF_INET, SOCK_STREAM, 0)) != -1) {
        flag = 1;
        if (setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)) == 0) {
            struct sockaddr_in listen_addr;

            listen_addr.sin_family = AF_INET;
            listen_addr.sin_port = htons(PORT);
            listen_addr.sin_addr.s_addr = htonl(HOST);
            if (bind(listen_sock, (struct sockaddr*)&listen_addr, sizeof(listen_addr)) == 0) {
                if (listen(listen_sock, 5) == 0) {
                    setup_sock(listen_sock);
                }
            }
        }
    }
    return listen_sock;
}
static void accept_callback(picoev_loop* loop, int fd, int events, void* cb_arg) {
    int newfd = accept(fd, NULL, NULL);

    if (newfd != -1) {
        setup_sock(newfd);
        picoev_add(loop, newfd, PICOEV_READ, TIMEOUT_SECS, rw_callback, NULL);
    }
}
void start_loop(picoev_loop * loop) {
    int listen_sock = start_listening();
    picoev_add(loop, listen_sock, PICOEV_READ, 0, accept_callback, NULL);
}

void run_loop(picoev_loop * loop) {
    while (1) {
        picoev_loop_once(loop, 10);
    }
}
#define MAX_FDS 1024

int main(const int argc, const char ** argv ) {
    picoev_init(MAX_FDS);

    picoev_loop* loop = picoev_create_loop(60);

    start_loop(loop);
    run_loop(loop);

    picoev_destroy_loop(loop);

    picoev_deinit();
    return 0;
}

file: 2_compile_and_run.sh

#!/bin/bash

clang \
    -Wall -Wextra -Weverything \
        -Wno-unused-parameter \
        -Wno-missing-prototypes \
        -Wno-padded \
        -Wno-missing-noreturn \
        -Wno-sign-conversion \
        -Wno-shorten-64-to-32 \
        -Wno-conversion \
    -o ./tmp/example \
    example.c \
    -I./tmp/picoev/ \
    ./tmp/picoev/libpicoev.a
./tmp/example

Full demo

Download A full example

file: 3_full_example.sh

#!/bin/bash

clang \
    -Wall -Wextra -Weverything \
    -o ./tmp/picoev_main \
    ./tmp/picoev/example/picoev_echo.c 
    -I./tmp/picoev/ \
    ./tmp/picoev/libpicoev.a
./tmp/picoev_main

And in another session

telnet 0.0.0.0 23456
Trying 0.0.0.0...
Connected to 0.0.0.0.
Escape character is '^]'.
123
123