EP 02 / TCP CHAT SERVER · BUILD
TCP Chat Server: Async, Tokio, Broadcast
Jun 3, 2026 · 22 min
Build a multi-client chat server from scratch in Rust. Every client gets its own task; messages fan out over a broadcast channel. We use tokio::select! to multiplex reading from the socket and receiving from the broadcast channel simultaneously.
What this episode covers
- Async/await fundamentals with the tokio runtime
tokio::spawnand the future-vs-task distinctionbroadcast::channelfor fan-out messaging across taskstokio::select!for multiplexing multiple async futures- Handling lagged receivers and closed channels gracefully
Code
use std::net::SocketAddr;
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
net::{TcpListener, TcpStream},
sync::broadcast::{self, Receiver, Sender},
};
async fn handle_client(
mut socket: TcpStream,
addr: SocketAddr,
tx: Sender<(SocketAddr, String)>,
mut rx: Receiver<(SocketAddr, String)>,
) -> std::io::Result<()> {
let mut buffer = [0; 1024];
loop {
tokio::select! {
result = socket.read(&mut buffer) => {
let n = match result {
Ok(0) => return Ok(()),
Ok(n) => n,
Err(e) => {
eprintln!("read error from {addr}: {e}");
return Ok(());
}
};
let message = String::from_utf8_lossy(&buffer[..n]).into_owned();
println!("{addr}: {}", message.trim_end());
let _ = tx.send((addr, message));
}
result = rx.recv() => {
match result {
Ok((from, message)) if from != addr => {
let line = format!("{from}: {message}");
if socket.write_all(line.as_bytes()).await.is_err() {
return Ok(());
}
},
Ok(_) => {},
Err(broadcast::error::RecvError::Lagged(n)) => {
eprintln!("client {addr} lagged, missed {n} messages");
},
Err(broadcast::error::RecvError::Closed) => return Ok(()),
}
}
}
}
}
#[tokio::main]
async fn main() -> std::io::Result<()> {
let listener = TcpListener::bind("127.0.0.1:8081").await?;
let (tx, _) = broadcast::channel::<(SocketAddr, String)>(16);
println!("server is listening on {:?}", listener.local_addr()?);
loop {
let (socket, addr) = listener.accept().await?;
println!("client {addr} connected");
tokio::spawn(handle_client(socket, addr, tx.clone(), tx.subscribe()));
}
}