使用 websocket 处理连接丢失

Posted

技术标签:

【中文标题】使用 websocket 处理连接丢失【英文标题】:Handling connection loss with websockets 【发布时间】:2015-01-14 06:24:00 【问题描述】:

我最近设置了一个运行良好的本地 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 detects loss of connectivity 时被调用,但我认为 Firefox 的默认设置不是及时的,我相信它是 10 分钟,我真的不想花几百分钟计算机about:config 更改此值。我读过的唯一其他建议是使用“ping/pong”保持活动轮询样式方法,这似乎与 websockets 的想法相悖。

是否有更简单的方法来检测这种断开连接行为?从技术角度来看,我正在阅读的旧帖子是否仍然是最新的,最好的方法仍然是“乒乓”风格?

【问题讨论】:

如果您不想自己处理保持活动令牌、计划重新连接尝试等等,那么您应该使用经过验证的库。 socket.io 浮现在脑海中。 【参考方案1】:

你必须添加乒乓球方法

接收__ping__发送__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 实现结合使用时,这对我非常有用:github.com/joewalnes/reconnecting-websocket,但需要注意的是:我还必须将间隔计时器分配给一个变量并在 ping 超时函数中清除它. 如果你正在使用它进行轮询,那么 websocket 的意义何在 :) @stevemoretz 我实际上是在问自己 Websockets 似乎是 TCP/IP 的一个非常薄的层,因此如果您需要在当前的 TCP/IP 实现之外实现附加功能,则必须自己添加它。因此,为什么回答者说您必须自己添加 ping/ping。但是,如果 websockets 内置了这个功能就好了,这样它就可以配置并且更通用,因为这似乎是一个常见的烦恼。【参考方案2】:

这是我最终做的解决方案,目前似乎工作正常,它完全特定于我的项目设置并依赖于我的问题中最初没有提到的标准,但它可能有用如果其他人碰巧在做同样的事情。

到 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);
; 

【讨论】:

因为现在几乎没有比这更合适的答案了,这里最好的做法可能是接受你的。【参考方案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 );

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

如果您想自己尝试一下,这里是 cloud 9 沙盒的链接:https://ide.c9.io/design1online/web-sockets

【讨论】:

我认为您缺少一些变量和代码行。 obj.action === 'join' 那是从哪里来的呢?而且我认为您的变量缺少清除您的 setTimeout。 obj.action === join 来自于点击 join 按钮。 setTimeout 在 keepAlive 中被引用。 为什么要定义两次 wss.broadcast? 这只是一个错字,固定,ty【参考方案4】:

websocket 协议定义了control frames for ping and pong。所以基本上,如果服务器发送一个 ping,浏览器会回复一个 pong,它也应该反过来工作。可能您使用的 WebSocket 服务器实现了它们,并且您可以定义浏览器必须响应或被视为死机的超时。这对于您在浏览器和服务器中的实现应该是透明的。

您可以使用它们来检测半开连接:http://blog.stephencleary.com/2009/05/detection-of-half-open-dropped.html

也相关:WebSockets ping/pong, why not TCP keepalive?

【讨论】:

答案不清楚。如何在javascript websocket中实现当网线断开时弹出警报的解决方案?【参考方案5】:

好的,我迟到了,但希望我能在这里增加一些价值。我在 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 中使用。

【讨论】:

以上是关于使用 websocket 处理连接丢失的主要内容,如果未能解决你的问题,请参考以下文章

使用WebSocket实现聊天室

使用WebSocket实现聊天室

WebSocket 客户端的状态不会因网络丢失而改变

使用 websocket 的 Web 应用程序“无法建立连接。接收端不存在。”

基于“广播”或“房间”的 Websocket 策略

记一次websocket连接不上问题?