Image

База знаний → Настройка и использование WebSocket сервер

Websocket сервер предназначен для получения данных в режиме реального времени, в отличие от классического веб сервера, когда Вы открываете ссылку в браузере и получаете данные, например: html, xml, json и др. сервер устанавливает соединение -> передает данные -> разрывает соединение. WebSocket сервер работает иначе, вы устанавливаете с ним соединение и оно всегда активно, как только данные изменились сервер их передает клиенту — так как имеет двустороннюю связь, соединение всегда активно и не разрывается.

1. Применение

Чтобы получать данные в режиме реального времени, например Вы хотите отображать посетителей Вашего сайта или статусы заказов интернет магазина в режиме онлайн на отдельной веб странице или вовсе на отдельном устройстве.

2. Принцип работы

Websocket сервер это отдельная служба, которая работает на отдельном порту, назначенным Вами для последующего подключения. Протокол WS (websocket server) или WSS (websocket server + SSL) позволяет настроить вывод данных с обновлением в реальном времени, например через браузер, используя html + JS.

Обратите внимание! Так как служба требует открытия нового порта, то настройка и запуск такой службы возможен только на виртуальном сервере, именно по этой причине WebSocket сервер недоступен для услуги виртуальный хостинг.

Представьте, если каждый клиент виртуального хостинга захочет запустить такую службу - потребуется для каждого открыть отдельный порт, что будет нарушать безопасность самой услуги. Независимо от хостинг провайдера запуск и настройка возможны только на VPS/VDS сервере.

3. Пример задачи

Предположим, что у нас есть php скрипт https://domain.tld/online-orders.php, который формирует вывод данных в формате json и мы хотим чтобы данные отображались в реальном времени, так как сейчас для такого результата нужно постоянное обновление.

Обратите внимание, если для ПК и браузера можно обновлять данные увеличением частоты запросов, то в случае частого запроса и обновления JSON данных с микроконтроллера будет просто приводить его к подвисанию на 1-2 секунды, а скажем регулярный POST/GET запрос каждые 20 секунд - всеже не связано с реальным временем.

Как вывод, нам нужен WSS, запустим новый VPS на Debian 12 с минимальной конфигурацией по ресурсам. Для WSS высокая производительность не нужна. Всю настройку мы будем производить от пользователя root.

3.1 Установка служб

apt update
apt install -y curl git nodejs npm

3.2 Создание проекта websocket

mkdir ~/websocket-server
cd ~/websocket-server
npm init -y
npm install ws node-fetch express

3.3 Создадим файл конфигурации в корне проекта websocket-server

nano server.js
import express from "express";
import { WebSocketServer } from "ws";
import fetch from "node-fetch";
import https from "https";
import fs from "fs";

// === Настройки ===
const PORT = 8080; // Придумаем порт для текущего WebSocket сервера
const FETCH_URL = "https://domain.tld/online-orders.php";
const FETCH_INTERVAL = 1000; // раз в 1 секунду

// === HTTPS сертификаты ===
// (Если нужен WSS, нужен SSL-сертификат. Можно использовать Let's Encrypt или self-signed)
const options = {
  key: fs.readFileSync("/etc/ssl/private/your-key.pem"),
  cert: fs.readFileSync("/etc/ssl/certs/your-cert.pem")
};

// === HTTP(S) сервер + WebSocket ===
const app = express();
const server = https.createServer(options, app);
const wss = new WebSocketServer({ server });

let latestData = null;

// === Периодический запрос JSON ===
async function updateData() {
  try {
    const res = await fetch(FETCH_URL);
    const json = await res.json();
    latestData = json;

    // отправляем всем подключённым клиентам
    wss.clients.forEach(client => {
      if (client.readyState === 1) {
        client.send(JSON.stringify(json));
      }
    });
  } catch (err) {
    console.error("Ошибка загрузки:", err);
  }
}

setInterval(updateData, FETCH_INTERVAL);
updateData(); // первая загрузка

// === WebSocket подключение ===
wss.on("connection", ws => {
  console.log("Клиент подключен");

  if (latestData) ws.send(JSON.stringify(latestData));

  ws.on("close", () => console.log("Клиент отключился"));
});

server.listen(PORT, () => {
  console.log(`WSS сервер запущен на порту ${PORT}`);
});

3.4 Настройка SSL для WSS через Lets Encrypt

Для SSL необходим домен, мы сделаем на субдомене, например ws.domain.tld

apt install certbot python3-certbot-nginx
certbot certonly --standalone -d ws.domain.tld

Пройдите короткую процедуру, указав email адрес для уведомлений. По завершению сертификат и ключ будут тут:

/etc/letsencrypt/live/ws.domain.tld/fullchain.pem
/etc/letsencrypt/live/ws.domain.tld/privkey.pem

Замените пути в файле server.js

const options = {
  key: fs.readFileSync("/etc/letsencrypt/live/ws.domain.tld/privkey.pem"),
  cert: fs.readFileSync("/etc/letsencrypt/live/ws.domain.tld/fullchain.pem")
};

3.5 Запуск и отладка

node server.js

У нас выдало ошибку SyntaxError: Cannot use import statement outside a module

В таком случае необходимо внести изменения в файл package.json добавив строку "type": "module",

Пример правильной конфигурации package.json

{
  "name": "websocket-server",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "type": "module",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
...

Запускаем снова

node server.js

WSS сервер запущен на порту 8080
Клиент подключен

3.6 Настроим автозапуск:

npm install -g pm2
pm2 start server.js --name websocket
pm2 save
pm2 startup

3.7 Проверим работу службы

pm2 list

3.8 Подключимся, используя html и JS

создайте на своем ПК файл wss-client.html следующего содержания:

Подключиться к WebSocket по домену напрямую через браузер нельзя, как к веб-серверу, необходимо создать скрипт. Укажите свой домен, заместо ws.domain.tld

<!DOCTYPE html>
<html lang="ru">
<head>
  <meta charset="UTF-8">
  <title>WSS Test</title>
  <style>
    body {
      font-family: monospace;
      background: #111;
      color: #0f0;
      padding: 20px;
    }
    input, button {
      background: #222;
      color: #0f0;
      border: 1px solid #0f0;
      padding: 6px 10px;
      font-family: monospace;
    }
    #log {
      margin-top: 20px;
      white-space: pre-wrap;
      background: #000;
      border: 1px solid #0f0;
      padding: 10px;
      height: 400px;
      overflow-y: auto;
    }
  </style>
</head>
<body>
  <h2>🔌 WebSocket Test Client</h2>

  <label>URL сервера:</label>
  <input id="wsUrl" type="text" size="50" value="wss://ws.domain.tld:8080">
  <button onclick="connect()">Подключиться</button>
  <button onclick="disconnect()">Отключиться</button>

  <div id="status">Статус: <b>Не подключен</b></div>
  <div id="log"></div>

  <script>
    let ws;

    function log(msg) {
      const logDiv = document.getElementById('log');
      logDiv.innerText += msg + "\n";
      logDiv.scrollTop = logDiv.scrollHeight;
    }

    function connect() {
      const url = document.getElementById('wsUrl').value;
      ws = new WebSocket(url);

      log("🔄 Подключаюсь к " + url + " ...");
      document.getElementById('status').innerHTML = "Статус: <b>Подключение...</b>";

      ws.onopen = () => {
        log("✅ Соединение установлено");
        document.getElementById('status').innerHTML = "Статус: <b style='color:lime'>Подключен</b>";
      };

      ws.onmessage = event => {
        try {
          const json = JSON.parse(event.data);
          log("📦 Получено:\n" + JSON.stringify(json, null, 2));
        } catch (e) {
          log("📦 Текст:\n" + event.data);
        }
      };

      ws.onclose = () => {
        log("❌ Соединение закрыто");
        document.getElementById('status').innerHTML = "Статус: <b style='color:red'>Отключен</b>";
      };

      ws.onerror = err => {
        log("⚠️ Ошибка: " + err.message);
      };
    }

    function disconnect() {
      if (ws) {
        ws.close();
        ws = null;
      }
    }
  </script>
</body>
</html>

3.9 Просмотр логов

Подключение должно работать, как только данные в нашем скрипте меняются, websocket обновляет их у всех подключенных к нему клиентов.

Просмотреть журнал событий можно командой

pm2 logs websocket
0|websocket  | Обновлено 5 визитов

4. Авторизация

В примере выше к серверу websocket могут подключиться абсолютно все, поэтому добавим авторизацию по токену.

Обновленный код server.js

import express from "express";
import { WebSocketServer } from "ws";
import fetch from "node-fetch";
import https from "https";
import fs from "fs";
import url from "url";

// === Настройки ===
const PORT = 8080;
const FETCH_URL = "https://domain.tld/online-orders.php";
const FETCH_INTERVAL = 1000; // каждые 1 секунду
const VALID_TOKENS = ["ABC123", "ESP32TOKEN", "MYSECRET"]; // список разрешённых токенов

// === SSL ===
const options = {
  key: fs.readFileSync("/etc/letsencrypt/live/ws.domain.tld/privkey.pem"),
  cert: fs.readFileSync("/etc/letsencrypt/live/ws.domain.tld/fullchain.pem")
};

// === Создаём сервер ===
const app = express();
const server = https.createServer(options, app);
const wss = new WebSocketServer({ server });

let latestData = null;

// === Функция для периодического получения JSON ===
async function updateData() {
  try {
    const res = await fetch(FETCH_URL);
    const json = await res.json();
    latestData = json;

    // Отправляем всем активным клиентам
    wss.clients.forEach(client => {
      if (client.readyState === 1) {
        client.send(JSON.stringify(json));
      }
    });
  } catch (err) {
    console.error("Ошибка загрузки:", err);
  }
}

setInterval(updateData, FETCH_INTERVAL);
updateData();

// === Обработка подключений ===
wss.on("connection", (ws, req) => {
  const query = url.parse(req.url, true).query;
  const token = query.token;

  if (!VALID_TOKENS.includes(token)) {
    console.log(`❌ Отклонено подключение с токеном: ${token}`);
    ws.close(4001, "Invalid token");
    return;
  }

  console.log(`✅ Клиент подключён с токеном: ${token}`);

  // Отправляем последнее состояние
  if (latestData) ws.send(JSON.stringify(latestData));

  ws.on("close", () => {
    console.log(`🔌 Клиент (${token}) отключился`);
  });
});

server.listen(PORT, () => {
  console.log(`✅ WSS сервер запущен на порту ${PORT}`);
});

4.1 Подключение с авторизацией по токену

Для примера через html + JS просто поменяйте строку на

<input id="wsUrl" value="wss://ws.domain.tld:8080/?token=ABC123">

5. В заключение

Настройка завершена, теперь все клиенты websocket сервера получают данные в реальном времени с использованием шифрования. В нашем примере мы используем php скрипт как посредника между системой для получения данных с использованием API для передачи их на websocket.

Сам php скрипт можно убрать и использовать API запрос в самом server.js с прохождением API авторизации к системе, что будет еще эффективнее, но статья тогда будет совсем огромной, так как придется затронуть безопасное хранение API ключа в файле .env и установку пакета npm install dotenv для работы с данными из файла конфигурации.





Нет комментариев