A server
Everything so far has been minidb in one process talking to itself. From here it is a server, and
it stays one for the rest of the day.
The protocol
One request per line, one response per line, all of it text:
SET users alice hello -> OK
GET users alice -> VALUE hello
GET users bob -> NIL
DEL users alice -> OK
PING -> ERR unknown verb PING
A text protocol is a workshop’s best friend. You can drive the whole server with nc localhost 7878
and read every byte that goes past, which means a failure is something you can look at rather than
something you have to instrument. Real systems pick differently: length-prefixed binary framing
avoids the delimiter problem entirely, and gRPC or Postgres wire format arrive with tooling. The
concurrency lessons are identical either way.
The one thing a line protocol needs from its types is a guarantee that a value cannot contain the
delimiter, which is why Value::parse rejects newlines and anything over 4 KiB. Parse at the edge
and the rest of the program cannot produce a line that does not round trip. src/protocol.rs has
the test that proves it.
Reading lines
Three pieces do the framing, and it is worth knowing each of them by name rather than as a block to copy:
#![allow(unused)]
fn main() {
tokio::io::split(stream); // -> (ReadHalf<S>, WriteHalf<S>)
BufReader::new(reader).lines(); // -> Lines<BufReader<R>>
lines.next_line().await; // -> io::Result<Option<String>>, no trailing newline
writer.write_all(bytes).await; // and the newline is yours to add back
}
BufReader matters for more than speed here. Without it, every read is a syscall, and with a
lines() wrapper on top it is a syscall per byte. It matters again in chapter 5 for a reason that
has nothing to do with performance: the buffer is what makes next_line safe to cancel.
tokio::io::split gives you a reader half and a writer half of the same stream, so a future reading
and a future writing can exist at the same time. For a TcpStream specifically, into_split gives
you owned halves that can be moved into separate tasks.
The binaries
From this chapter on, every exercise that changes the server carries two of them. This one does not: it is the protocol and nothing else, so there is nothing here to run. From the next exercise onwards:
cargo run # the server, on 127.0.0.1:7878
cargo run --bin client # a second terminal
The client sends one line per request and prints what comes back. It is worth actually running: the tests tell you the behaviour is right, and typing at the thing tells you what it feels like.
Exercise
The exercise for this section is located in 03_server/00_intro