A real-time TCP chat server and client written in Go.
Sync is a multi-client chat system that runs over raw TCP. It supports username-based identity, session tokens for reconnection, message history with WAL persistence, direct messages, user listings, per-user stats, and graceful shutdown.
| Aspect | Status |
|---|---|
| Build | Compiles (go build ./... passes, go vet clean) |
| Server | Implemented — listens on :9000 |
| Client | Implemented — connects to :9000 (localhost only) |
| Persistence | WAL + snapshot implemented |
| CLI | cobra root command scaffolded but not wired up |
| Tests | TestBroadcast exists but fails |
| Docker | Not yet available |
- Multi-client TCP chat — concurrent clients via goroutines
- Username registration — with
Guest<random>fallback - Direct messages —
/msg <user> <message> - User listing —
/userswith idle status, message count, uptime - Message history —
/history [N](last N messages, capped at 100) - Per-user stats —
/stats(messages sent/received, last active) - Session tokens — reconnect with
reconnect:<user>:<token>, 1-hour TTL - WAL persistence — messages appended to
messages.wal - Snapshot persistence — JSON snapshots every 5 min (when >100 messages) and on shutdown
- Crash recovery — restores state from snapshot/WAL on startup
- Graceful shutdown — SIGINT/SIGTERM handling
- Inactive client cleanup — 30s tick, 5 min timeout
- Read timeouts — 30s for username, 5 min for messages
- Panic recovery — per-goroutine
recover()guards - Slow-client simulation — ~10% of clients get artificial write delays
- Go 1.25 or later
# Build both server and client
go build ./...
# Build individually
go build ./cmd/server
go build ./cmd/client# Start the server (listens on :9000)
./server
# Connect as a client (in another terminal)
./clientStart the server with no arguments:
./serverThe server listens on :9000 (all interfaces) and stores chat data in ./chatdata/.
Connect to the server:
./clientOnce connected, you'll be prompted for a username. The following commands are available in the chat:
| Command | Description |
|---|---|
/msg <user> <message> |
Send a direct message |
/users |
List connected users (with idle status, totals, uptime) |
/history [N] |
Show last N messages |
/stats |
Show messages sent/received and last-active time |
/token |
Show your reconnect token |
/quit |
Leave the chat |
| Any other text | Broadcast to all users |
If you disconnect, you can reconnect with your previous identity:
reconnect:<username>:<token>
Your session token is displayed when you first connect, and can be shown again any time with /token.
├── cmd/
│ ├── root.go # cobra root command (not yet wired to binaries)
│ ├── server/main.go # Server entry point → room.StartServer()
│ └── client/main.go # Client entry point → room.StartClient()
├── internal/
│ └── room/
│ ├── types.go # Core types (Message, Client, Room, etc.)
│ ├── server.go # Room init, Run() event loop, shutdown
│ ├── handler.go # Broadcast, join/leave, history, user list, DMs
│ ├── io.go # TCP I/O, command parsing, /commands
│ ├── client.go # Interactive TCP client
│ ├── session.go # Session creation, reconnection tokens, cleanup
│ ├── persist.go # WAL + snapshot persistence
│ ├── startServer.go # StartServer() wrapper
│ └── room_test.go # Broadcast test (currently failing)
├── pkg/
│ └── token/
│ └── token.go # Crypto-random hex token generator
├── chatdata/ # Runtime data (WAL + snapshots)
├── go.mod
└── README.md
The Room.Run() method runs a central event loop that processes messages from connected clients and handles channel operations sequentially in a single goroutine, avoiding concurrency issues in message processing.
Messages are written to a Write-Ahead Log (messages.wal) as JSON lines. Every 5 minutes (when more than 100 messages exist) or at shutdown, a snapshot is saved to snapshot.json. On startup, the server loads the most recent snapshot and replays any remaining WAL entries.
Each client gets a cryptographically random 32-character hex token on connect. Sessions have a 1-hour TTL and are tracked with activity timestamps. Clients that are inactive for 5 minutes are removed.
TestBroadcastfails — the test itself has bugs: typo"Hellow"instead of"Hello!", wrong error messages (first assertion reports "Client1 didn't recieve" while reading from client1 correctly but the fallback says "Client2"), and typos (recieve,CLient2)- Client connects to
:9000only —client.go:12hardcodes localhost, so the client cannot connect to a remote server (cross-device use not yet supported) - cobra root command not wired —
cmd/root.godefines asynccommand but neither binary uses it - Typos throughout —
Recieved,persistance,actvity,recoverd, etc. (server output only, not user-facing) chatdata/andtestdata/not gitignored — runtime data shows up ingit status
The project has a detailed future plan (see plan.md, which is gitignored). Key milestones: config/CI/Docker, WebSocket/SSE transports, channel-based pub/sub, client SDKs, resilience, horizontal scaling with Redis, observability, E2E encryption, CLI tool, and performance work.
- spf13/cobra v1.10.2 — CLI scaffolding (root command not yet wired up)
- Go standard library — everything else (
net,bufio,sync,crypto/rand,encoding/json,os/signal, etc.)
This project is not yet licensed.