Вопрос или проблема
Ранее я настроил сервер 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, что на самом деле является неопределенным статусом закрытия. Это предполагает, что сервер закрыл соединение без отправки сообщения с указанием причины. Рассмотрим возможные причины:
-
Ошибки аутентификации: В коде проверяется токен и параметр
service
. В случае несовпадения соединение закрывается. Если токен или параметры передаются неверно, это может привести к немедленному закрытию. -
Ошибки в логике серверного кода: Некорректная обработка различных условий в серверной логике может вызвать преждевременные закрытие. Например, ошибка в блоке if может неверно определить условия на закрытие.
-
Проблемы из-за сторонних зависимостей: Подключение к другим системам, например, MQTT или MySQL, может вызывать ошибки, которые ведут к закрытию сокета.
-
Проблемы сетевого уровня: Проблемы с сетью или 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}`);
});
}
Ваша обработка событий для этого сервиса должна проверить правильность исходящих сообщений и соединения. Вам необходимо убедиться, что ответ серверу сформирован верно и не вызывает ошибок на стороне клиента.
Применение
-
Проверка исходящих сообщений: Убедитесь, что сервер отправляет сообщения правильно. Попробуйте упростить их и проверить: возможно, слишком большое количество начальных сообщений вызывает проблемы у клиента.
-
Дебаггинг сервера: Разместите дополнительные логи в коде для отслеживания состояний WebSocket и обмена данными. Это поможет выявить точный момент и причину закрытия.
-
Тестирование без сторонних подключений: Временно отключите всё, что связано с MQTT и MySQL, и проверьте соединение. Это поможет выяснить, связаны ли проблемы непосредственно с этими интеграциями.
-
Улучшение кода клиента: Убедитесь, что клиентская сторона корректно обрабатывает сообщения и не вызывает ошибок. Добавьте обработчики ошибок
onerror
иonclose
, чтобы получить больше информации. -
Использование проверки ping/pong: Добавьте механизмы проверки соединения, такие как ping и pong, для поддержания активного соединения и обнаружения возможных проблем.
-
Изучение сетевых настроек: Проверьте настройки сервера и сетевые политики, которые могут завершать соединения после простоя или низкой активности.
Заключение
Важно помнить, что интеграция с внешними сервисами и работа на реальных системах часто сопряжена с появлением множества факторов, которые могут повлиять на стабильность работы WebSocket. Следовательно, систематический подход, включающий глубокий анализ и последовательную проверку всех частей системы, позволит выявить и устранить основную причину проблем.