Network protocol in Rust
Semaphore is a length-prefixed binary message protocol built from scratch, with a framing codec you can read end to end, plus a TCP server and client that speak it.
pick a message, watch it become bytes, then decode those bytes one at a time
len 4 byte length prefix tag 1 byte message kind body the rest of the frame
The decoder checks the declared length against the frame cap before it allocates anything for the payload. A length prefix above the cap is rejected on the spot.
Every message on the wire is a 4 byte big-endian length prefix followed by exactly that many payload bytes. That is the entire framing layer.
Most people reach for a framework the moment two processes need to talk over a socket. Semaphore takes the other path, small enough to read in one sitting, with every layer a pure, unit-tested piece you can hold in your head.
A pure, dependency-free encoder and a streaming decoder that reassembles a frame correctly even when it arrives split across many reads, and rejects an oversize length prefix before allocating anything.
PING and PONG, plus a small key-value SET and GET request-response protocol encoded on top of frames, with a one-byte tag and bounds-checked bodies.
Plain std::net and OS threads, no async runtime. The server holds an in-memory map, the client issues blocking SET, GET, and PING calls.
The payload of a frame is a small typed protocol, a one-byte tag followed by a tag-specific body.
| Message | Tag | Body |
|---|---|---|
| PING | 0x01 | none |
| PONG | 0x02 | none |
| SET | 0x03 | key (u16-len string), value (u32-len bytes) |
| GET | 0x04 | key (u16-len string) |
| VALUE | 0x05 | value (u32-len bytes) |
| NOT_FOUND | 0x06 | none |
Start the server, then talk to it from the CLI or as a library.
cargo run -- serve --addr 127.0.0.1:7878 cargo run -- ping --addr 127.0.0.1:7878 cargo run -- set --addr 127.0.0.1:7878 name semaphore cargo run -- get --addr 127.0.0.1:7878 name