【问题标题】:Reconnection of Client when server reboots in WebSocket服务器在 WebSocket 中重新启动时重新连接客户端
【发布时间】:2011-04-16 09:10:14
【问题描述】:

我正在使用使用 PHP5 和 Chrome 浏览器作为客户端的 Web 套接字。 我已从网站http://code.google.com/p/phpwebsocket/ 获取代码。

我运行服务器,客户端也已连接。我也可以聊天。 现在,当我重新启动服务器(通过杀死它并重新启动它)时, 客户端获取断开连接的信息,但是当我发送消息时不会自动与服务器重新连接。

如何做到这一点?比如当我得到断开连接的信息时,我应该检查它并将其发送到 JavaScript 以刷新页面还是重新连接?

【问题讨论】:

    标签: php websocket phpwebsocket


    【解决方案1】:

    当服务器重启时,Web Socket 连接关闭,因此触发 JavaScript onclose 事件。这是一个尝试每五秒重新连接一次的示例。

    function start(websocketServerLocation){
        ws = new WebSocket(websocketServerLocation);
        ws.onmessage = function(evt) { alert('message received'); };
        ws.onclose = function(){
            // Try to reconnect in 5 seconds
            setTimeout(function(){start(websocketServerLocation)}, 5000);
        };
    }
    

    【讨论】:

    • 我希望有更优雅的方式,无需构造新对象和定义事件动作...
    • 5 分钟后,浏览器冻结。只有我一个人吗?
    • 您应该添加“ws = null;”在 setTimeout() 之前避免乘以 ws 对象和 eventHandligs
    • 如果我错了,请纠正我,但这段代码有点危险,因为一定数量的断开连接会导致堆栈溢出。那是因为你递归地调用start,而没有返回。
    • @Forivin 这里没有堆栈溢出问题。由于在任何给定时刻,Javascript 中只有 1 个单线程执行我们的代码,因此 setTimeout() 会安排传递的函数在将来该单线程再次空闲时执行。此处调用 setTimeout() 后,线程从函数返回(清栈),然后去处理队列中的下一个事件。它最终会到达我们调用 start 的匿名函数,该函数将作为栈顶帧调用。
    【解决方案2】:

    Andrew 给出的解决方案并不完美,因为在连接丢失的情况下,服务器可能会发送几个关闭事件。

    在这种情况下,您将设置几个 setTimout。 Andrew 给出的解决方案可能只有在服务器在五秒之前准备好时才有效。

    然后,基于 Andrew 解决方案,重新设计,我利用 setInterval 将 ID 附加到窗口对象(这样它“无处不在”可用):

    var timerID=0;
    
    var socket;
    
    /* Initiate what has to be done */
    
    socket.onopen=function(event){
     /* As what was before */
     if(window.timerID){ /* a setInterval has been fired */
       window.clearInterval(window.timerID);
       window.timerID=0;
     }
     /* ... */
    }
    
    socket.onclose=function(event){
      /* ... */
     if(!window.timerID){ /* Avoid firing a new setInterval, after one has been done */
      window.timerID=setInterval(function(){start(websocketServerLocation)}, 5000);
     }
     /* That way, setInterval will be fired only once after losing connection */
     /* ... */
    }
    

    【讨论】:

    • 如果你对他们应用“全局计时器 id”的想法,你仍然可以使用setTimeout ;)
    • “Andrew 给出的解决方案可能只有在服务器在五秒前准备就绪的情况下才有效。”——该说法不正确。如果服务器在 5 秒后仍然不可用,您的客户端将无法打开 WebSocket 连接,并且将再次触发 onclose 事件。
    【解决方案3】:

    重新连接WebSocket

    有一个小的 JavaScript 库装饰 WebSocket API 以提供一个 WebSocket 连接,如果连接断开,该连接将自动重新连接。

    gzip 压缩的压缩库小于 600 字节。

    存储库在此处可用:

    https://github.com/joewalnes/reconnecting-websocket

    还有一个 TypeScript 库。只需包含它并将new WebSocket 替换为new ReconnectingWebSocket

    存储库在此处可用:

    https://github.com/pladaria/reconnecting-websocket

    服务器泛滥

    如果服务器重新启动时有大量客户端连接到服务器。 通过使用指数退避算法来管理客户端的重新连接时间可能是值得的。

    算法是这样工作的:

    1. 对于 k 次尝试,生成 0 到 2^k - 1 之间的随机时间间隔,
    2. 如果您能够重新连接,请将 k 重置为 1,
    3. 如果重新连接失败,k 增加 1,进程在第 1 步重新开始,
    4. 为了截断最大间隔,当尝试 k 达到一定次数时,k 在每次尝试后停止增加。

    参考:

    http://blog.johnryding.com/post/78544969349/how-to-reconnect-web-sockets-in-a-realtime-web-app

    ReconnectingWebSocket 不使用此算法处理重新连接。

    【讨论】:

    • 很好的答案,特别是因为它提到一旦服务器关闭 Web 套接字连接并且所有客户端(可能是数百或数千个)尝试同时重新连接时服务器负载高的风险。除了指数退避,您还可以将延迟随机化,例如在 0 到 10 秒之间。这也会分散服务器上的负载。
    • @MartinSchilliger,我重新编辑了答案以保留对 Javascript 库的引用,并添加了对您提供的 Typescript 的引用。如果我理解正确,Javascript 代码可以在 Typescript 代码中使用,但不能反过来,这就是为什么我认为保留对 Javascript 库的引用很重要,因为它可以帮助更多的用例。我希望这种变化对你来说会很好。
    • @JoëlEsponde 谢谢。 TypeScript 库还包括一个 JS 版本,因此也只能与 JS 一起使用(我自己做)。如果我是对的,第一个库被暂停,只有 TypeScript 正在积极开发。
    • @MartinSchilliger,谢谢!我查看了 typescript 库,没有看到任何实现 ReconnectingWebSocket 的 .js 文件。我刚看到一个 .ts 文件。所以,我猜你在编译 .ts 文件后得到了一个 javascript 文件? AFAIK,网络浏览器不支持本机打字稿文件。
    • @JoëlEsponde npm-package 包含编译的 .js 文件也在 /dist 文件夹中(像往常一样),所以它只能与 JavaScript 一起使用。
    【解决方案4】:

    我已经将这个模式用于纯原生 JavaScript 有一段时间了,它支持的情况比其他答案多。

    document.addEventListener("DOMContentLoaded", function() {
    
      'use strict';
    
      var ws = null;
    
      function start(){
    
        ws = new WebSocket("ws://localhost/");
        ws.onopen = function(){
          console.log('connected!');
        };
        ws.onmessage = function(e){
          console.log(e.data);
        };
        ws.onclose = function(){
          console.log('closed!');
          //reconnect now
          check();
        };
    
      }
    
      function check(){
        if(!ws || ws.readyState == 3) start();
      }
    
      start();
    
      setInterval(check, 5000);
    
    
    });
    

    这将在服务器关闭连接后立即重试,并且它会检查连接以确保它也每 5 秒启动一次。

    因此,如果服务器在运行时或在 onclose 事件发生时未启动,则连接仍会在重新联机后恢复。

    注意:使用此脚本不会让您停止尝试打开连接...但我认为这就是您想要的?

    【讨论】:

    • 我只会改变:function check(){ if(!ws || ws.readyState === WebSocket.CLOSED) start(); }
    • 这种方法,加上here 描述的保持活动技术,似乎对我很有效。
    • @Peter,不确定 ws 状态是否打开,您需要(或应该)ping,如果我是正确的,它已经在 websocket 协议中。这种过度杀伤力只是在您的服务器上加载...
    • @comte 一些 ws 服务器在客户端没有发送消息的“空闲期”后断开您的连接,因此为了保持连接打开,ping 是一种邪恶的必要性。
    【解决方案5】:

    以下是我在项目中使用的代码,它们 100% 有效。

    1. 将所有 websocket 代码放入 init 函数中。
    2. 在 onclose 回调中再次调用 init。
    3. 最后调用文档就绪函数内部的init函数。

    var name = sessionStorage.getItem('name');

    wsUri =  "ws://localhost:8080";   
    var websocket;
    $(function() {  
        init();  
        $("#chat_text_box").on("keypress", function(e) {         
            if (e.keyCode == 13) {   //For Enter Button    
                e.preventDefault();
                var mymessage = $('#chat_text_box').val();               
                if(mymessage){
                    var msg = {  type: 'chat_text',  data : {  name:name,  msg:mymessage }  };                
                    console.log(msg);
                    websocket.send(JSON.stringify(msg));
                    $('#chat_text_box').val('');
                }               
                return false;                       
            }        
        });      
    });     
    function init() { 
        websocket = new WebSocket(wsUri);      
        websocket.onopen = function(ev) { /*connection is open */    } 
        websocket.onmessage = function(ev) {        
            var data = JSON.parse(ev.data); //PHP sends Json data        
            var type = data.type;//alert(JSON.stringify(data));
            switch(type) {
                case "chat_text":
                    var text = "<div><span class='user'>"+data.data.sender_name+" : </span><span class='msg'>"+data.data.msg+"</span></div>";
                    $('#chat-messages').append(text);
                    break;            
                default:
                    break;
    
            }        
    
        };     
        websocket.onerror   = function(ev){}; 
        websocket.onclose = function(ev) {   init();   };  
    }
    

    【讨论】:

      【解决方案6】:

      无法评论,但以下内容:

      var socket;
      
      const socketMessageListener = (event) => {
        console.log(event.data);
      };
      
      const socketOpenListener = (event) => {
        console.log('Connected');
        socket.send('hello');
      };
      
      const socketCloseListener = (event) => {
        if (socket) {
          console.error('Disconnected.');
        }
        socket = new WebSocket('ws://localhost:8080');
        socket.addEventListener('open', socketOpenListener);
        socket.addEventListener('message', socketMessageListener);
        socket.addEventListener('close', socketCloseListener);
      };
      
      socketCloseListener();
      
      // for testing
      setTimeout(()=>{
        socket.close();
      },5000);
      

      加上https://www.npmjs.com/package/back 已经足够好了:)

      【讨论】:

      • 这个最适合我。它在 ReactJS 中运行良好。其他的不太好。您仍然可以将套接字创建部分放在 5 秒计时器内,以避免它锁定浏览器,尤其是在 Inspect 打开的情况下。
      【解决方案7】:

      function wsConnection(url){
          var ws = new WebSocket(url);
          var s = (l)=>console.log(l);
      	ws.onopen = m=>s(" CONNECTED")
          ws.onmessage = m=>s(" RECEIVED: "+JSON.parse(m.data))
          ws.onerror = e=>s(" ERROR")
          ws.onclose = e=>{
              s(" CONNECTION CLOSED");
              setTimeout((function() {
                  var ws2 = new WebSocket(ws.url);
      			ws2.onopen=ws.onopen;
                  ws2.onmessage = ws.onmessage;
                  ws2.onclose = ws.onclose;
                  ws2.onerror = ws.onerror;
                  ws = ws2
              }
              ).bind(this), 5000)
          }
          var f = m=>ws.send(JSON.stringify(m)) || "Sent: "+m;
          f.ping = ()=>ws.send(JSON.stringify("ping"));
          f.close = ()=>ws.close();
          return f
      }
      
      c=new wsConnection('wss://echo.websocket.org');
      setTimeout(()=>c("Hello world...orld...orld..orld...d"),5000);
      setTimeout(()=>c.close(),10000);
      setTimeout(()=>c("I am still alive!"),20000);
      <pre>
      This code will create a websocket which will 
      reconnect automatically after 5 seconds from disconnection.
      
      An automatic disconnection is simulated after 10 seconds.

      【讨论】:

        【解决方案8】:

        WebSocket 的客户端关闭事件有一个 wasClean 属性,这对我很有用。在客户端计算机进入睡眠模式等情况或服务器意外停止等情况下,它似乎设置为 true。如果您手动关闭套接字,它设置为 false,在这种情况下您不想打开套接字自动再次。以下来自 Angular 7 项目的代码。我在服务中有这段代码,所以它可以从任何组件中使用。

            notifySocketClose(event) { 
        
                if (!event.wasClean) { 
                    setTimeout(() => {
                        this.setupSocket()
                    }, 1000);       
                }
            }
        
            setupSocket() { // my function to handle opening of socket, event binding etc.
            .....
            .....
        
                    this.websocketConnection = this.websocketConnection ? this.websocketConnection : new WebSocket(socketUrl);
                    this.websocketConnection.onclose = this.notifySocketClose.bind(this);   
                } 
            }
            .....
            .....
        
        

        【讨论】:

          【解决方案9】:

          最后我在vue+ts中让ws自动重连,类似如下:

          private async mounted() {
              // Connect to WebSocket
              const sn = "sb1234567890";
              const host =
                  window.location.protocol == "https:"
                      ? "wss://www.xxx.net"
                      : process.env.DEV_TYPE === "fullstack"
                      ? "ws://10.0.0.14:8528"
                      : "ws://www.xxx.net:8528";
              const wsUri = host + "/feed-home/" + sn;
              await this.startWs(wsUri, sn);
              // !!!Deprecated: failed to reconnect
              // let ws = new WebSocket();
              // console.log(ws);
              // ws.onopen = async function(event) {
              //     console.log(event, "openEvent");
              //     clearInterval(that.timer);
              // };
              // ws.onclose = async function(event) {
              //     console.log(event, "close");
              //     that.timer = setInterval(() => {
              //         console.log("Heart Beat");
              //         ws.send("HeartBeat");
              //         // ws = new WebSocket("ws://10.0.0.14:8528/feed-home/" + sn);
              //         console.log(ws);
              //     }, 60000);
              // };
              // ws.onmessage = async function(event) {
              //     console.log(event, "ws");
              //     alert("get it!");
              //     await alert("please update!");
              //     await that.getHome(sn);
              // };
          }
          private wsReconnected: boolean = false; // check whether WebSocket is reconnected
          private async startWs(uri: string, sn: string) {
              let that = this;
              let ws = new WebSocket(uri);
              ws.onopen = async () => {
                  if (that.wsReconnected) {
                      await that.getHome(sn); // Refresh api data after reconnected
                  }
                  ws.send("Current client version: " + window.version);
              };
              ws.onmessage = async evt => {
                  await that.getHome(sn);
                  that.$message({
                      type: "success",
                      message: evt.data,
                      showClose: true,
                      center: true,
                      duration: 20 * 1000
                  });
              };
              ws.onclose = async () => {
                  that.wsReconnected = true;
                  await that.startWs(uri, sn);
                  const sleep = (seconds: number) => {
                      return new Promise(resolve =>
                          setTimeout(resolve, seconds * 1000)
                      );
                  };
                  await sleep(10); // Try to reconnect in 10 seconds
                  // !!!Deprecated: Use timer will cause multiply ws connections
                  // if (!that.wsTimer) {
                  //     // Try to reconnect in 10 seconds
                  //     that.wsTimer = setInterval(async () => {
                  //         console.log("reconnecting websocket...");
                  //         await that.startWs(uri, sn);
                  //     }, 10 * 1000);
                  // }
              };
          }
          

          【讨论】:

            猜你喜欢
            • 2015-11-20
            • 2018-02-10
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2015-10-23
            • 2013-11-10
            • 2016-08-16
            • 2020-09-05
            相关资源
            最近更新 更多