Intermediate Projects
TCP Echo Server
A multi-client TCP echo and command server binds a TcpListener, accepts concurrent client connections across worker threads, enforces per-socket read/write timeouts, tracks active connection metrics with atomic counters, and processes framed commands (ECHO, UPPER, STATS, QUIT).
Concurrent TCP Server Architecture:
+------------------------------------+
| `TcpListener` (127.0.0.1:ephemeral)|
| `ServerStats` (`Arc<ServerStats>`) |
+------------------------------------+
|
+------------------------+------------------------+
| `accept()` | `accept()`
v v
[ Worker Thread 1 (`TcpStream`) ] [ Worker Thread 2 (`TcpStream`) ]
- `set_read_timeout(5s)` - `set_read_timeout(5s)`
- `BufReader::new(&stream)` - `BufReader::new(&stream)`
- Handles `ECHO`, `UPPER`, `STATS`, `QUIT` - Handles `ECHO`, `UPPER`, `STATS`, `QUIT`
Protocol Commands and Server Telemetry
| Client Command | Server Response | Side Effects on ServerStats |
|---|---|---|
ECHO <msg>\n |
<msg>\n |
Increments total_commands and bytes_echoed. |
UPPER <msg>\n |
<MSG IN UPPERCASE>\n |
Increments total_commands and bytes_echoed. |
STATS\n |
active=<N> cmds=<M> bytes=<B>\n |
Reads atomic telemetry counters in $O(1)$. |
QUIT\n |
BYE\n (and closes socket) |
Decrements active_connections via RAII ConnectionGuard. |
Complete Runnable Implementation
use std::io::{self, BufRead, BufReader, Write};
use std::net::{SocketAddr, TcpListener, TcpStream};
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use std::thread;
use std::time::Duration;
#[derive(Default)]
pub struct ServerStats {
pub active_connections: AtomicUsize,
pub total_commands: AtomicU64,
pub bytes_echoed: AtomicU64,
}
/// RAII guard that increments `active_connections` on connect and decrements it on disconnect!
struct ConnectionGuard {
stats: Arc<ServerStats>,
}
impl ConnectionGuard {
fn enter(stats: Arc<ServerStats>) -> Self {
stats.active_connections.fetch_add(1, Ordering::Relaxed);
Self { stats }
}
}
impl Drop for ConnectionGuard {
fn drop(&mut self) {
self.stats.active_connections.fetch_sub(1, Ordering::Relaxed);
}
}
pub fn handle_client(mut stream: TcpStream, stats: Arc<ServerStats>) -> io::Result<()> {
let _guard = ConnectionGuard::enter(Arc::clone(&stats));
stream.set_read_timeout(Some(Duration::from_secs(5)))?;
stream.set_write_timeout(Some(Duration::from_secs(5)))?;
stream.set_nodelay(true)?;
let mut reader = BufReader::new(&stream);
let mut line = String::new();
while reader.read_line(&mut line)? > 0 {
let trimmed = line.trim_end_matches(['\r', '\n']);
stats.total_commands.fetch_add(1, Ordering::Relaxed);
if trimmed == "QUIT" {
stream.write_all(b"BYE\n")?;
break;
} else if trimmed == "STATS" {
let reply = format!(
"active={} cmds={} bytes={}\n",
stats.active_connections.load(Ordering::Relaxed),
stats.total_commands.load(Ordering::Relaxed),
stats.bytes_echoed.load(Ordering::Relaxed),
);
stream.write_all(reply.as_bytes())?;
} else if let Some(rest) = trimmed.strip_prefix("UPPER ") {
let upper = format!("{}\n", rest.to_uppercase());
stats
.bytes_echoed
.fetch_add(rest.len() as u64, Ordering::Relaxed);
stream.write_all(upper.as_bytes())?;
} else {
let payload = trimmed.strip_prefix("ECHO ").unwrap_or(trimmed);
stats
.bytes_echoed
.fetch_add(payload.len() as u64, Ordering::Relaxed);
writeln!(stream, "{payload}")?;
}
line.clear();
}
Ok(())
}
fn run_test_session(addr: SocketAddr, requests: &[&str]) -> io::Result<Vec<String>> {
let mut stream = TcpStream::connect(addr)?;
let mut reader = BufReader::new(&stream);
let mut responses = Vec::new();
for &req in requests {
writeln!(stream, "{req}")?;
let mut reply = String::new();
reader.read_line(&mut reply)?;
responses.push(reply.trim_end().to_string());
}
Ok(responses)
}
fn main() -> io::Result<()> {
let listener = TcpListener::bind("127.0.0.1:0")?;
let server_addr = listener.local_addr()?;
let stats = Arc::new(ServerStats::default());
let stats_server = Arc::clone(&stats);
let server_thread = thread::spawn(move || -> io::Result<()> {
let mut workers = Vec::new();
for stream_res in listener.incoming().take(2) {
let stream = stream_res?;
let st = Arc::clone(&stats_server);
workers.push(thread::spawn(move || handle_client(stream, st)));
}
for w in workers {
w.join().expect("worker panicked")?;
}
Ok(())
});
// Session 1: ECHO and UPPER
let r1 = run_test_session(
server_addr,
&["ECHO hello world", "UPPER rust 2024", "QUIT"],
)?;
assert_eq!(r1, vec!["hello world", "RUST 2024", "BYE"]);
// Session 2: Query server telemetry via STATS
let r2 = run_test_session(server_addr, &["STATS", "QUIT"])?;
assert_eq!(r2[0], "active=1 cmds=4 bytes=20");
server_thread.join().expect("server panicked")?;
assert_eq!(stats.active_connections.load(Ordering::Relaxed), 0);
println!("Client 1 replies: {r1:?}");
println!("Client 2 stats: {}", r2[0]);
Ok(())
}
Client 1 replies: ["hello world", "RUST 2024", "BYE"]
Client 2 stats: active=1 cmds=4 bytes=20