#io-uring #linux-networking #async-io #async-networking #high #performance #zero-copy

libuio

A io_uring based async framework designed for high performance networking on linux

5 unstable releases

0.3.0 Jul 28, 2024
0.2.1 Jul 28, 2024
0.2.0 Jul 28, 2024
0.1.1 Jul 14, 2024
0.1.0 Jul 14, 2024

#1191 in Asynchronous

Apache-2.0

105KB
2K SLoC

libuio

Crates.io Version docs.rs GitHub branch check runs

A high performance linux specific async framework that is based on io_uring and supports zero-copy networking.


lib.rs:

libuio

This is a fully featured async framework designed to run on linux and tailored for high performance networking solutions. The implementation is inherently multi-threaded by design and leverages io_uring under the hood as a I/O driver. This allows for unprecedented efficiency gains over the likes of epoll, poll, and select. The package is split up into a handful of modules each handling specifc subset of the functionality needed.

For detailed examples see the examples directory in the root of this repository.

At a high level a simple TCP echo server works as you would expect:

use std::io;

use futures::StreamExt;

use libuio::net::TcpListener;

#[libuio::main]
async fn main() -> io::Result<()> {
    // Since we are demonstrating a TCP server, lets start by creating a new TcpListener that
    // is set to listen on [::]:9091 and have a connection backlog of 1024.
    let mut listener = TcpListener::with_outstanding("[::]", 9091, 1024)?;

    let mut buf = vec![0u8; 1024];

    println!("Listening on: {}", listener.local_addr());

    // Or we can grab a async stream of incoming connections, this is using the
    // opcode::AcceptMulti, which is a highly efficient implementation of the standard accept
    // loop. This will loop endlessly until dropped or there is an unrecoverable error.
    //
    // Note that you want to call incoming OUTSIDE of a loop like bellow, otherwise you will
    // be implicitly droping/recrating the incoming future which results in performance worse
    // than that of a a `listener.accept().await` loop would provide.
    let mut incoming = listener.incoming();
    while let Some(conn) = incoming.next().await {
        let mut conn = match conn {
            Ok(conn) => conn,
            Err(e) => {
                println!("Oh no we had an error: {}", e);
                continue;
            }
        };

        println!("Got connection from: {}", conn.peer_addr());

        let read = match conn.recv(buf.as_mut_slice()).await {
            Ok(ret) => ret,
            Err(e) => {
                println!("Failed to receive from client: {}", e);
                continue;
            }
        };

        let s = String::from_utf8_lossy(&buf[..read]);

        println!("Client request: {}", s);

        conn.send(&buf[..read])
            .await
            .expect("Failed to respond to client.");
    }
    Ok(())
}

Similarly here is an example TCP client interacting with the above server:

use std::{io, net::SocketAddr};

use libuio::net::TcpStream;

#[libuio::main]
async fn main() -> io::Result<()> {
    println!("Connecting to remote server.");

    let remote_addr: SocketAddr = "[::1]:9091".parse().unwrap();
    let mut client = TcpStream::new(false)?;

    // Connect to the defined remote host.
    client.connect(&remote_addr).await?;

    println!(
        "Connected to remote peer {}, local address: {}",
        client.peer_addr(),
        client.local_addr(),
    );

    // Send some data to the remote host.
    client.send("Hello from client!".as_bytes()).await?;

    // Now read back anything the server sent and then exit.
    let mut buf = vec![0u8; 1024];
    let read = client.recv(buf.as_mut_slice()).await?;

    let str = String::from_utf8_lossy(&buf[..read]);
    println!("Server response: {}", str);
    Ok(())
}

As the above example demonstrates this is almost a direct drop in replacement for std::net::TcpListener and std::net::TcpStream.

Dependencies

~3.5MB
~71K SLoC