Skip to content

Real-time protocols — WebSocket, TCP, and UDP for games

The choice of network protocol shapes every aspect of how a game performs under load — and how you test it. This page explains the trade-offs between WebSocket, raw TCP, and UDP for game backends, what MaxoPerf and k6 support natively, and the practical workarounds when you need to test protocols outside that envelope.

WebSocket is a full-duplex, message-oriented protocol built on TCP. The connection starts as an HTTP/1.1 upgrade handshake (the 101 Switching Protocols response) and then becomes a persistent, low-overhead binary or text channel.

Games and real-time web applications use WebSocket because:

  • It works through firewalls and proxies that block raw TCP on arbitrary ports.
  • The browser WebSocket API is universally available — no plugin, no native app required.
  • Framing and masking are handled by the protocol, so application code works with messages, not streams.
  • TLS (wss://) is transparent — the same TLS termination as HTTPS.

WebSocket is the right choice for browser-based games, mobile games that go through an HTTP proxy, and any real-time service that must traverse corporate firewalls.

Raw TCP is lower latency than WebSocket when stripping the HTTP upgrade overhead matters (typically < 100 μs), and it gives the server full control over framing. Game engines like Valve’s Source SDK, most dedicated game-server frameworks, and authoritative simulation servers often use raw TCP on a custom port.

Load-testing raw TCP requires a custom k6 extension or a JMeter TCP sampler. MaxoPerf runs standard k6 and Taurus/JMeter — the JMeter TCP Sampler works, but requires a JMX file with the sampler configured for your protocol’s framing. This is supported but requires more authoring effort than the k6 WebSocket API.

UDP is the protocol of choice for latency-critical, loss-tolerant game traffic: player position updates, hit detection, real-time audio, and any payload where a dropped packet is better than a delayed one. Most competitive first-person shooters, racing games, and battle royale titles use a custom UDP-based protocol (often QUIC, ENet, or a bespoke reliability layer on top of raw UDP).

UDP load testing is hard for a fundamental reason: standard load-testing tools — including k6, JMeter, Taurus, and the MaxoPerf runner fleet — operate at the TCP/HTTP/WebSocket layer. Generating meaningful UDP load requires either:

  1. A custom k6 xk6 extension compiled with UDP support (not available in the standard MaxoPerf runner image).
  2. A dedicated UDP load-testing tool (e.g. iperf3, netperf, or a game-engine-specific bench tool) running on infrastructure you provision.
  3. Testing the HTTP/WebSocket control plane separately and using UDP simulation tooling for the data plane.
ProtocolMaxoPerf supportNotes
WebSocket (ws://, wss://)Fullk6 k6/ws module; long-lived connections, bidirectional messages, custom metrics
HTTP/1.1 and HTTP/2Fullk6 k6/http; REST, gRPC-web, SSE
gRPC (HTTP/2)Fullk6 k6/net/grpc module
TCP (raw)PartialJMeter TCP Sampler in a .jmx entrypoint
UDPNot nativelyWorkarounds described below
QUIC / HTTP/3ExperimentalServer-dependent; test via k6 HTTP if server negotiates

For browser-based or mobile games using WebSocket, k6 on MaxoPerf is the most productive approach. The following minimal script models one player session:

import ws from 'k6/ws';
import { check } from 'k6';
import { Trend } from 'k6/metrics';
const tickLatency = new Trend('game_tick_latency_ms');
export const options = {
vus: 200,
duration: '5m',
thresholds: {
ws_connecting: ['p(95)<200'],
game_tick_latency_ms: ['p(95)<80'],
},
};
export default function () {
const res = ws.connect(
`wss://game.staging.example.com/ws?token=${__ENV.GAME_TOKEN}`,
{},
function (socket) {
socket.on('open', () => {
socket.send(JSON.stringify({ type: 'join', room: 'arena-1' }));
});
socket.on('message', (data) => {
const msg = JSON.parse(data);
// Measure server-to-client tick latency
if (msg.type === 'world_state' && msg.server_ts) {
tickLatency.add(Date.now() - msg.server_ts);
}
check(msg, { 'no error event': (m) => m.type !== 'error' });
// Acknowledge every world-state tick
if (msg.type === 'world_state') {
socket.send(JSON.stringify({ type: 'ack', seq: msg.seq }));
}
});
socket.on('error', (e) => {
check(null, { 'no ws error': () => false });
});
// 5-minute session
socket.setTimeout(() => socket.close(), 300000);
}
);
check(res, { 'ws handshake 101': (r) => r && r.status === 101 });
}

Upload this as the k6 entrypoint in MaxoPerf. The custom game_tick_latency_ms trend metric measures the game-specific server-push latency — the time from when the server generates a world state to when the client receives it — which is what players actually experience as “lag.”

When you must load-test a UDP game server, the practical approaches are:

Approach 1 — Test the control plane separately. Most UDP game servers have an HTTP or WebSocket control plane for matchmaking, session allocation, and authentication. Test that control plane at full load in MaxoPerf. UDP data-plane capacity is then validated with a smaller, dedicated tool (e.g. a custom Go/Rust UDP flood tool running on a single VM) that measures raw packet handling, not session-level load.

Approach 2 — Proxy shim. Build a thin WebSocket proxy that wraps your UDP game protocol. Load-test the WebSocket proxy in MaxoPerf at target concurrency, then extrapolate UDP capacity from the proxy’s CPU and memory profile.

Approach 3 — Game engine built-in bench. Many game engines ship with a headless bench client (e.g. Unreal’s dedicated server bench mode). Use MaxoPerf to orchestrate the REST/WebSocket workload and run the engine bench separately on provisioned VMs.

SituationRecommended approach
Browser/mobile game via WebSocketk6 on MaxoPerf — full support
HTTP REST backend (auth, inventory, leaderboard)k6 HTTP on MaxoPerf
gRPC game servicesk6 gRPC module on MaxoPerf
Raw TCP dedicated serverJMeter TCP Sampler .jmx on MaxoPerf
UDP game server data planeSeparate UDP tool + MaxoPerf for control plane
QUIC/HTTP3 (if server supports)k6 HTTP on MaxoPerf (experimental)