簡體   English   中英

使用 websockets 處理連接丟失

[英]Handling connection loss with websockets

我最近設置了一個運行良好的本地 WebSocket 服務器,但是我在理解我應該如何處理客戶端或服務器故意啟動的突然丟失連接時遇到了一些麻煩,即:服務器斷電,以太網電纜拔出等...我需要客戶端知道連接是否在 ~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();

但是,如果我只是將以太網電纜從計算機背面拉出,或者禁用了連接,則不會調用onclose 我在另一篇文章中讀到它最終會在TCP 檢測到連接丟失時被調用,但它不是我需要的及時方式,因為我認為 Firefox 的默認值是 10 分鍾,我真的不想去大約數百台計算機about:config更改此值。 我讀過的唯一其他建議是使用“ping/pong”保持活動輪詢樣式方法,這似乎與 websockets 的想法相悖。

有沒有更簡單的方法來檢測這種斷開連接的行為? 從技術角度來看,我正在閱讀的舊帖子是否仍然是最新的,最好的方法仍然是“乒乓”風格?

你必須添加乒乓方法

收到__ping__ send __pong__后,在服務器中創建代碼

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 --//
}

這是我最終做的解決方案,目前似乎工作正常,它完全特定於我的項目設置並依賴於我的問題中最初未提及的標准,但它可能對其他人有用如果他們碰巧做同樣的事情。

與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);
}; 

我使用了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>

如果您想自己嘗試一下,這里是雲9沙箱的鏈接: https//ide.c9.io/design1online/web-sockets

websocket協議定義了ping和pong的控制幀 所以基本上,如果服務器發送ping,瀏覽器將用pong回答,它應該也可以反過來工作。 您使用的WebSocket服務器可能會實現它們,您可以定義瀏覽器必須響應或被視為死機的超時。 對於您在瀏覽器和服務器中的實現,這應該是透明的。

您可以使用它們來檢測半開連接: http//blog.stephencleary.com/2009/05/detection-of-half-open-dropped.html

還有關系: WebSockets ping / pong,為什么不用TCP keepalive?

好吧,我遲到了,但希望我能在這里增加一些價值。 我在Angular應用程序中的 TypeScript 實現以處理丟失的WebSocket連接。 這不使用PING PONG策略以避免讓服務器一直忙碌。 這僅在連接丟失后才開始嘗試建立連接,並在每 5 秒后繼續嘗試直到連接成功。 所以我們開始:

export class WebSocketClientComponent implements OnInit {

   webSocket?: WebSocket;
   selfClosing = false;
   reconnectTimeout: any;

   ngOnInit(): void {
      this.establishWebSocketConnection();
   }

   establishWebSocketConnection() {

      this.webSocket = new WebSocket('YourServerURlHere');
 
      this.webSocket.onopen = (ev: any) => {
  
         if (this.reconnectTimeout) {
            clearTimeout(this.reconnectTimeout);
         }
      }

      this.webSocket.onclose = (ev: CloseEvent) => {
  
         if (this.selfClosing === false) {
            this.startAttemptingToEstablishConnection();
         }
      }
   }

   private startAttemptingToEstablishConnection() {
      this.reconnectTimeout = setTimeout(() => this.establishWebSocketConnection(), 5000);
   }
}

就是這樣。 如果您想在某個時候關閉 websocket 連接,請設置selfClosing = true 這將停止再次嘗試重新連接。 希望這可以幫助。 我敢肯定,同樣的代碼也可以用在原生JS中。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM