web-dev-qa-db-fra.com

Gérer la perte de connexion avec les websockets

J'ai récemment mis en place un serveur WebSocket local qui fonctionne bien, mais j'ai quelques problèmes pour comprendre comment gérer une perte de connexion soudaine que ni le client ni le serveur n'ont déclenchée intentionnellement, c'est-à-dire: le serveur perd son alimentation, les câbles Ethernet retiré etc ... J'ai besoin que le client sache si la connexion a été perdue en ~ 10 secondes.

Côté client, la connexion est tout simplement:

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!');
};

Je peux déclencher manuellement la déconnexion de la connexion qui fonctionne bien,

websocket_conn.close();

Mais si j'ai simplement retiré le câble Ethernet à l'arrière de l'ordinateur ou désactivé la connexion, onclose n'est pas appelé. J'ai lu dans un autre article qu'il serait finalement appelé quand TCP détecte une perte de connectivité , mais ce n'est pas en temps opportun dont j'ai besoin par défaut pour Firefox, je crois que c'est 10 minutes, et je je ne veux pas vraiment faire le tour de centaines d'ordinateurs about:config modification de cette valeur. La seule autre suggestion que j'ai lue est d'utiliser une méthode de style d'interrogation persistante "ping/pong" qui semble contre-intuitive à l'idée de websockets.

Existe-t-il un moyen plus simple de détecter ce type de comportement de déconnexion? Les anciens messages que je lis sont-ils toujours à jour d'un point de vue technique, et la meilleure méthode est-elle toujours de style "ping/pong"?

27
MLeFevre

C'est la solution que j'ai fini par faire qui semble bien fonctionner pour le moment, elle est entièrement spécifique à la configuration de mon projet et repose sur des critères en place qui n'étaient pas mentionnés à l'origine dans ma question, mais cela pourrait être utile pour quelqu'un d'autre s'il se trouve qu'ils font la même chose.

La connexion au serveur websocket se fait dans un module complémentaire Firefox, et par défaut, la configuration de Firefox TCP a un délai de 10 minutes. Vous pouvez voir des détails supplémentaires avec about:config et recherche de TCP.

Les modules complémentaires de Firefox peuvent accéder à ces paramètres

var prefs = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService);

et modifiez également ces paramètres en spécifiant la branche et la préférence ainsi que la nouvelle valeur

prefs.getBranch("network.http.tcp_keepalive.").setIntPref('long_lived_idle_time', 10);

Alors maintenant, tout ordinateur avec l'addon installé a un délai de 10 secondes pour les connexions TCP. Si la connexion est perdue, l'événement onclose est déclenché, ce qui affiche une alerte et tente également pour rétablir la connexion

websocket_conn.onclose = function (e) {
    document.getElementById('websocket_no_connection').style.display = 'block';
    setTimeout(my_extension.setup_websockets, 10000);
}; 
7
MLeFevre

Vous devez ajouter la méthode de ping-pong

Créer un code sur le serveur lors de la réception __ ping __ envoyer __ pong __ retour

Le code JavaScript est donné ci-dessous

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 --//
}
28
Sarath Ak

J'ai utilisé l'idée de ping/pong et cela fonctionne bien. Voici mon implémentation dans mon fichier 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;
  });
});

Voici mon fichier 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>

Voici un lien vers le sandbox cloud 9 si vous voulez l'essayer vous-même: https://ide.c9.io/design1online/web-sockets

4
Jade

Le protocole websocket définit cadres de contrôle pour ping et pong . Donc, fondamentalement, si le serveur envoie un ping, le navigateur répondra avec un pong, et cela devrait également fonctionner dans l'autre sens. Le serveur WebSocket que vous utilisez les met probablement en œuvre et vous pouvez définir un délai d'expiration pendant lequel le navigateur doit répondre ou être considéré comme mort. Cela devrait être transparent pour votre implémentation dans le navigateur et le serveur.

Vous pouvez les utiliser pour détecter les connexions semi-ouvertes: http://blog.stephencleary.com/2009/05/detection-of-half-open-dropped.html

Également pertinent: WebSockets ping/pong, pourquoi pas TCP keepalive?

1
vtortola