# WebSocket - bidirectional поверх HTTP _Сеть: L4 и выше · LinuxLab Knowledge Base_ **TL;DR:** WebSocket - двусторонний канал поверх одного TCP. Апгрейд из HTTP/1.1 через Upgrade header, потом обмен бинарными frame'ами. Используется для real-time UI - чаты, дашборды, live-обновления. ## Зачем WebSocket HTTP - request/response, сервер не может «толкнуть» обновление сам. До WebSocket'а делали polling (запрос каждые 5 сек) или long-polling. Это тратит CPU и каналы. WebSocket даёт **одно TCP-соединение**, через которое и клиент, и сервер шлют сообщения когда захотят. Применение в LinuxLab: WebSocket на `/api/ws/sessions/{id}` - PTY для терминала + state-updates от агента в контейнере. ## Handshake Начинается как обычный [[http-protocol|HTTP/1.1]] запрос с upgrade: ``` GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Sec-WebSocket-Version: 13 ``` Сервер отвечает 101 Switching Protocols: ``` HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= ``` `Sec-WebSocket-Accept` = base64(SHA1(client_key + magic_GUID)). Это не безопасность, а защита от случайного апгрейда того, что не понимает WebSocket. После 101 - **тот же TCP-сокет**, но протокол поменялся: дальше идут бинарные WebSocket-frame'ы, не HTTP. ## Frame format ``` 0 1 2 3 4 5 6 7 +-+-+-+-+---+---+-+-+---+---+----+ |F|R|R|R| op|M| payload-len |...| |I|S|S|S|cod|A| | | |N|V|V|V|e |S| | | | |1|2|3| |K| | | +-+-+-+-+---+-+-+--------------+ ``` - **FIN** - последний frame в логическом message? (можно фрагментировать) - **opcode** - 0x1 text, 0x2 binary, 0x8 close, 0x9 ping, 0xA pong - **MASK** - клиент → сервер обязан маскировать payload XOR-ом, сервер → клиент не маскирует - **payload-len** - 7 / 16 / 64 бит (extended если > 125) Маскирование с клиента нужно для защиты от cache-poisoning старых HTTP-прокси. ## ws:// vs wss:// - `ws://` - голый TCP, порт 80 - `wss://` - TLS-обёрнут, порт 443 В проде используем `wss://` всегда. WebSocket-handshake живёт внутри [[tls-handshake|TLS-сессии]] - с виду как HTTPS-запрос, апгрейд внутри шифрованного канала. ## Ping / Pong Чтобы убедиться, что соединение живо (NAT не закрыл, peer не упал), стороны шлют **ping-frame** (opcode 0x9). Получатель обязан вернуть **pong** (0xA) с тем же payload. Если pong не приходит за N секунд - соединение протухло, реконнект. Стандартный интервал: 30-60 секунд (короче чем NAT-timeout, обычно 5 минут на UDP-NAT и 2 часа на TCP-NAT). ## Close Корректное закрытие - **close-frame** (0x8) с 2-байтным status code: | Код | Что | |------|-----| | 1000 | normal closure | | 1001 | going away (страница закрыта) | | 1002 | protocol error | | 1003 | unsupported data | | 1006 | abnormal closure (не приходит явно, означает «соединение разорвано») | | 1011 | server error | | 4000+ | application-defined | Хорошая клиентская реализация на 1006 делает экспоненциальный реконнект. ## Backpressure WebSocket не имеет нативного flow-control на уровне сообщений (только TCP-window). Если сервер шлёт быстрее чем клиент жуёт - буфер растёт. На больших нагрузках надо либо application-level backpressure (acks от клиента), либо drop-message политика. ## Размер сообщения Технически до 2^63 байт. Реально - сервера ставят лимит 1-16 MB. Большие файлы лучше через HTTP, не через WS. ## Когда что-то пошло не так - **Connection closed before handshake** - reverse-proxy не пропустил `Upgrade` header. В nginx нужен `proxy_set_header Upgrade $http_upgrade;` - **1006 каждые 60 сек** - load balancer закрывает idle. Нужен ping или конфиг LB на больший timeout - **Stuck after 1 message** - клиент забыл MASK, сервер дропнул - **Memory growth** - нет backpressure, медленные клиенты копят buffer - **Echo обратный к серверу** - забыли валидацию `Origin` header, cross-site WebSocket hijacking ## Команды ```bash curl -i -N -H 'Connection: Upgrade' -H 'Upgrade: websocket' -H 'Sec-WebSocket-Version: 13' -H 'Sec-WebSocket-Key: dGVzdA==' http://localhost:8080/ws ``` Поднять минимальный WebSocket-handshake руками - увидеть 101 ответ ```bash tcpdump -i any -nn -A 'tcp port 80' | grep -A2 -i 'upgrade' ``` Поймать WebSocket upgrade в plain text. Для wss:// - не получится без mitm ```bash ss -tnH | grep ':443' | wc -l ``` Сколько активных TLS-соединений (включая wss://) сейчас держится ```bash wscat -c wss://echo.websocket.org ``` Простой WebSocket-клиент в shell (npm i -g wscat); шлёшь сообщение - получаешь эхо ## См. также - [HTTP/1.1, HTTP/2, HTTP/3](/kb/http-protocol.md) - [TCP three-way handshake](/kb/tcp-handshake.md) - [TLS handshake](/kb/tls-handshake.md) - [Порт - как несколько сервисов делят один IP](/kb/port.md)