WebSocket Protocol

WebSocket protocol

Let's walk through how the WebSocket protocol works and why it relies on ping/pong messages to stay healthy.

Context:

  • You already know the basics of TCP (three-way handshake, etc.).

Where WebSocket lives in the stack

The WebSocket protocol operates at the application layer.

Application Layer   : WebSocket
Transport Layer     : TCP
Network Layer       : IP
Link Layer          : Ethernet / Wi-Fi

WebSocket uses TCP for transport, so it inherits both TCP's reliability and its quirks.

Flow

1. TCP connection
2. HTTP Upgrade request
3. HTTP 101 Switching Protocols
4. WebSocket frames exchange
5. Close handshake

At step 1 the standard TCP three-way handshake runs, after which HTTP negotiates the protocol switch.

Why start with HTTP?

  1. It drops neatly into the existing HTTP-based infrastructure (reverse proxies, load balancers, etc.).
  2. Firewalls already allow HTTP traffic, so fewer network rules need to change.
  3. It reuses familiar ports such as 80 and 443.

When WebSocket was introduced, the internet was already optimized for HTTP. Starting with the same method and ports avoided a parallel deployment story. HTTP 1.1 even defined 101 Switching Protocols, so standard servers and clients already had a hook for an upgrade dance.

A classic issue: half-open connections

The proper finalization:

Client           Server

FIN ------------>
        ACK <----
        FIN <----
ACK ------------>

However, if a client crashes before sending FIN, the server keeps the socket open forever. To detect that situation the protocol allows either side to send periodic ping frames. If the peer replies with a pong, both halves know the connection is still alive. If no pong arrives within a timeout, the connection can be closed.

What if the server never pings?

If the server does not ping (and clients do not either), it cannot tell whether the client has silently died.

Client                 Server
  |                      |
  |---- WebSocket open ->|
  |                      |
  |---- data ----------->|
  |                      |
  X client crash
  |                      |
  |  (server never notices)

Example code:

import asyncio

from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from uvicorn import Config, Server

app = FastAPI()


@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    await websocket.accept()

    try:
        while True:
            message = await websocket.receive()
            print("received:", message)

            await websocket.send_text("ok")

    except WebSocketDisconnect:
        print("client disconnected")


async def main():
    config = Config(
        app=app,
        host="0.0.0.0",
        port=8000,
        loop="asyncio",
        ws="websockets",
        ws_ping_interval=None,   # disable server-side ping
        ws_ping_timeout=None,
    )
    server = Server(config)
    await server.serve()


if __name__ == "__main__":
    asyncio.run(main())