Recipe
Serve a line protocol over TCP with threads
Use when: clients send text commands, one per line, and each command gets a reply
Goal: a server that answers several clients at once, and a way to test it in one program
Taught in A TCP client and server
Quick recipe
-
1
TcpListener::bind("127.0.0.1:0")in examples and tests, or a configured address in production; read the real address withlocal_addr().Example
The bed-temperature server starts with
let listener = TcpListener::bind("127.0.0.1:0")?;and thenlet addr = listener.local_addr()?;. The operating system picks a free port, andaddris what eachConsoleconnects to.Why: port 0 can never collide with another program or test.
-
2
for stream in listener.incoming()yields oneTcpStreamper client.Example
for stream in listener.incoming()gives one item per client.let Ok(stream) = stream else { continue };skips an accept that failed, and keeps serving the rest.Why: the listener keeps listening; each stream is one conversation.
-
3
thread::spawn(move || handle(stream)), or send the stream to a fixed set of workers over a channel.Example
With
handle(stream)called right in the loop, client B'sTEMP 2gets(no reply within 200 ms), because the thread is busy with A. Withthread::spawn(move || handle(stream)), the same program printsB: bed 2: 21.5 C.- a small, trusted set of clients — one thread per connection.
- many or untrusted clients — a fixed number of workers, or async (Section 4).
Why: the accept loop must get back to
acceptat once, or other clients wait. -
4
let mut writer = stream.try_clone()?;thenBufReader::new(stream).Example
handlestarts withlet mut writer = stream.try_clone()?;and then loops overBufReader::new(stream).lines(). The reader gets the original stream, andwriteris the second handle.Why: the
BufReaderowns one handle for reading lines; the clone writes replies. -
5
for line in reader.lines(): parse the command, thenwriteln!(writer, ...).Example
The line
TEMP 1comes in.writeln!(writer, "bed {}: 21.5 C", line.trim_start_matches("TEMP "))sends backbed 1: 21.5 C, one line for one command.Why: lines are the framing, so each iteration is exactly one command however the bytes arrived.
-
6
Stop on your quit command, or when
lines()ends because the client closed the connection.Example
On
QUIT,handlewritesBYEandbreaks.handlereturns, and both handles are dropped, which closes the connection. If the client closes first withoutQUIT,lines()simply ends, and the loop stops the same way.Why: dropping the stream closes the connection;
readreturningOk(0)means the client left.
Common mistakes
- Calling
handledirectly in the accept loop, so one client blocks all the others. - Treating each
readas one whole command. - Reading from the raw stream after wrapping it in a
BufReader, which skips buffered bytes. - A hard-coded port in tests, so tests running at once collide.