Concurrent Servers: Part 7 - Rust
This is part 7 in a series of posts on writing concurrent network servers. In this part, we discuss how the challenges described in earlier parts are tackled in the Rust programming language.
All posts in the series:
- Part 1 - Introduction
- Part 2 - Threads
- Part 3 - Event-driven
- Part 4 - libuv
- Part 5 - Redis case study
- Part 6 - Callbacks, Promises and async/await
- Part 7 - Rust (this part)
Several years have passed since the previous parts were published. I've recently went over them to make sure the information presented is still relevant and all the code samples build and run using modern toolchains. I strongly recommend reviewing the previous parts before reading this one.
This post assumes a basic familiarity with the Rust programming language. It will only explain Rust constructs when we encounter code that wouldn't appear in an introductory book or tutorial.
Setting the baseline - a sequential state machine server
The first few parts in the series focused on a socket server that implements a simple state machine protocol. See part 1 for a complete description of the protocol. Let's start by showing how this protocol is implemented in a basic sequential Rust server:
use async_socket_server::serve_connection;
use std::net::TcpListener;
fn main() -> std::io::Result<()> {
let port = match std::env::args().nth(1) {
Some(s) => s,
None => "9090".to_string(),
};
let addr = format!("127.0.0.1:{port}");
let listener = TcpListener::bind(addr)?;
println!("Serving on port {port}");
loop {
let (stream, addr) = listener.accept()?;
println!("connection received from {}", addr);
if let Err(e) = serve_connection(stream) {
eprintln!("error serving connection: {}", e);
} else {
println!("peer done {addr}");
}
}
}
With the function serve_connection defined as:
pub enum ProcessingState {
WaitForMsg,
InMsg,
}
pub fn serve_connection(mut stream: TcpStream) -> std::io::Result<()> {
stream.write_all(b"*")?;
let mut state = ProcessingState::WaitForMsg;
let mut buf = [0u8; 1024];
loop {
let n = stream.read(&mut buf)?;
if n == 0 {
// Connection closed by the client.
break;
}
for byte in &buf[..n] {
match state {
ProcessingState::WaitForMsg => {
if *byte == b'^' {
state = ProcessingState::InMsg;
}
}
ProcessingState::InMsg => {
if *byte == b'