TCP server with tokio::spawn

Three lines of substance: TcpListener::bind opens the port. accept loops. tokio::spawn launches a task per connection. Done.

01-tcp-echo/src/main.rs
rust
#[tokio::main]
async fn main() -> io::Result<()> {
    let listener = TcpListener::bind("127.0.0.1:6380").await?;
    println!("echo server listening on 127.0.0.1:6380");
    loop {
        let (socket, _addr) = listener.accept().await?;
        tokio::spawn(async move { let _ = handle_conn(socket).await; });
    }
}

async fn handle_conn(mut socket: TcpStream) -> io::Result<()> {
    let mut buf = [0u8; 4096];
    loop {
        let n = socket.read(&mut buf).await?;
        if n == 0 { return Ok(()); }
        socket.write_all(&buf[..n]).await?;
    }
}

The entire echo server. tokio tasks are cheap (~64 bytes), so per-conn spawning is the standard idiom.

Both spawn a unit of work per connection. Go goroutines are ~2 KB stacks scheduled by the Go runtime; tokio tasks are state machines compiled from async fn, much smaller (~64 bytes), scheduled by the tokio runtime. The architecture is identical; tokio is closer to a stackless coroutine model.

Quiz: Quiz

Loading practice…