Reading — step 1 of 5
Read
Ping/Pong & Keep-Alive
A WebSocket lives as long as its TCP connection — and TCP is quietly terrible at telling you when the other side is gone. Two separate problems hide here.
Problem 1: middleboxes kill idle connections. NAT routers and load balancers keep a table entry per connection and expire entries that go quiet — often after 60 seconds. Your perfectly healthy but silent WebSocket just stops delivering, and neither endpoint gets an error until it next tries to send.
Problem 2: half-open connections. If a laptop lid closes or a phone drops off Wi-Fi, no FIN packet is ever sent. The server's socket looks open; send() happily queues bytes into the void. Without an application-level check, dead connections pile up holding memory and file descriptors.
The protocol's answer
PING (opcode 0x9) and PONG (opcode 0xA) are control frames — always FIN=1, payload at most 125 bytes. The rules (RFC 6455 §5.5.2–5.5.3):
- On receiving a PING, an endpoint MUST send a PONG carrying the identical payload — that echo is how the pinger matches answers to questions.
- If several PINGs arrive before you can respond, answering only the most recent one is allowed.
- An unsolicited PONG is legal — a one-way heartbeat that requires no reply.
Sending traffic keeps NAT entries fresh (fixes problem 1); expecting the echo back on a deadline detects dead peers (fixes problem 2):
A 30–45 second cadence is typical: comfortably under the ~60s idle timeouts in the wild, without wasting bandwidth.
Who writes the PONG? Usually not you: browsers answer pings automatically (the JS WebSocket API doesn't even expose ping), and server libraries (ws, websockets, gorilla) auto-reply too. What no library can do for you is the policy side — deciding to send pings, tracking the outstanding one, and killing the connection when the echo never comes. That bookkeeping is this lesson's exercise. (TCP keepalive exists too, but its kernel-level defaults are hours and it proves only that the kernel answered — run app-level pings.)
The bookkeeping state machine
Track one pending ping: (sent_at, payload). A new PING replaces it (most-recent wins). A PONG matching the pending payload clears it; anything else is a mismatch. A liveness check compares now - sent_at against the timeout — and only reads state.
Your exercise
Implement NOW/PING/PONG/CHECK. The trap the grader catches: CHECK must not mutate state. In the hidden test, a ping times out (CHECK 30 prints TIMEOUT) and then the pong finally arrives — the expected output is OK, then IDLE: the full sequence is OK, TIMEOUT, OK, IDLE. If your CHECK clears the pending ping on timeout, the late PONG a prints MISMATCH instead. Also: the deadline is inclusive — sent at 0, NOW 30, CHECK 30 prints OK, not TIMEOUT (use <=); and a PONG with no ping outstanding prints MISMATCH.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…