Skip to content

libuv

The cross-platform async I/O library extracted from Node.js.

Networking & ConcurrencyNetworking / AsyncC

What it is

libuv is a multi-platform C library that provides asynchronous I/O, event loops, timers, and cross-platform abstractions for networking, filesystems, and concurrency. It is designed to support high-performance, scalable applications.

libuv allows developers to handle TCP/UDP sockets, files, and timers asynchronously using an event loop. It supports callbacks, handles, streams, and integrates well with C/C++ applications requiring high concurrency.

Licence
MIT
Powers
Node.js, Julia and Neovim

When to use it

The question documentation cannot answer for you — because it cannot recommend something else.

Reach for it when

  • Building an event-driven server or runtime in C
  • You need one abstraction over epoll, kqueue and IOCP

Look elsewhere when

  • A C++ project that could use Boost.Asio's higher-level, type-safe interface

Installation

sudo apt install libuv1-dev

Getting started

The smallest useful thing you can do with it, and what each part means.

Initializing the event loop
#include <uv.h>
#include <stdio.h>

int main() {
    uv_loop_t *loop = uv_default_loop();
    printf("Loop started\n");
    uv_run(loop, UV_RUN_DEFAULT);
    uv_loop_close(loop);
    return 0;
}
Initializes the default libuv event loop, runs it (though no events are scheduled), and closes it.
Using a timer
#include <uv.h>
#include <stdio.h>

void timer_cb(uv_timer_t* handle) {
    printf("Timer fired!\n");
}

int main() {
    uv_loop_t *loop = uv_default_loop();
    uv_timer_t timer_req;
    uv_timer_init(loop, &timer_req);
    uv_timer_start(&timer_req, timer_cb, 1000, 0); // Fire once after 1000ms
    uv_run(loop, UV_RUN_DEFAULT);
    uv_loop_close(loop);
    return 0;
}
Sets up a one-time timer that calls a callback after 1 second.

Advanced usage

Where the library earns its place over a simpler alternative.

TCP server
#include <uv.h>
#include <stdio.h>

void on_new_connection(uv_stream_t *server, int status) {
    if (status < 0) {
        fprintf(stderr, "New connection error: %s\n", uv_strerror(status));
        return;
    }
    printf("New client connected\n");
}

int main() {
    uv_loop_t *loop = uv_default_loop();
    uv_tcp_t server;
    uv_tcp_init(loop, &server);
    struct sockaddr_in addr;
    uv_ip4_addr("0.0.0.0", 7000, &addr);
    uv_tcp_bind(&server, (const struct sockaddr*)&addr, 0);
    int r = uv_listen((uv_stream_t*)&server, 128, on_new_connection);
    if (r) {
        fprintf(stderr, "Listen error: %s\n", uv_strerror(r));
        return 1;
    }
    uv_run(loop, UV_RUN_DEFAULT);
    uv_loop_close(loop);
    return 0;
}
Creates a basic TCP server that listens on port 7000 and prints a message when a client connects.
Asynchronous file read
#include <uv.h>
#include <stdio.h>
#include <stdlib.h>

void read_cb(uv_fs_t* req) {
    printf("File content: %s\n", (char*)req->bufs->base);
    uv_fs_req_cleanup(req);
}

int main() {
    uv_loop_t *loop = uv_default_loop();
    uv_fs_t open_req;
    uv_fs_open(loop, &open_req, "test.txt", O_RDONLY, 0, NULL);
    uv_fs_t read_req;
    char *buffer = malloc(1024);
    uv_buf_t iov = uv_buf_init(buffer, 1024);
    uv_fs_read(loop, &read_req, open_req.result, &iov, 1, 0, read_cb);
    uv_run(loop, UV_RUN_DEFAULT);
    uv_loop_close(loop);
    free(buffer);
    return 0;
}
Demonstrates asynchronous file reading using libuv.
UDP server
#include <uv.h>
#include <stdio.h>

void alloc_buffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) {
    buf->base = malloc(suggested_size);
    buf->len = suggested_size;
}

void on_read(uv_udp_t *req, ssize_t nread, const uv_buf_t *buf, const struct sockaddr *addr, unsigned flags) {
    if (nread > 0) printf("Received: %s\n", buf->base);
    free(buf->base);
}

int main() {
    uv_loop_t *loop = uv_default_loop();
    uv_udp_t recv_socket;
    uv_udp_init(loop, &recv_socket);
    struct sockaddr_in addr;
    uv_ip4_addr("0.0.0.0", 7000, &addr);
    uv_udp_bind(&recv_socket, (const struct sockaddr*)&addr, 0);
    uv_udp_recv_start(&recv_socket, alloc_buffer, on_read);
    uv_run(loop, UV_RUN_DEFAULT);
    uv_loop_close(loop);
    return 0;
}
Sets up a UDP server that asynchronously receives messages.

Errors and fixes

The failures you are most likely to hit, and what actually resolves them.

UV_EADDRINUSE
Occurs when trying to bind to an address/port already in use. Choose a free port or close conflicting applications.
UV_ENOENT
File or directory not found. Verify paths for filesystem operations.
UV_ECONNREFUSED
Connection refused on TCP/UDP. Check that the server is running and reachable.

Best practices

  • Always initialize and close the event loop properly.
  • Free memory for buffers and handles to avoid leaks.
  • Use callbacks to handle events asynchronously.
  • Leverage libuv timers and async handles for concurrency instead of threads.
  • Handle errors in callbacks to prevent crashing the loop.

Alternatives

Comparable options, and the reason you would pick one over the other.

Background

Why it exists, and what it was reacting to.

libuv was originally developed as part of Node.js to provide a consistent, event-driven, asynchronous I/O layer across different platforms. It is now used in many other projects that require efficient non-blocking I/O, including networking applications, servers, and frameworks.