Вебсокет закрывается сразу после подключения.

Вопрос или проблема

Ранее я настроил сервер websocket на node.js, который работает без проблем. У меня есть 3 разных веб-страницы, которые в настоящее время получают доступ к серверу websocket. Каждая страница имеет параметр в URI под названием “service”, который определяет, как сервер websocket отвечает через операторы if.

Теперь я добавил еще одну службу на сервер websocket (еще один оператор if), который, похоже, немедленно закрывает сокет с кодом 1005. Я добавил несколько строк для отправки сообщений клиенту, после чего сокет немедленно закрывается.

Я добавил базовую страницу, которая ничего не делает, кроме подключения к серверу websocket, и он все равно закрывается. Если я изменю параметр на одну из других работающих служб, то websocket остается открытым на странице test.html.

Я пытался гуглить ошибку и использовать chatGPT, но так и не смог найти причину.

Вот очищенный сервер websocket на node.js, первый блок if для service == shelly не работает. (Я удалил ненужные функции, которые работают):

  try{
    const mysql = require('mysql');
    const mysql2 = require('mysql2/promise');
    const dbConfig = JSON.parse(fs.readFileSync('/var/www/config','ascii'));

    const fs = require('fs');
    const http = require('http');
    const WebSocket = require('ws');
    const { v4: uuidv4 } = require('uuid');
    const url = require('url');
    const token = fs.readFileSync('/var/www/token','ASCII').slice(0,-1);

    const mqtt = require('mqtt');
    const mqttBroker="mqtts://mqtt.example.com:8883";
    const mqttTopics = [
      'shellyShed/events/rpc',
      'shellyGate/events/rpc',
      'shellyGarage/events/rpc',
      'shellyShedLight/events/rpc'
      ];
    const mqttUser="user";
    const mqttPass="Pass";
    const mqttOptions = {
      username: mqttUser,
      password: mqttPass,
      rejectUnauthorised: false,
    };

    let arServer = {server data here};
    let solarJSON;
    let solarTmp;
    clients = {};
    shellyClients = {};

    // создать вебсервер для websocket
    const server = await http.createServer()
      .listen(56112);
    const wss = new WebSocket.Server({ server });
    console.log("сервер вебсокетов запущен...")

    // настроить mqtt-клиент
    const mqttClient = mqtt.connect(mqttBroker,mqttOptions);
    mqttClient.on('connect', () => {
      console.log(`подключено к MQTT брокеру ${mqttBroker}`);
      mqttClient.subscribe(mqttTopics, (err) => {
        if (!err) {
          console.log(`подписка на MQTT тему: ${mqttTopics}`);
        } else {
          console.error(`не удалось подписаться: ${err}`);
        }
      });
    });

    mqttClient.on('message', (topic, message) => {
      console.log(`Получено сообщение MQTT: { ${topic}, сообщение: ${message} } )`);
      for (const id in shellyClients) {
        const ws = shellyClients[id];
        console.log(`id для отправки: ${id}`);

        if(ws.readyState === WebSocket.OPEN) {
          console.log(`вебсокет ${id} открыт`);
          ws.send(JSON.stringify({ topic, message: message.toString() } ));
        } else {
          delete shellyClients[id];
          console.log(`вебсокет ${id} не открыт, удаление`);
        }
      }
    });

    // настроить пул базы данных
    const poolConfig = {
      host: "127.0.0.1",
      user: "user",
      password: "pass",
      database: "db"
    }
    const pool = mysql2.createPool(poolConfig);

    wss.on('connection', function connection(ws, req) {
      const tokenMatch = url.parse(req.url, true).query.token; //загрузить сохраненный токен из параметра URI token
      const service = url.parse(req.url, true).query.service; //загрузить запрашиваемую службу из параметра URI service

      console.log("Клиент подключен");
      ws.on("close", (code, reason) => {
        console.log(`WebSocket закрыт: Код ${code}, Причина: ${reason}`);
      });

      if(tokenMatch == token && service == "shelly") {
        ws.id = uuidv4();
        ws.ip = ws._socket.remoteAddress;
        console.log(`UUID ${ws.id} подключился для Shelly MQTT: ${ws.ip}`);

        shellyClients[ws.id] = ws;

        ws.send(`подключен как ${ws.id} с ${ws.ip}`);
        ws.send(`подключен как ${ws.id} 1`);
        ws.send(`подключен как ${ws.id} 2`);
        ws.send(`подключен как ${ws.id} 3`);
        ws.send(`подключен как ${ws.id} 4`);
        ws.on("message", data => {
          console.log(`${ws.id} отправил: ${data}`);
        });

      }

      if(tokenMatch == token && service == "encryption") {
        //проверить, имеет ли клиент правильный токен, закрыть соединение
        //если не соответствует
        ws.id = uuidv4();
        ws.ip = ws._socket.remoteAddress;
        console.log(`UUID ${ws.id} подключился для Шифрования: ${ws.ip}`);
        clients[ws.id] = ws;
        ws.send("подключен");

        reEncryptDb(ws, 4, pool);
      }

      if(tokenMatch == token && service == "server") {
      //проверить, имеет ли клиент правильный токен, закрыть соединение
      //если не соответствует
        ws.id = uuidv4();
        ws.ip = ws._socket.remoteAddress;
        console.log(`UUID ${ws.id} подключился для проверки сервера: ${ws.ip}`);
        checkServer(); //первая проверка сервера при аутентификации соединения

        function checkServer() {
        //рабочая функция

        }

        fs.watch('/directory/', (eventType, filename) => {
          //следить за директорией и запускать checkServer при изменении
          checkServer();
        })

        ws.on("close", function() {
          console.log(`UUID ${ws.id} отключился: ${ws.ip}`);
        });

      } else if(tokenMatch == token && service == "solar") {
      //проверить, имеет ли клиент правильный токен, закрыть соединение
      //если не соответствует
        ws.id = uuidv4();
        ws.ip = ws._socket.remoteAddress;
        console.log(`UUID ${ws.id} подключился для Solar: ${ws.ip}`);

        setInterval(() => {
          ws.ping();
        }, 10000);

        ws.onmessage = function(message) {
          console.log(message.data);
        }

        // создать объект соединения с mysql
        let conn = mysql.createConnection({
          host: "host",
          user: "user",
          password: "pass",
          database: "other_db"
        });

        // активировать соединение
        conn.connect(function(err) {
          if (err) throw err;
          console.log(`UUID ${ws.id} подключился к базе данных`);
        });

        let getData = setInterval(() => {
          // получить данные modbus из базы данных и отправить их клиенту каждую секунду

          // sql для получения общей энергии с начала дня, если несколько записей, получить наименьшее значение

          // отправить запрос в базу данных
          try {
            conn.query(sql, [fullDate], function (err, result) {
              productionTotal = result[0]["production_total"];
              productionTotalScale = result[0]["production_total_scale"];
              exportEnergy = result[0]["export_energy"];
              importEnergy = result[0]["import_energy"];
              importExportScale = result[0]["import_export_scale"];
            });
          }
          catch(err) {
            console.log("Не удалось получить значение энергии за день из БД");
            console.log(err);
            dailyEnergy = "undefined";
            wssError = "true";
          }

          // sql для получения последних данных modbus из базы данных
          sql = "sql select query";

          // отправить запрос в базу данных
          try{
            conn.query(sql, function (err, result) {
              // создать объект данных, используя шаблоны строк и интерполяцию
              modbusJSON = `{JSON here}`;

              ws.send(modbusJSON);
            });
          }
          catch(err){
            console.log("Ошибка запроса к таблице solar.modbus в базе данных");
            modbusJSON = `{"wss_error":"true", "wss_error_text":${err}}`;
          }
        }, 1000);

        ws.on("close", function() {
          clearInterval(getData);
          conn.end(console.log(`UUID ${ws.id} отключился от базы данных`));
          console.log(`UUID ${ws.id} отключился: ${ws.ip}`);
        });

        ws.on("message", data => {
          console.log(`${ws.id} отправил: ${data}`)
        });
      } else { ws.close(); }
    });

    //////// функции ///////////
    //функции ниже работают

})();

Это страница test.html.

<body>
test</body>
<script>

// настроить websocket
let wss = new WebSocket("wss://wss.example.com/wss?token=nottheactualtoken&service=shelly");

window.onunload = function() {
  wss.close();
}

//реагировать на сообщения от сервера
wss.onmessage = function(message) {
console.log(message.data);
};

</script>
</html>'''

Любая помощь в указании правильного направления была бы оценена

Ответ или решение

Теория

Проблема, связанная с немедленным закрытием WebSocket соединения после его установления, может быть вызвана различными факторами. В вашем случае сокет закрывается с кодом 1005, что на самом деле является неопределенным статусом закрытия. Это предполагает, что сервер закрыл соединение без отправки сообщения с указанием причины. Рассмотрим возможные причины:

  1. Ошибки аутентификации: В коде проверяется токен и параметр service. В случае несовпадения соединение закрывается. Если токен или параметры передаются неверно, это может привести к немедленному закрытию.

  2. Ошибки в логике серверного кода: Некорректная обработка различных условий в серверной логике может вызвать преждевременные закрытие. Например, ошибка в блоке if может неверно определить условия на закрытие.

  3. Проблемы из-за сторонних зависимостей: Подключение к другим системам, например, MQTT или MySQL, может вызывать ошибки, которые ведут к закрытию сокета.

  4. Проблемы сетевого уровня: Проблемы с сетью или SSL могут также вызывать закрытие соединения.

Пример

В вашем коде, особенно в ветке service == "shelly", имеется несколько конструкций, которые могут вызвать проблемы:

if(tokenMatch == token && service == "shelly") {
    ws.id = uuidv4();
    ws.ip = ws._socket.remoteAddress;
    console.log(`UUID ${ws.id} has connected for Shelly MQTT: ${ws.ip}`);

    shellyClients[ws.id] = ws;

    ws.send(`connected as ${ws.id} from ${ws.ip}`);
    ws.send(`connected as ${ws.id} 1`);
    ws.send(`connected as ${ws.id} 2`);
    ws.send(`connected as ${ws.id} 3`);
    ws.send(`connected as ${ws.id} 4`);

    ws.on("message", data => {
      console.log(`${ws.id} has sent us: ${data}`);
    });
}

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

Применение

  1. Проверка исходящих сообщений: Убедитесь, что сервер отправляет сообщения правильно. Попробуйте упростить их и проверить: возможно, слишком большое количество начальных сообщений вызывает проблемы у клиента.

  2. Дебаггинг сервера: Разместите дополнительные логи в коде для отслеживания состояний WebSocket и обмена данными. Это поможет выявить точный момент и причину закрытия.

  3. Тестирование без сторонних подключений: Временно отключите всё, что связано с MQTT и MySQL, и проверьте соединение. Это поможет выяснить, связаны ли проблемы непосредственно с этими интеграциями.

  4. Улучшение кода клиента: Убедитесь, что клиентская сторона корректно обрабатывает сообщения и не вызывает ошибок. Добавьте обработчики ошибок onerror и onclose, чтобы получить больше информации.

  5. Использование проверки ping/pong: Добавьте механизмы проверки соединения, такие как ping и pong, для поддержания активного соединения и обнаружения возможных проблем.

  6. Изучение сетевых настроек: Проверьте настройки сервера и сетевые политики, которые могут завершать соединения после простоя или низкой активности.

Заключение

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

Оцените материал
Добавить комментарий

Капча загружается...