← Week 1: Tokio Fundamentals

Day 7: Challenge — Concurrent TCP Echo Server

Phase 2 · Jun 16, 2026

← Week 1: Tokio Fundamentals

Challenge Overview

Build a production-quality TCP echo server in Rust using Tokio.

Requirements:

  1. Accept concurrent connections on port 8080
  2. Limit to 100 concurrent connections using a semaphore
  3. Echo lines back prefixed with the connection's remote address
  4. Timeout idle connections after 30 seconds
  5. Graceful shutdown on Ctrl+C — stop accepting, wait for active connections to finish
  6. Log connection count, bytes echoed, and errors using the tracing crate
← Week 1: Tokio Fundamentals

Key Components to Use

use tokio::net::TcpListener;
use tokio::io::{BufReader, AsyncBufReadExt, AsyncWriteExt};
use tokio::sync::Semaphore;
use tokio::time::{timeout, Duration};
use tokio::task::JoinSet;
use tokio_util::sync::CancellationToken;
use tracing::{info, error, instrument};
← Week 1: Tokio Fundamentals

Suggested Structure

async fn accept_loop(
    listener: TcpListener,
    semaphore: Arc<Semaphore>,  // connection limit
    shutdown: CancellationToken,
) {
    let mut set = JoinSet::new();
    loop {
        tokio::select! {
            // Acquire permit before accepting (backpressure)
            permit = semaphore.acquire_owned() => {
                let (stream, addr) = listener.accept().await.unwrap();
                set.spawn(handle_conn(stream, addr, permit));
            }
            Some(_) = set.join_next() => {}
            _ = shutdown.cancelled() => break,
        }
    }
    set.shutdown().await;
}
← Week 1: Tokio Fundamentals

Connection Handler

async fn handle_conn(
    stream: TcpStream,
    addr: SocketAddr,
    _permit: OwnedSemaphorePermit, // dropped when task ends
) {
    let result = timeout(
        Duration::from_secs(30),
        echo_lines(stream, addr),
    ).await;

    match result {
        Ok(Ok(bytes)) => info!(%addr, bytes, "connection closed"),
        Ok(Err(e)) => error!(%addr, %e, "connection error"),
        Err(_) => info!(%addr, "connection timed out"),
    }
}
← Week 1: Tokio Fundamentals

Testing

# Terminal 1: run the server
cargo run

# Terminal 2: test echo
nc 127.0.0.1 8080
hello
# → [127.0.0.1:54321] hello

# Terminal 3: load test (100 connections)
for i in $(seq 1 100); do
    echo "connection $i" | nc -q1 127.0.0.1 8080 &
done
wait

# Terminal 2: verify graceful shutdown
# Press Ctrl+C in Terminal 1 while connections are active
# Confirm server waits for active connections before exiting
← Week 1: Tokio Fundamentals

Week 1 Recap

Topic Key API
Futures Future::poll, async/await, Pin
Runtime tokio::runtime, work-stealing, spawn_blocking
I/O TcpListener, AsyncReadExt, BufReader, into_split
Channels mpsc, oneshot, broadcast, watch
Timers sleep, timeout, interval, select!
Lifecycle JoinSet, CancellationToken, structured concurrency

Next week: Tower — typed, composable middleware for network services.