A TLS-encrypted TCP chat server with a client CLI, Prattle demonstrates systems programming concepts such as async I/O, concurrent client handling, network protocol design, and graceful resource management.
- Features
- Tech Stack
- Architecture
- Commands
- Prerequisites
- Running the Server
- Connecting as a Client
- Running Tests
- Project Goals
- TLS Encryption: All client-server communication is encrypted using Rustls
- Concurrent Client Handling: Supports multiple simultaneous clients using both shared state and message passing in Tokio's async runtime
- Command System: Simple text-based protocol with commands for chatting, actions, and server queries
- Backpressure Handling: Recognizes slow clients and warns them when they fall behind
- Graceful Shutdown: Cleanly handles server shutdown with proper client notification and connection draining
- Strict Code Quality and Testing: Completely forbids
unsafe,unwrap, andexpectusing Clippy and includes a comprehensive test suite, with all checks enforced in CI
- Rust - Chosen for performance and concurrency safety
- Tokio - Async runtime for handling concurrent client connections
- Rustls - Modern TLS library for secure encryption
- Tracing - Structured logging for observability
The server uses a broadcast channel architecture where:
- The server accepts TLS connections and spawns a task per client
- Clients select unique usernames upon connecting
- Messages are broadcast through a
tokio::sync::broadcastchannel - Each client task concurrently manages receiving broadcasts, handling user input, and listening for the shutdown signal
- Graceful shutdown (via a separate broadcast channel) waits for two-way
close_notifywith timeouts, both per client and globally
/quit Leave the server
/help Show the help message
/who List online users
/action <action> Broadcast an action, e.g. /action waves
[anything else] Send a regular message
- For Nix users, the toolchain is included as a flake.
- Otherwise, install:
- The Rust toolchain
- The command runner Just (or manually run the commands in the
justfile)
The server binds to 127.0.0.1:8000 by default, which can be overridden with the BIND_ADDR environment variable. BIND_ADDR will automatically be read in from a .env file if present.
just serveSimply execute the command just to connect to the running server using the client CLI. As with the server, the BIND_ADDR environment variable will be read from .env if present, falling back to the same default:
justjust testThe suite of unit and integration tests includes spawning a server and simulating multiple concurrent clients to verify:
- Concurrent connection behavior
- Username validation and collision handling
- Command parsing and execution
- Multi-client broadcasting
- Graceful shutdown with edge cases
This project was built as a learning exercise to gain and demonstrate experience with:
- Lower-level async programming working directly with Tokio
- Networking concepts and protocol design
- TLS/cryptography in practice
- Rust's ownership model in a concurrent programming context