Ponowne połączenie klienta po ponownym uruchomieniu serwera w technologii WebSocket


94

Używam gniazda sieciowego przy użyciu PHP5 i przeglądarki Chrome jako klienta. Pobrałem kod ze strony http://code.google.com/p/phpwebsocket/ .

Uruchamiam serwer, a klient też jest podłączony. Ja też mogę rozmawiać. Teraz, kiedy ponownie uruchamiam serwer (zabijając go i uruchamiając ponownie), klient otrzymuje rozłączone informacje, ale automatycznie nie łączy się ponownie z serwerem, gdy wysyłam wiadomość.

Jak to osiągnąć? Na przykład, kiedy dostaję odłączone informacje, czy powinienem je sprawdzić i wysłać do JavaScript w celu odświeżenia strony lub ponownego połączenia?

Odpowiedzi:


143

Po ponownym uruchomieniu serwera połączenie Web Socket jest zamykane, więc onclosewyzwalane jest zdarzenie JavaScript . Oto przykład, który próbuje połączyć się ponownie co pięć sekund.

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
Miałem nadzieję, że istnieje bardziej elegancki sposób, bez konstruowania nowego obiektu i definiowania działań związanych z wydarzeniami ...
ciembor

3
Po 5 minutach przeglądarka zawiesza się. Czy jestem jedynym?
Marc

16
Należy dodać „ws = null;” przed setTimeout (), aby uniknąć mnożenia obiektów ws i eventHandligs
maksymalnie

8
Popraw mnie, jeśli się mylę, ale ten kod jest trochę niebezpieczny, ponieważ pewna liczba rozłączeń spowoduje przepełnienie stosu. Dzieje się tak, ponieważ dzwonisz startrekurencyjnie, bez powrotu.
Forivin

11
@Forivin Brak problemu z przepełnieniem stosu tutaj. Ponieważ w Javascript jest tylko 1 pojedynczy wątek wykonujący nasz kod w dowolnym momencie, setTimeout () planuje wykonanie przekazanej funkcji w przyszłości, gdy ten pojedynczy wątek będzie znowu wolny. Po wywołaniu setTimeout () w tym miejscu wątek wraca z funkcji (czyści stos), a następnie przechodzi do przetwarzania następnego zdarzenia w kolejce. W końcu dotrze do naszej anonimowej funkcji, która wywołuje start i zostanie wywołana jako górna ramka na stosie.
Śmierdzący

40

Rozwiązanie podane przez Andrzeja nie działa idealnie, ponieważ w przypadku utraty połączenia serwer może wysłać kilka bliskich zdarzeń.

W takim przypadku ustawisz kilka setTimout. Rozwiązanie podane przez Andrzeja może działać tylko wtedy, gdy serwer będzie gotowy przed upływem pięciu sekund.

Następnie, w oparciu o rozwiązanie Andrew, przerobione, skorzystałem z setInterval dołączając identyfikator do obiektu okna (dzięki czemu jest on dostępny „wszędzie”):

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

możesz nadal używać, setTimeoutjeśli zastosujesz do nich ideę "globalnego identyfikatora timera";)
RozzA

3
„Rozwiązanie podane przez Andrzeja może działać tylko wtedy, gdy serwer będzie gotowy przed upływem pięciu sekund.” - Stwierdzenie nie jest prawdziwe. Jeśli serwer jest nadal niedostępny po pięciu sekundach, Twój klient nie otworzy połączenia WebSocket i onclosezdarzenie zostanie uruchomione ponownie.
Sourav Ghosh

36

ReconnectingWebSocket

GitHub hostuje małą bibliotekę JavaScript, która zdobi interfejs API WebSocket, aby zapewnić połączenie WebSocket, które automatycznie nawiąże połączenie, jeśli połączenie zostanie porzucone.

Zminimalizowana biblioteka z kompresją gzip ma mniej niż 600 bajtów.

Oficjalne repozytorium jest dostępne tutaj:

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

Powódź serwera

Jeśli duża liczba klientów jest połączona z serwerem podczas ponownego uruchamiania. Warto zarządzać czasami ponownego łączenia klientów przy użyciu algorytmu wykładniczego wycofywania.

Algorytm działa w ten sposób:

  1. Dla k prób wygeneruj losowy przedział czasu między 0 a 2 ^ k - 1,
  2. Jeśli możesz się ponownie połączyć, zresetuj k do 1,
  3. Jeśli ponowne połączenie się nie powiedzie, k wzrasta o 1, a proces jest uruchamiany ponownie od kroku 1,
  4. Aby skrócić maksymalny interwał, po osiągnięciu określonej liczby prób k, k przestaje rosnąć po każdej próbie.

Odniesienie:

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

ReconnectingWebSocket nie obsługuje ponownych połączeń przy użyciu tego algorytmu.


Świetna odpowiedź, zwłaszcza, że ​​wspomina o ryzyku dużego obciążenia serwera, gdy serwer zamyka połączenia przez gniazdo sieciowe, a wszyscy klienci (których może być setki lub tysiące) próbują połączyć się ponownie w tym samym czasie. Zamiast wykładniczego wycofywania, możesz również losowo ustawić opóźnienie, na przykład od 0 do 10 sekund. Spowoduje to również rozłożenie obciążenia na serwerze.
Jochem Schulenklopper

29

Używam tego wzoru od jakiegoś czasu dla czystego waniliowego JavaScript i obsługuje on o kilka więcej przypadków niż inne odpowiedzi.

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


});

Spowoduje to ponowienie próby, gdy tylko serwer zamknie połączenie, a także sprawdzi połączenie, aby upewnić się, że jest aktywne również co 5 sekund.

Więc jeśli serwer nie działa, gdy to działa lub w czasie zdarzenia onclose, połączenie zostanie przywrócone, gdy znów będzie online.

UWAGA: Korzystanie z tego skryptu nigdy nie pozwoli Ci przestać próbować otworzyć połączenia ... ale myślę, że tego właśnie chcesz?


7
Zmieniłbym tylko: function check () {if (! Ws || ws.readyState === WebSocket.CLOSED) start (); }
dieresys

1
To podejście, wraz z opisaną tutaj techniką utrzymywania przy życiu , wydaje mi się dobrze działać.
Peter

@Peter, nie jestem pewien, czy stan ws jest otwarty, musisz (lub powinieneś) pingować, jeśli mam rację, to już jest w protokole websocket. Ta przesada właśnie załadowała się na twój serwer ...
comte

@comte niektóre serwery ws rozłączają cię po „okresie bezczynności”, kiedy żadne wiadomości nie są wysyłane od klienta, więc aby utrzymać otwarte połączenie, ping jest złą koniecznością.
RozzA

2

Poniżej znajdują się kody, których użyłem w swoim projekcie, które działają w 100%.

  1. Umieść cały kod websocket wewnątrz funkcji init.
  2. Wewnątrz wywołania zwrotnego onclose ponownie wywołaj init.
  3. Na koniec wywołaj funkcję init wewnątrz funkcji gotowości dokumentu.

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

Nie można komentować, ale następujące kwestie:

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

Plus https://www.npmjs.com/package/back jest już wystarczająco dobry :)


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

Na koniec wykonuję automatyczne ponowne łączenie ws w vue + ts, podobnie jak poniżej:

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

Zdarzenie zamknięcia po stronie klienta dla WebSocket ma właściwość wasClean , która była dla mnie przydatna. Wygląda na to, że jest ustawiona na true w przypadkach, gdy komputer kliencki przechodzi w tryb uśpienia itp. Lub gdy serwer jest nieoczekiwanie zatrzymany itp. Jest ustawiona na false, jeśli ręcznie zamkniesz gniazdo, w którym to przypadku nie chcesz otwierać gniazdo ponownie automatycznie. Poniżej kod z projektu Angular 7. Mam ten kod w usłudze, więc można go używać z dowolnego komponentu.

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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.