Как поднять свой WebSocket сервер на Node.js: основы

Привет, Хабр!
Сегодня создадим свой WebSocket сервер на Node.js. Это тот самый протокол, который позволяет отправлять и принимать данные в реальном времени без перекладывания на HTTP. Для этого мы будем использовать библиотеку ws.
Подготовка окружения
Первый шаг — установить Node.js, если его еще нет. Ну, а если есть, проверим версии:
node -v
npm -v
Все на месте? Отлично. Теперь создадим папку для проекта:
mkdir websocket-server
cd websocket-server
npm init -y
Тут сразу заложим фундамент проекта. npm init -y
создает стандартный package.json
. Но не забудь туда добавить "type": "module"
, чтобы сразу подключать все по человески, без всяких require:
{
"type": "module",
"dependencies": {
"ws": "^8.12.0"
}
}
Теперь пришло время установить библиотеку для работы с WebSocket — ws:
npm install ws
Это некий золотой стандарт среди библиотек для WebSocket на Node.js.
Создание базового WebSocket сервера
Окей, есть инструменты, пора за работу! В файле server.js
создадим сервер WebSocket, который уже готов принять соединение. Минимум кода:
import { WebSocketServer } from 'ws';
const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', (ws) => {
console.log('Новый клиент подключился!');
ws.on('message', (message) => {
console.log(`Получено сообщение: ${message}`);
ws.send('Сообщение получено!');
});
ws.on('close', () => {
console.log('Клиент отключился');
});
});
Разберем. Мы создали сервер на порту 8080. Как только клиент подключается, мы сообщаем об этом в консоль. Когда он присылает сообщение, сервер отвечает:»Сообщение получено! ». А когда клиент отключается, сервер говорит об этом. Запустим его:
node server.js
Теперь можно подключиться к серверу через браузер, Postman или вообще любой клиент WebSocket.
Обработка сообщений и работа с несколькими клиентами
Теперь научим сервер работать с несколькими клиентами одновременно и обрабатывать не только текст, но и бинарные данные.
Обработка бинарных данных
Чтобы обрабатывать бинарные данные, нужно убедиться, что сервер может различать текст и двоичные сообщения:
ws.on('message', (data, isBinary) => {
if (isBinary) {
console.log('Получены бинарные данные');
} else {
console.log(`Получено сообщение: ${data}`);
}
ws.send(data); // Отправляем обратно клиенту то, что получили
});
Теперь сервер умеет отличать текстовые сообщения от бинарных и логирует их соответственно.
Множество клиентов
Но сервер не всегда работает с одним клиентом. Добавим возможность для нескольких клиентов обмениваться сообщениями. Будем сохранять всех подключенных клиентов в массиве и рассылаем сообщения всем остальным:
const clients = [];
wss.on('connection', (ws) => {
clients.push(ws);
ws.on('message', (message) => {
clients.forEach((client) => {
if (client !== ws && client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
});
ws.on('close', () => {
clients.splice(clients.indexOf(ws), 1); // Убираем клиента из списка при отключении
});
});
Теперь, когда один клиент отправляет сообщение, оно транслируется всем остальным подключенным клиентам, кроме самого отправителя.
Безопасность
Нельзя говорить о серверной архитектуре, не затронув безопасность. WebSocket не исключение. Самый простой способ защиты — ограничить число подключений с одного IP-адреса и установить таймауты.
Ограничение числа подключений
Если хочешь избежать атак типа DoS, придется ограничить количество соединений с одного IP. Пример, как это сделать:
const ipConnections = new Map();
wss.on('connection', (ws, req) => {
const ip = req.socket.remoteAddress;
if (ipConnections.get(ip) >= 5) {
ws.close();
return;
}
ipConnections.set(ip, (ipConnections.get(ip) || 0) + 1);
ws.on('close', () => {
ipConnections.set(ip, ipConnections.get(ip) - 1);
});
});
Теперь сервер будет автоматически разрывать соединение, если с одного IP слишком много подключений.
Зашифрованные соединения
Да, да — WebSocket через SSL. Важно, чтобы данные между сервером и клиентом были зашифрованы, особенно если это чувствительная информация. Чтобы перейти на wss://
, нужно настроить SSL:
import { createServer } from 'https';
import { readFileSync } from 'fs';
import { WebSocketServer } from 'ws';
const server = createServer({
cert: readFileSync('path/to/cert.pem'),
key: readFileSync('path/to/key.pem')
});
const wss = new WebSocketServer({ server });
wss.on('connection', (ws) => {
ws.on('message', (message) => {
console.log(`Received: ${message}`);
});
ws.send('Secure connection established!');
});
server.listen(8080);
Теперь сервер использует защищенные соединения.
Оптимизация и масштабирование
Последний штрих в любой архитектуре — это масштабируемость и оптимизация. WebSocket сервера легко могут работать с тысячами клиентов, но для этого нужно грамотно оптимизировать код.
Широковещательные сообщения
Если нужно отправлять сообщения сразу всем клиентам, то это можно сделать следующим образом:
wss.on('message', (message) => {
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
});
Масштабирование с Redis
Можно синхронизировать несколько серверов через Redis Pub/Sub, чтобы сообщения отправлялись на все экземпляры WebSocket сервера:
import Redis from 'ioredis';
const redis = new Redis();
const sub = new Redis();
sub.subscribe('messages');
sub.on('message', (channel, message) => {
wss.clients.forEach((client) => {
client.send(message);
});
});
wss.on('connection', (ws) => {
ws.on('message', (message) => {
redis.publish('messages', message);
});
});
Таким образом, клиенты, подключенные к разным серверам, могут обмениваться сообщениями через Redis.
Заключение
WebSocket — это невероятно мощный инструмент для создания real-time приложений, но, как и в любом деле, чтобы стать мастером, нужно идти дальше. Так что, если вам показалось, что этого мало (а это точно так), вот вам несколько идей, чем можно заняться дальше:
Реализуй чат с историей сообщений — каждый клиент, подключаясь, может получать последние 50 сообщений, даже если он пропустил живое общение.
Работа с WebSocket в продакшене — изучи, как лучше мониторить и перезагружать WebSocket серверы, например, с помощью PM2 или Kubernetes.
Веб-сокеты и микросервисы — попробуй организовать обмен данными через WebSocket в архитектуре микросервисов, когда один сервис передает информацию другим.
Теперь, когда есть эти основы, всё зависит от вас. Исследуйте документацию, читайте статьи на Хабре, экспериментируйте с кодом, развивайте свои навыки — и создавайте что-то по-настоящему крутое.
А еще приходите на бесплатный захватывающий вебинар посвященный изучению мощного фреймворка разработки веб-приложений Nest.Js. Зарегистрироваться.