Все посты
Node.jsWebSocketNetworkingFrom scratch

Я построил WebSocket-сервер из сырого TCP, чтобы по-настоящему его понять

Опубликовано 5 августа 2025 г. · 7 мин чтения

Зачем строить то, что уже существует?

Честный ответ: потому что я годами использовал WebSocket и всё равно не мог ответить "что такое фрейм?" или "почему существует маскирование?" Я знал API браузера. Знал, как подключить socket.io. Нулевое понимание того, что происходит между клиентом и сервером.

Поэтому я построил noServer: Node.js HTTP и WebSocket сервер без зависимостей на сырых TCP. Без express, без ws, без socket.io — только net-модуль Node и RFC.

Сырой TCP — отправная точка

net.createServer() от Node даёт сокет для каждого соединения. Этот сокет — байты на входе, байты на выходе. Никакого протокола, никакой разбивки на фреймы.

Рукопожатие

RFC 6455 определяет магический GUID: 258EAFA5-E914-47DA-95CA-C5AB0DC85B11. Единственная цель: не дать обычным HTTP-серверам случайно принять upgrade-запросы.

После ответа 101 соединение обновлено. Больше нет HTTP. Дальше — протокол фреймов WebSocket.

Фреймы: настоящий протокол

Сообщение WebSocket разбивается на фреймы. У каждого фрейма двоичный заголовок, затем полезная нагрузка.

Маскирование и почему оно существует

Фреймы клиента ДОЛЖНЫ быть маскированы. Фреймы сервера НЕ ДОЛЖНЫ быть маскированы. Это не опционально.

Маскирование — XOR с 4-байтным ключом:

Это не шифрование. Не безопасность. Единственная цель — не дать фреймам WebSocket выглядеть как HTTP для промежуточных прокси.

Что я теперь знаю

Библиотека ws — около 2000 строк. После построения noServer я понимаю, что делает большинство этих строк. Почему существует ping/pong, почему бит FIN, почему маскирование только на клиенте.

Я всё ещё использую ws в продакшне. Речь шла не о замене библиотек — о понимании.

Если вы используете WebSocket и никогда не читали RFC 6455, постройте игрушечную реализацию. Вы поймёте протокол за один день так, как не даст никакое количество чтения документации.