Course Navigation
Module 1: Game Server Fundamentals
Module 2: State Synchronization
Module 3: Advanced Networking
Module 4: Scaling & Performance
Module 5: Game Systems
Module 6: Production & Deployment
Capstone Project
Network Protocols for Games
Learning Objectives
- • Understand why HTTP isn't suitable for real-time games
- • Master UDP vs TCP trade-offs for different game types
- • Learn message serialization with Protocol Buffers
- • Build UDP server with connection management
- • Handle packet loss and out-of-order delivery
Lesson 2.1: Why Not HTTP?
❌ Why HTTP Fails for Games
Developers new to games often ask: "Why not just use HTTP/REST like web apps?" Let's see why this approach fails for real-time games.
The HTTP Problem
Scenario: Player moves in game at 60 updates/sec
HTTP/REST Approach
T=0ms Client: POST /game/move {"x": 10, "y": 20}
└─> Network (50ms)
T=50ms Server receives, processes
T=60ms Server responds 200 OK with full game state
└─> Network (50ms)
T=110ms Client receives → Finally sees update Result: 110ms latency before player sees anything. Unplayable!
Why HTTP Fails for Games
- • Request/Response Model: Must wait for response
- • High Overhead: Headers, compression, parsing
- • No Streaming: Can't continuously send state
- • Polling: If you poll for state updates, you're constantly sending requests = huge waste
Lesson 2.2: TCP vs UDP Trade-offs
TCP (Transmission Control Protocol)
Characteristics:
- Connection-oriented (handshake required)
- Guaranteed delivery (retransmits lost packets)
- In-order delivery (packets arrive in sequence)
- Automatic congestion control
- Slower (overhead for guarantees)
Overhead per packet: 20 bytes minimum
Latency: Higher (retransmits, waits for ACK)
Best for: Turn-based games, MMO chat, file transfers
NOT good for: Fast-paced shooters Real-World Examples
- • Turn-based strategy (Chess.com)
- • Card games (Hearthstone)
- • Text chat in any game
- • Persistent messaging (trade requests, emails)
UDP (User Datagram Protocol)
Characteristics:
- Connectionless (no handshake)
- No delivery guarantee (sent, might be lost)
- No ordering guarantee (packets arrive out of order)
- No congestion control (app must handle)
- Faster (minimal overhead)
Overhead per packet: 8 bytes
Latency: Lower (send immediately)
Best for: Real-time shooters, fighting games
Tradeoff: App must handle loss/ordering Real-World Examples
- • FPS shooters (Valorant, CS:GO)
- • Battle royale (Fortnite)
- • Fighting games (Street Fighter)
- • Action games (any real-time)
Visual Comparison
TCP Timeline
T=0ms Client sends packet
└─> Network (50ms)
T=50ms Server receives
└─> Server responds ACK (50ms)
T=100ms Client receives ACK
└─> Can send next packet
Total: 100ms to send one update (too slow!) UDP Timeline
T=0ms Client sends packet
└─> Network (50ms)
T=50ms Server receives
(No waiting for ACK, immediately ready for next)
Total: 50ms latency (much better!) 💡 Hybrid Approaches
Most modern games use both protocols for different purposes:
TCP Used For:
- • Authentication
- • Lobby chat
- • Ranked queue
- • Inventory updates
UDP Used For:
- • In-game position
- • Shooting
- • Abilities
- • Animations
Lesson 2.3: Message Serialization
Format Comparison
| Format | Size | Speed | Ease | Network |
|---|---|---|---|---|
| JSON | 80B | Slow | Easy | Bandwidth hog |
| MessagePack | 30B | Fast | Medium | Good |
| Protobuf | 16B | Fast | Medium | Excellent |
| Binary | 14B | Fastest | Hard | Best |
Recommendation for Games
- • Prototype/development: JSON (easy debugging)
- • Production: Protocol Buffers (good balance)
- • Ultra-competitive (esports): Binary (every byte counts)
Protocol Buffers Example
syntax = "proto3";
package game;
// Player input from client
message PlayerInput {
int32 player_id = 1;
float move_x = 2; // -1 to 1
float move_y = 3; // -1 to 1
uint32 tick = 4; // Which tick this input is for
bool shoot = 5;
bool jump = 6;
uint32 target_player_id = 7;
}
// World update from server to client
message WorldUpdate {
uint32 tick = 1;
int64 server_time_ms = 2;
repeated ProjectileState projectiles = 4;
repeated Entity entities = 5;
}
message PlayerState {
int32 player_id = 1;
Vector3 position = 2;
Vector3 velocity = 3;
float rotation = 4;
int32 health = 5;
string animation = 6;
repeated StatusEffect effects = 7;
}
message Vector3 {
float x = 1;
float y = 2;
float z = 3;
} Usage in Go
import "game.pb" // Generated from protobuf
// Encode message
input := &game.PlayerInput{
PlayerId: 5,
MoveX: 0.5,
MoveY: 0.0,
Tick: 120,
Shoot: true,
}
// Convert to bytes
data, err := proto.Marshal(input)
if err != nil {
log.Fatal(err)
}
// Send over network
conn.WriteToUDP(data, remoteAddr)
// On receiving end
var receivedInput game.PlayerInput
err = proto.Unmarshal(packetData, &receivedInput)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Player %d moved (%.2f, %.2f)\n",
receivedInput.PlayerId,
receivedInput.MoveX,
receivedInput.MoveY) Lab 2: Build UDP Server with Broadcast
Objective
Build a UDP server that accepts connections from multiple clients, tracks connected clients, broadcasts messages to all connected clients, and handles timeouts.
Requirements
- • Accept UDP connections
- • Track multiple clients by IP:Port
- • Broadcast received messages to all clients
- • Detect timeouts and remove disconnected clients
- • Print stats every 5 seconds
Starter Code
package main
import (
"fmt"
"log"
"net"
"time"
)
type GameServer struct {
conn *net.UDPConn
clients map[int]*ClientConnection
clientsByAddr map[string]int
nextPlayerID int
disconnectTimeout time.Duration
ticker *time.Ticker
}
type ClientConnection struct {
PlayerID int
RemoteAddr *net.UDPAddr
LastPacketAt time.Time
IsConnected bool
}
// Broadcast message from one player to all others
type BroadcastMessage struct {
SenderID int
Message string
Timestamp int64
}
func NewGameServer(addr string, port int) (*GameServer, error) {
// TODO: Create UDP listener on addr:port
// Return GameServer or error if binding fails
return &GameServer{
clients: make(map[int]*ClientConnection),
clientsByAddr: make(map[string]int),
nextPlayerID: 1,
disconnectTimeout: 30 * time.Second,
}, nil
}
func (g *GameServer) Start() {
// TODO: Start listening loop
// 1. Read UDP packets in loop
// 2. For each packet: call HandlePacket()
}
func (g *GameServer) HandlePacket(data []byte, remoteAddr *net.UDPAddr) {
// TODO:
// 1. Check if sender is known (by IP:Port)
// 2. If new: assign PlayerID, register client
// 3. If known: update LastPacketAt
// 4. Parse message and broadcast to all
}
func (g *GameServer) Broadcast(message BroadcastMessage) {
// TODO:
// 1. Convert message to bytes
// 2. Send to all connected clients
// 3. Log broadcast
}
func (g *GameServer) CheckTimeouts() {
// TODO:
// 1. Check each client's LastPacketAt
// 2. If older than disconnectTimeout:
// - Remove from clients
// - Remove from clientsByAddr
// - Notify others that they left
}
func (g *GameServer) PrintStats() {
fmt.Printf("[%s] Connected: %d players\n",
time.Now().Format("15:04:05"),
len(g.clients))
for playerID, client := range g.clients {
fmt.Printf(" Player %d: %s (last: %.1fs ago)\n",
playerID,
client.RemoteAddr.String(),
time.Since(client.LastPacketAt).Seconds())
}
}
func main() {
server, err := NewGameServer("127.0.0.1", 9000)
if err != nil {
log.Fatal(err)
}
// Start server loop
go server.Start()
// Periodically check timeouts
go func() {
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
for range ticker.C {
server.CheckTimeouts()
server.PrintStats()
}
}()
// Run for 60 seconds
time.Sleep(60 * time.Second)
} Client Simulator (for testing)
// Run this in separate process to test server
func TestClient(id int, serverAddr string) {
conn, err := net.Dial("udp", serverAddr)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
// Send initial message
msg := fmt.Sprintf("Hello, I'm client %d", id)
conn.Write([]byte(msg))
// Send periodic messages
ticker := time.NewTicker(10 * time.Second)
for i := 0; i < 5; i++ {
<-ticker.C
msg := fmt.Sprintf("Message %d from client %d", i, id)
conn.Write([]byte(msg))
}
} Expected Output
[09:32:15] Connected: 2 players
Player 1: 127.0.0.1:54321 (last: 0.2s ago)
Player 2: 127.0.0.1:54322 (last: 0.1s ago)
[09:32:20] Connected: 2 players
Player 1: 127.0.0.1:54321 (last: 5.2s ago)
Player 2: 127.0.0.1:54322 (last: 5.1s ago)
[09:32:25] Connected: 1 players
Player 2: 127.0.0.1:54322 (last: 0.1s ago)
[Player 1 TIMED OUT] ✅ Success Criteria
- • Server accepts UDP connections on specified port
- • Tracks multiple clients by IP:Port combination
- • Broadcasts received messages to all connected clients
- • Detects timeouts and removes disconnected clients
- • Prints connection stats every 5 seconds
Ready for Week 3?
Next week we'll dive into state synchronization, client-side prediction, and keeping all players in sync while handling network issues.
Continue to Week 3: State Synchronization →