Обработка потери соединения с помощью websockets
Недавно я установил локальный сервер WebSocket, который отлично работает, однако у меня есть несколько проблем, которые понимают, как я должен обрабатывать внезапную потерю соединения, которое не было инициировано ни клиентом, ни сервером, то есть: сервер теряет мощность, кабели ethernet вытащили и т.д. Мне нужен клиент, чтобы узнать, потеряно ли соединение в течение ~ 10 секунд.
Клиентская сторона, соединение просто:
var websocket_conn = new WebSocket('ws://192.168.0.5:3000');
websocket_conn.onopen = function(e) {
console.log('Connected!');
};
websocket_conn.onclose = function(e) {
console.log('Disconnected!');
};
Я могу вручную запустить соединение, которое отлично работает,
websocket_conn.close();
Но если я просто вытащил кабель Ethernet из задней части компьютера или отключил соединение, onclose
не будет вызван. Я прочитал в другом сообщении, что в конечном итоге он будет вызван, когда TCP обнаружит потерю подключения, но это не так своевременно, как мне по умолчанию, для Firefox я верьте, 10 минут, и я действительно не хочу обойти сотни компьютеров about:config
, изменяя это значение. Единственное другое предложение, которое я прочитал, - использовать метод стиля опроса "ping/pong" keep-alive, который кажется нелогичным для идеи websockets.
Есть ли более простой способ обнаружить такое поведение отключения? Старые сообщения, которые я читаю, все еще актуальны с технической точки, а лучший метод - стиль "пинг/понг"?
Ответы
Ответ 1
Это было решение, которое я закончил тем, что, похоже, работает нормально на данный момент, оно полностью зависит от моей настройки проекта и полагается на критерии, которые не были первоначально упомянуты в моем вопросе, но это может быть полезно для кого-то другого, если они совершают одно и то же.
Соединение с сервером websocket происходит в Firefox-аддоне, и по умолчанию настройка Firefox TCP имеет 10-минутный тайм-аут. Дополнительные сведения можно просмотреть с помощью about:config
и поиска TCP.
Дополнения Firefox могут обращаться к этим параметрам
var prefs = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService);
а также изменить эти параметры, указав ветвь и предпочтение вместе с новым значением
prefs.getBranch("network.http.tcp_keepalive.").setIntPref('long_lived_idle_time', 10);
Итак, теперь на любом компьютере с установленным аддоном есть 10-секундный тайм-аут для TCP-соединений. Если соединение потеряно, запускается событие onclose
, которое отображает предупреждение, а также пытается восстановить соединение
websocket_conn.onclose = function (e) {
document.getElementById('websocket_no_connection').style.display = 'block';
setTimeout(my_extension.setup_websockets, 10000);
};
Ответ 2
Вам нужно добавить метод ping pong
Создайте код на сервере, когда получите __ ping __ отправьте __ понг __ назад
Ниже приведен код JavaScript ниже
function ping() {
ws.send('__ping__');
tm = setTimeout(function () {
/// ---connection closed ///
}, 5000);
}
function pong() {
clearTimeout(tm);
}
websocket_conn.onopen = function () {
setInterval(ping, 30000);
}
websocket_conn.onmessage = function (evt) {
var msg = evt.data;
if (msg == '__pong__') {
pong();
return;
}
//////-- other operation --//
}
Ответ 3
Я использовал идею ping/pong, и она работает хорошо. Здесь моя реализация в файле server.js:
var SOCKET_CONNECTING = 0;
var SOCKET_OPEN = 1;
var SOCKET_CLOSING = 2;
var SOCKET_CLOSED = 3;
var WebSocketServer = require('ws').Server
wss = new WebSocketServer({ port: 8081 });
//Broadcast method to send message to all the users
wss.broadcast = function broadcast(data,sentBy)
{
for (var i in this.clients)
{
if(this.clients[i] != sentBy)
{
this.clients[i].send(data);
}
}
};
//Send message to all the users
wss.broadcast = function broadcast(data,sentBy)
{
for (var i in this.clients)
{
this.clients[i].send(data);
}
};
var userList = [];
var keepAlive = null;
var keepAliveInterval = 5000; //5 seconds
//JSON string parser
function isJson(str)
{
try {
JSON.parse(str);
}
catch (e) {
return false;
}
return true;
}
//WebSocket connection open handler
wss.on('connection', function connection(ws) {
function ping(client) {
if (ws.readyState === SOCKET_OPEN) {
ws.send('__ping__');
} else {
console.log('Server - connection has been closed for client ' + client);
removeUser(client);
}
}
function removeUser(client) {
console.log('Server - removing user: ' + client)
var found = false;
for (var i = 0; i < userList.length; i++) {
if (userList[i].name === client) {
userList.splice(i, 1);
found = true;
}
}
//send out the updated users list
if (found) {
wss.broadcast(JSON.stringify({userList: userList}));
};
return found;
}
function pong(client) {
console.log('Server - ' + client + ' is still active');
clearTimeout(keepAlive);
setTimeout(function () {
ping(client);
}, keepAliveInterval);
}
//WebSocket message receive handler
ws.on('message', function incoming(message) {
if (isJson(message)) {
var obj = JSON.parse(message);
//client is responding to keepAlive
if (obj.keepAlive !== undefined) {
pong(obj.keepAlive.toLowerCase());
}
if (obj.action === 'join') {
console.log('Server - joining', obj);
//start pinging to keep alive
ping(obj.name.toLocaleLowerCase());
if (userList.filter(function(e) { return e.name == obj.name.toLowerCase(); }).length <= 0) {
userList.push({name: obj.name.toLowerCase()});
}
wss.broadcast(JSON.stringify({userList: userList}));
console.log('Server - broadcasting user list', userList);
}
}
console.log('Server - received: %s', message.toString());
return false;
});
});
Вот мой файл index.html:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Socket Test</title>
</head>
<body>
<div id="loading" style="display: none">
<p align="center">
LOADING...
</p>
</div>
<div id="login">
<p align="center">
<label for="name">Enter Your Name:</label>
<input type="text" id="name" />
<select id="role">
<option value="0">Attendee</option>
<option value="1">Presenter</option>
</select>
<button type="submit" onClick="login(document.getElementById('name').value, document.getElementById('role').value)">
Join
</button>
</p>
</div>
<div id="presentation" style="display: none">
<div class="slides">
<section>Slide 1</section>
<section>Slide 2</section>
</div>
<div id="online" style="font-size: 12px; width: 200px">
<strong>Users Online</strong>
<div id="userList">
</div>
</div>
</div>
<script>
function isJson(str) {
try {
JSON.parse(str);
}
catch (e) {
return false;
}
return true;
}
var ws;
var isChangedByMe = true;
var name = document.getElementById('name').value;
var role = document.getElementById('role').value;
function init()
{
loading = true;
ws = new WebSocket('wss://web-sockets-design1online.c9users.io:8081');
//Connection open event handler
ws.onopen = function(evt)
{
ws.send(JSON.stringify({action: 'connect', name: name, role: role}));
}
ws.onerror = function (msg) {
alert('socket error:' + msg.toString());
}
//if their socket closes unexpectedly, re-establish the connection
ws.onclose = function() {
init();
}
//Event Handler to receive messages from server
ws.onmessage = function(message)
{
console.log('Client - received socket message: '+ message.data.toString());
document.getElementById('loading').style.display = 'none';
if (message.data) {
obj = message.data;
if (obj.userList) {
//remove the current users in the list
userListElement = document.getElementById('userList');
while (userListElement.hasChildNodes()) {
userListElement.removeChild(userListElement.lastChild);
}
//add on the new users to the list
for (var i = 0; i < obj.userList.length; i++) {
var span = document.createElement('span');
span.className = 'user';
span.style.display = 'block';
span.innerHTML = obj.userList[i].name;
userListElement.appendChild(span);
}
}
}
if (message.data === '__ping__') {
ws.send(JSON.stringify({keepAlive: name}));
}
return false;
}
}
function login(userName, userRole) {
if (!userName) {
alert('You must enter a name.');
return false;
}
//set the global variables
name = userName;
role = userRole;
document.getElementById('loading').style.display = 'block';
document.getElementById('presentation').style.display = 'none';
document.getElementById('login').style.display = 'none';
init();
}
</script>
</body>
</html>
Здесь ссылка на облачную песочницу, если вы хотите попробовать ее самостоятельно: https://ide.c9.io/design1online/web-sockets
Ответ 4
Протокол websocket определяет управляющие кадры для ping и pong. Так что в основном, если сервер отправляет пинг, браузер ответит на понг, и он должен работать и наоборот. Возможно, сервер WebSocket, который вы используете, реализует их, и вы можете определить тайм-аут, в котором браузер должен отвечать или считаться мертвым. Это должно быть прозрачным для вашей реализации как в браузере, так и на сервере.
Вы можете использовать их для обнаружения полуоткрытых соединений: http://blog.stephencleary.com/2009/05/detection-of-half-open-dropped.html
Также уместно: Websockets ping/pong, почему не TCP keepalive?
Ответ 5
На стороне сервера внезапная потеря соединения или чистое отсоединение отправляют немедленный сигнал веб-рассылки, который можно интерпретировать с помощью:
def read_next_message(self):
nextByte = self.rfile.read(1)
if nextByte == b'' or nextByte == b'\x88': #Client is dead or disconnected
self.disconnect()
return
length = self.rfile.read(1)[0] & 127
# Continue to read message...
Таким образом, нет необходимости иметь таймаут.