WebSocket basics: your first connectionОсновы WebSocket: первое соединение
Why WebSocket existsЗачем нужен WebSocket
Classic HTTP is request/response: the client asks, the server answers, the connection is done. To get fresh data you either poll on a timer (wasteful and laggy) or hold a request open with long-polling (awkward). The WebSocket protocol, standardised as RFC 6455, solves this by upgrading a single HTTP connection into a persistent, full-duplex channel. After the upgrade, either side can send a message at any time with almost no per-message overhead.
That makes it the natural transport for chat, multiplayer games, collaborative editors, trading screens, live dashboards and notifications.
Классический HTTP — это запрос/ответ: клиент спрашивает, сервер отвечает, соединение закрывается. Чтобы получать свежие данные, вы либо опрашиваете сервер по таймеру (расточительно и с задержкой), либо удерживаете запрос открытым через long-polling (неуклюже). Протокол WebSocket, стандартизированный в RFC 6455, решает это, превращая одно HTTP-соединение в постоянный полнодуплексный канал. После апгрейда любая сторона может отправить сообщение в любой момент почти без накладных расходов на каждое сообщение.
Поэтому это естественный транспорт для чатов, мультиплеерных игр, совместных редакторов, биржевых терминалов, живых дашбордов и уведомлений.
The upgrade handshakeРукопожатие (upgrade)
A WebSocket connection starts life as an ordinary HTTP GET carrying an Upgrade header. If the server agrees, it replies with 101 Switching Protocols and the same TCP socket is now a WebSocket.
GET /v1 HTTP/1.1 Host: echo.socketforge.dev Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Sec-WebSocket-Version: 13
The server hashes the key with a fixed GUID and echoes it back in Sec-WebSocket-Accept, proving it understood the protocol:
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
You almost never write this by hand — the browser and server libraries do it. But it explains why a WebSocket lives on http:///https:// ports and passes through the same proxies as normal web traffic.
WebSocket-соединение начинается как обычный HTTP GET с заголовком Upgrade. Если сервер согласен, он отвечает 101 Switching Protocols, и тот же TCP-сокет становится WebSocket-ом.
GET /v1 HTTP/1.1 Host: echo.socketforge.dev Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Sec-WebSocket-Version: 13
Сервер хеширует ключ с фиксированным GUID и возвращает результат в Sec-WebSocket-Accept, подтверждая, что понял протокол:
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Вручную это писать почти никогда не нужно — браузер и серверные библиотеки делают всё сами. Но это объясняет, почему WebSocket живёт на портах http:///https:// и проходит через те же прокси, что и обычный веб-трафик.
The four ready statesЧетыре состояния соединения
Every WebSocket instance exposes a numeric readyState. Knowing it is the difference between a clean app and a pile of “socket is not open” errors.
- 0 — CONNECTING: handshake in flight, you may not send yet.
- 1 — OPEN: ready;
send()works. - 2 — CLOSING: a close handshake started.
- 3 — CLOSED: done, or never opened.
Каждый экземпляр WebSocket предоставляет числовой readyState. Понимание этого — разница между аккуратным приложением и кучей ошибок «socket is not open».
- 0 — CONNECTING: рукопожатие в процессе, отправлять ещё нельзя.
- 1 — OPEN: готово;
send()работает. - 2 — CLOSING: начато закрывающее рукопожатие.
- 3 — CLOSED: завершено или так и не открылось.
A minimal browser clientМинимальный браузерный клиент
The client API is just four events and two methods. Note that we only call send() after onopen fires:
const ws = new WebSocket("wss://echo.socketforge.dev/v1"); ws.addEventListener("open", () => { console.log("open, readyState =", ws.readyState); ws.send(JSON.stringify({ type: "hello", t: Date.now() })); }); ws.addEventListener("message", (e) => { const msg = JSON.parse(e.data); console.log("received", msg); }); ws.addEventListener("close", (e) => console.log("closed", e.code, e.reason)); ws.addEventListener("error", () => console.warn("socket error — close will follow"));
Always preferwss://(TLS) overws://. Browsers block insecure WebSockets from HTTPS pages, and plaintext frames are trivially readable on the network.
Клиентский API — это всего четыре события и два метода. Обратите внимание: send() вызываем только после срабатывания onopen:
const ws = new WebSocket("wss://echo.socketforge.dev/v1"); ws.addEventListener("open", () => { console.log("open, readyState =", ws.readyState); ws.send(JSON.stringify({ type: "hello", t: Date.now() })); }); ws.addEventListener("message", (e) => { const msg = JSON.parse(e.data); console.log("received", msg); }); ws.addEventListener("close", (e) => console.log("closed", e.code, e.reason)); ws.addEventListener("error", () => console.warn("socket error — close will follow"));
Всегда предпочитайтеwss://(TLS) вместоws://. Браузеры блокируют небезопасные WebSocket-ы со страниц HTTPS, а кадры открытым текстом легко читаются в сети.
A minimal Node.js echo serverМинимальный echo-сервер на Node.js
Using the popular ws package, a server that echoes every message back is just a few lines:
// npm i ws — node server.js import { WebSocketServer } from "ws"; const wss = new WebSocketServer({ port: 8080 }); wss.on("connection", (socket, req) => { console.log("client connected from", req.socket.remoteAddress); socket.on("message", (data, isBinary) => { // Echo back exactly what we got socket.send(data, { binary: isBinary }); }); socket.on("close", () => console.log("client gone")); });
Run it, point the client at ws://localhost:8080, and you have a working round-trip. In production you would terminate TLS at a reverse proxy (Caddy, nginx) and forward the upgrade to this process.
С популярным пакетом ws сервер, отражающий каждое сообщение обратно, занимает несколько строк:
// npm i ws — node server.js import { WebSocketServer } from "ws"; const wss = new WebSocketServer({ port: 8080 }); wss.on("connection", (socket, req) => { console.log("client connected from", req.socket.remoteAddress); socket.on("message", (data, isBinary) => { // Возвращаем ровно то, что получили socket.send(data, { binary: isBinary }); }); socket.on("close", () => console.log("client gone")); });
Запустите его, направьте клиент на ws://localhost:8080 — и у вас есть рабочий round-trip. В продакшене TLS терминируется на обратном прокси (Caddy, nginx), который пробрасывает upgrade в этот процесс.
Where to go nextЧто дальше
You can send and receive — now make it robust. Real networks drop sockets, so read Reconnection with exponential backoff, then add a heartbeat so you notice dead connections quickly.
Вы умеете отправлять и принимать — теперь сделайте это надёжным. Реальные сети рвут сокеты, поэтому прочитайте «Переподключение с экспоненциальной задержкой», а затем добавьте heartbeat, чтобы быстро замечать мёртвые соединения.