Skip to main content

Chapter 2: TCP Server

Now that we have a RESP parser, let’s build the network layer. We’ll create a TCP server that accepts client connections and processes commands concurrently. This is where theory meets reality. You will face the same design decisions that every network service author faces: how to handle multiple clients at once, how to keep shared state consistent, and how to shut down cleanly without dropping in-flight requests. Building a TCP server from scratch teaches you what frameworks like Express or Gin abstract away — and more importantly, why they make the choices they make.
Prerequisites: Chapter 1: RESP Protocol
Further Reading: Networking
Time: 2-3 hours
Outcome: A TCP server that responds to PING with PONG

Server Architecture


Why Go for Redis?

Go is perfect for building Redis because:
  1. Goroutines: Lightweight threads for handling thousands of connections
  2. Channels: Safe communication between goroutines
  3. net package: Excellent networking primitives
  4. Sync package: Mutexes, atomic operations for thread-safety
Real Redis is single-threaded by design. Salvatore Sanfilippo chose this because the bottleneck for an in-memory database is almost always network I/O, not CPU. A single-threaded event loop avoids all locking overhead and makes the codebase dramatically simpler. Our Go version uses goroutines (one per connection), which is more idiomatic Go, but we still serialize writes through a mutex to ensure consistency. This is a conscious trade-off: goroutines make the code easier to reason about at the connection level, while the mutex preserves the “commands are atomic” guarantee that Redis clients expect.

Implementation

Step 1: Server Structure

internal/server/server.go

Step 2: Client Handler

internal/server/client.go

Step 3: In-Memory Store

internal/server/store.go

Step 4: Register Commands

internal/server/commands.go

Step 5: Main Entry Point

cmd/server/main.go

Testing Your Server

Build and Run

Test with redis-cli


Exercises

Implement atomic increment/decrement:
Create a client connection pool:

Key Takeaways

Goroutine per Connection

Go makes concurrent connection handling simple and efficient

Thread-Safe Store

Use sync.RWMutex for concurrent read access, exclusive writes

Lazy Expiration

Check expiration on access + background cleanup

Command Routing

Map command names to handler functions for clean design

Further Reading

Concurrency Patterns

Learn more about concurrent programming patterns

Linux Networking

Understand TCP/IP at the kernel level

What’s Next?

In Chapter 3: Data Structures, we’ll implement:
  • Lists (LPUSH, RPUSH, LPOP, LRANGE)
  • Sets (SADD, SMEMBERS, SINTER)
  • Hashes (HSET, HGET, HGETALL)
  • Sorted Sets with skip lists

Next: Data Structures

Implement Redis data structures beyond strings