Introduction

libuv is the event library at the core of Node.js. It is written in C and provides a cross-platform abstraction on top of Epoll in Linux, kqueue in BSD, and IOCP in Windows. In addition in comes with a thread pool and a lot of built in functions to provide asynchronous filesystem operations, all using the same basic interface. For anyone who has ever tried to tackle asynchronous IO using select(), libuv is the answer to your prayers. libuv was initially an abstraction on top of libev – which in turn is a bloat-free version of libevent – in order for Node.js to work on windows, as libev was unix only.

The learning curve for getting into libuv can be quite steep. There is a quite comprehensive tutorial here, but I found it to leave out a lot of the details making it a bit hard to follow. There is also a lot of information in the official documentation, but it requires that you have at least basic knowledge of libuv and works better as a reference manual.

This tutorial describes a very simple server that accepts connections and sends “Hello, World!” to each client before closing the connection.

libuv

All code in this tutorial is made using libuv 1.7.5. If it’s not already installed on your system, you can find instructions on github.

Code

The code for this program can be found on github.

To compile the hello world server, run:

$ make hello-server

You can test it by running:

$ ./hello-server

And then in another terminal, run:

$ nc localhost 1234

This should print “Hello, World!” in your terminal.

hello-world/server.c:

#include <stdio.h>
#include <stdlib.h>
#include <uv.h>

#define PORT 1234
#define BACKLOG 10

// Close client
void close_client(uv_handle_t *handle) {
    // Free client handle after connection has been closed
    free(handle);
}

// Write callback
void on_write(uv_write_t *req, int status) {
    // Check status
    if (status < 0) {
        fprintf(stderr, "Write failed: %s\n", uv_strerror(status));
    }

    // Close client hanlde
    uv_close((uv_handle_t*)req->handle, close_client);

    // Free request handle
    free(req);
}

// Callback for new connections
void new_connection(uv_stream_t *server, int status) {
    // Check status code, anything under 0 means an error.
    if (status < 0) {
        fprintf(stderr, "New connection error: %s\n", uv_strerror(status));
        return;
    }

    // Create handle for client
    uv_tcp_t *client = malloc(sizeof(*client));
    memset(client, 0, sizeof(*client));
    uv_tcp_init(server->loop, client);

    // Accept new connection
    if (uv_accept(server, (uv_stream_t*) client) == 0) {
        // Create write request handle
        uv_write_t *req = malloc(sizeof(*req));
        memset(req, 0, sizeof(*req));

        // Add a buffer with hello world
        char *s = "Hello, World!\n";
        uv_buf_t bufs[] = {uv_buf_init(s, (unsigned int)strlen(s))};

        // Write and call on_write callback when finished
        int ret = uv_write((uv_write_t*)req, (uv_stream_t*)client, bufs, 1, on_write);

        if (ret < 0) {
            fprintf(stderr, "Write error: %s\n", uv_strerror(ret));
            uv_close((uv_handle_t*)client, close_client);
        }
    }
    else {
        // Accept failed, closing client handle
        uv_close((uv_handle_t*)client, close_client);
    }

}

int main(void) {
    // Initialize the loop
    uv_loop_t *loop;
    loop = malloc(sizeof(uv_loop_t));
    uv_loop_init(loop);

    // Create sockaddr struct
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));

    // Convert ipv4 address and port into sockaddr struct
    uv_ip4_addr("0.0.0.0", PORT, &addr);

    // Set up tcp handle
    uv_tcp_t server;
    uv_tcp_init(loop, &server);

    // Bind to socket
    uv_tcp_bind(&server, (const struct sockaddr*)&addr, 0);

    // Listen on socket, run new_connection() on every new connection
    int ret = uv_listen((uv_stream_t*) &server, BACKLOG, new_connection);
    if (ret) {
        fprintf(stderr, "Listen error: %s\n", uv_strerror(ret));
        return 1;
    }

    // Start the loop
    uv_run(loop, UV_RUN_DEFAULT);

    // Close loop and shutdown
    uv_loop_close(loop);
    free(loop);
    return 0;
}

The code for the hello world server might look complicated, but it is for the most part just boiler plate. I’ll go through the most important bits part by part.

main()

Libuv provides it’s own functions in place of the normal unix socket functions. They work, for the most part, just like the normal ones, just that they work with handles and are cross-platform.

// Set up tcp handle
uv_tcp_t server;
uv_tcp_init(loop, &server);

Unix uses file descriptors for working with IO, libuv uses handles. For a TCP stream, use the uv_tcp_t handle, which is an extension of the uv_stream_t handle. Libuv use a crude form of struct inheritance, so that if you cast a uv_tcp_t as a uv_stream_t, you can access it’s members. You’ll be doing this a lot when working with libuv, as you see from the code.

uv_tcp_init():

int uv_tcp_init(uv_loop_t* loop, uv_tcp_t* handle)

Use uv_tcp_init() to initialize the handle. Each handle is tied to a specified loop and may not be used with other loops.

// Listen on socket, run new_connection() on every new connection
int ret = uv_listen((uv_stream_t*) &server, BACKLOG, new_connection);
if (ret) {
    fprintf(stderr, "Listen error: %s\n", uv_strerror(ret));
    return 1;
}

uv_listen():

int uv_listen(uv_stream_t* stream, int backlog, uv_connection_cb cb)

To listen for incoming connections, uv_listen() is used with the server handle, casted as a uv_stream_t pointer, as uv_listen() works with all kinds of streams, not just TCP streams. It also takes a backlog argument, just as with the normal listen(), and a callback function pointer. listen() is not a blocking call, so it’s not really the listen() call that is asynchronous. The callback is called when the socket connected to the server handle is ready for reading, which means there is a new connection.

uv_connection_cb:

void (*uv_connection_cb)(uv_stream_t* server, int status)

The callback takes two arguments: the server handle and the return status of the call.

// Start the loop
uv_run(loop, UV_RUN_DEFAULT);

uv_listen() doesn’t call the callback by itself, the callback is called by the event loop.

uv_run():

int uv_run(uv_loop_t* loop, uv_run_mode mode)

The second argument to uv_run() is the run mode. UV_RUN_DEFAULT means that the loop will run until there are no more active handles or requests. See the docs for information on the different run modes.

new_connection()

// Check status code, anything under 0 means an error.
if (status < 0) {
    fprintf(stderr, "New connection error: %s\n", uv_strerror(status));
    return;
}

The error handling in libuv is similar to the normal C error handling using errno, except that the error codes is put directly into the status code. If status is less than 0, there’s an error, and status is equal to -errno, that is, errno negated. uv_strerror() returns a message similar to the one returned by strerror().

// Create handle for client
uv_tcp_t *client = malloc(sizeof(*client));
memset(client, 0, sizeof(*client));
uv_tcp_init(server->loop, client);

Each client connection needs a separate handle. The loop can be accessed through the server handle.

// Create write request handle
uv_write_t *req = malloc(sizeof(*req));
memset(req, 0, sizeof(*req));

// Add a buffer with hello world
char *s = "Hello, World!\n";
uv_buf_t bufs[] = {uv_buf_init(s, (unsigned int)strlen(s))};

// Write and call on_write callback when finished
int ret = uv_write((uv_write_t*)req, (uv_stream_t*)client, bufs, 1, on_write);

if (ret < 0) {
    fprintf(stderr, "Write error: %s\n", uv_strerror(ret));
    uv_close((uv_handle_t*)client, close_client);
}

To make a write request, create a request handle. There’s no need to initialize this, that is taken care of inside uv_write().

uv_write():

int uv_write(uv_write_t* req, uv_stream_t* handle, const uv_buf_t bufs[], unsigned int nbufs, uv_write_cb cb)

uv_write() also takes an array of uv_buf_t structs, which has two members: base, a char pointer to a buffer, and len, an unsigned int with the length of the buffer. Use uv_buf_init() to create a uv_buf_t from the string literal.

uv_buf_init():

uv_buf_t uv_buf_init(char* base, unsigned int len)

You might notice that bufs[] is declared on the stack. This might seem odd because bufs[] may not be reachable by the time the write request is executed. The reason this works is because bufs[] is copied into a heap by uv_write(). Not sure why it’s done in this way, but I’m guessing it’s for a good reason. This means that you don’t have to bother with freeing bufs[] afterwards. However, you do have to free what buf->base points to, if heap allocated. But in this program, using a string literal, there’s one less free to worry about. The freeing of the request handle and the buf->base, if applicable, should be done in the write callback.

And that’s about it. I encourage playing around with it a bit, trying to extend it to doing a little more than just send “Hello, World!” to the clients. The official libuv documentation is highly recommended reading as it is a really good reference for all the different functions and structures of lib, in addition to shedding some light on the overall architecture and design.