服务器在WebSocket中重启时重新连接客户端


93

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

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

如何实现呢?就像我收到断开连接的信息一样,是否应该检查它并将其发送给JavaScript以刷新页面或重新连接?

Answers:


143

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

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

3
我希望有一种更优雅的方法,而无需构造新的对象和定义事件动作……
cimbor

3
5分钟后,浏览器冻结。我是唯一一个?
Marc

16
您应该添加“ ws = null;” 的setTimeout(),以避免之前乘法WS对象和eventHandligs
最大

8
如果我错了,请纠正我,但是此代码有点危险,因为一定数量的断开连接会导致堆栈溢出。那是因为您start递归调用而从未返回。
Forivin

10
@Forivin这里没有stackoverflow问题。由于在任何给定时刻Javascript中只有一个单线程执行我们的代码,因此setTimeout()计划传递的函数在该单线程再次释放时在将来执行。在这里调用setTimeout()之后,线程从函数返回(清除堆栈),然后处理队列中的下一个事件。最终它将到达我们的匿名函数,该函数调用start,并将其称为堆栈中的顶部框架。
臭臭的

39

Andrew提供的解决方案无法完美运行,因为如果失去连接,服务器可能会发送多个关闭事件。

在这种情况下,您将设置几个setTimout。仅当服务器在五秒钟前准备就绪时,安德鲁给出的解决方案才可能起作用。

然后,根据安德鲁解决方案进行了重新设计,我使用了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 */
 /* ... */
}

setTimeout如果您将“全局计时器ID”概念应用到它们,则仍然可以使用;)
RozzA,

3
“安德鲁给出的解决方案可能仅在服务器在五秒钟前准备就绪时才起作用。”-陈述不正确。如果服务器在五秒钟后仍然不可用,则您的客户端将无法打开WebSocket连接,并且onclose事件将再次被触发。
Sourav Ghosh

36

重新连接WebSocket

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

使用gzip压缩的压缩库小于600个字节。

官方存储库位于此处:

https://github.com/joewalnes/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秒之间。这也将分散服务器上的负载。
Jochem Schulenklopper

30

我已经将这种模式用于纯香草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事件发生时未启动,则一旦连接恢复在线,连接仍将恢复。

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


7
我只会更改:function check(){if(!ws || ws.readyState === WebSocket.CLOSED)start(); }
dieresys '16

1
这种方法,加上此处介绍的保持活动的技术,对我来说似乎很好。
彼得

@Peter,不确定ws状态是否打开,您需要(或应该)ping,如果我正确的话,它已经在websocket协议中了。这种过大的杀伤力只是在您的服务器上加载了...
comte '18

@comte某些ws服务器在客户端没有发送任何消息的“空闲期”后断开了与您的连接,因此为了保持连接打开状态,ping是非常必要的。
RozzA

2

以下是我在我的项目中使用的代码,它们可以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();   };  
}

2

无法发表评论,但以下内容:

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已经足够好了:)


1

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.


0

最后,我在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);
        // }
    };
}

0

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);   
        } 
    }
    .....
    .....
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.