การเชื่อมต่อใหม่ของไคลเอ็นต์เมื่อเซิร์ฟเวอร์รีบูตใน WebSocket


94

ฉันใช้เว็บซ็อกเก็ตโดยใช้ PHP5 และเบราว์เซอร์ Chrome เป็นไคลเอนต์ ฉันได้นำรหัสจากเว็บไซต์http://code.google.com/p/phpwebsocket/

ฉันเรียกใช้เซิร์ฟเวอร์และไคลเอนต์ก็เชื่อมต่อด้วย ทักแชทได้เหมือนกัน ตอนนี้เมื่อฉันรีสตาร์ทเซิร์ฟเวอร์ (โดยการฆ่ามันและเริ่มต้นใหม่อีกครั้ง) ไคลเอนต์จะได้รับข้อมูลที่ถูกตัดการเชื่อมต่อ แต่จะไม่เชื่อมต่อกับเซิร์ฟเวอร์อีกครั้งโดยอัตโนมัติเมื่อฉันส่งข้อความ

จะบรรลุเป้าหมายนี้ได้อย่างไร? เช่นเดียวกับเมื่อฉันได้รับข้อมูลที่ไม่เชื่อมต่อฉันควรตรวจสอบและส่งไปยัง JavaScript เพื่อรีเฟรชหน้าหรือเชื่อมต่อใหม่?

คำตอบ:


143

เมื่อเซิร์ฟเวอร์รีบูตการเชื่อมต่อเว็บซ็อกเก็ตจะปิดดังนั้น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
ฉันหวังว่าจะมีวิธีที่หรูหรากว่านี้โดยไม่ต้องสร้างวัตถุใหม่และกำหนดการกระทำของเหตุการณ์ ...
ciembor

3
หลังจากผ่านไป 5 นาทีเบราว์เซอร์จะหยุดทำงาน ฉันเป็นคนเดียวเหรอ?
Marc

16
คุณควรเพิ่ม "ws = null;" ก่อน setTimeout () เพื่อหลีกเลี่ยงการคูณวัตถุ ws และ eventHandligs
สูงสุด

8
แก้ไขฉันถ้าฉันผิด แต่รหัสนี้ค่อนข้างอันตรายเนื่องจากการตัดการเชื่อมต่อจำนวนหนึ่งจะทำให้สแตกล้น นั่นเป็นเพราะคุณโทรstartซ้ำโดยไม่เคยกลับมา
Forivin

11
@Forivin ไม่มีปัญหา stackoverflow ที่นี่ เนื่องจากมีเพียง 1 เธรดเดียวใน Javascript ที่รันโค้ดของเราในช่วงเวลาใดเวลาหนึ่ง setTimeout () จึงกำหนดเวลาให้ฟังก์ชันการส่งผ่านถูกเรียกใช้งานในอนาคตเมื่อเธรดเดี่ยวนั้นว่างอีกครั้ง หลังจาก setTimeout () ถูกเรียกที่นี่เธรดจะกลับมาจากฟังก์ชัน (การล้างสแต็ก) จากนั้นไปที่การประมวลผลเหตุการณ์ถัดไปในคิว ในที่สุดมันจะเข้าสู่ฟังก์ชั่นนิรนามของเราที่เรียก start และจะถูกเรียกว่าเป็นเฟรมบนสุดในสแตก
Stinky

40

โซลูชันที่ให้โดย Andrew ทำงานได้ไม่สมบูรณ์เนื่องจากในกรณีที่ขาดการเชื่อมต่อเซิร์ฟเวอร์อาจส่งเหตุการณ์ปิดหลายครั้ง

ในกรณีนี้คุณจะต้องตั้งค่า setTimout หลายรายการ โซลูชันที่ให้โดย 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 */
 /* ... */
}

คุณยังสามารถใช้ได้setTimeoutถ้าคุณใช้แนวคิด "global timer id" กับพวกเขา;)
RozzA

3
"โซลูชันที่แอนดรูว์มอบให้อาจใช้งานได้ก็ต่อเมื่อเซิร์ฟเวอร์พร้อมก่อนห้าวินาทีเท่านั้น" - ข้อความดังกล่าวไม่เป็นความจริง หากเซิร์ฟเวอร์ยังไม่พร้อมใช้งานหลังจากผ่านไปห้าวินาทีไคลเอนต์ของคุณจะไม่สามารถเปิดการเชื่อมต่อ WebSocket ได้และoncloseเหตุการณ์จะเริ่มทำงานอีกครั้ง
Sourav Ghosh

36

การเชื่อมต่อใหม่ WebSocket

GitHub โฮสต์ไลบรารี JavaScript ขนาดเล็กที่ตกแต่ง WebSocket API เพื่อให้การเชื่อมต่อ WebSocket ซึ่งจะเชื่อมต่อใหม่โดยอัตโนมัติหากการเชื่อมต่อหลุด

Minified library ที่บีบอัด gzip น้อยกว่า 600 ไบต์

มีที่เก็บอย่างเป็นทางการที่นี่:

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

เซิร์ฟเวอร์ท่วม

หากมีไคลเอ็นต์จำนวนมากเชื่อมต่อกับเซิร์ฟเวอร์เมื่อรีบูต การจัดการการกำหนดเวลาเชื่อมต่อใหม่ของไคลเอ็นต์อาจคุ้มค่าโดยใช้อัลกอริทึม Exponential Backoff

อัลกอริทึมทำงานดังนี้:

  1. สำหรับความพยายาม k ให้สร้างช่วงเวลาสุ่มระหว่าง 0 ถึง 2 ^ k - 1
  2. หากคุณสามารถเชื่อมต่อใหม่ได้ให้รีเซ็ต k เป็น 1
  3. หากการเชื่อมต่อใหม่ล้มเหลว k จะเพิ่มขึ้น 1 และกระบวนการเริ่มต้นใหม่ที่ขั้นตอนที่ 1
  4. หากต้องการตัดช่วงเวลาสูงสุดเมื่อถึงจำนวนครั้งที่กำหนด k แล้ว k จะหยุดเพิ่มขึ้นหลังจากความพยายามแต่ละครั้ง

Référence:

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

การเชื่อมต่อใหม่ WebSocket ไม่จัดการการเชื่อมต่อใหม่โดยใช้อัลกอริทึมนี้


คำตอบที่ดีโดยเฉพาะอย่างยิ่งเนื่องจากกล่าวถึงความเสี่ยงของการโหลดเซิร์ฟเวอร์สูงเมื่อเซิร์ฟเวอร์ปิดการเชื่อมต่อซ็อกเก็ตเว็บและไคลเอนต์ทั้งหมด (ซึ่งอาจมีจำนวนนับร้อยหรือหลายพัน) พยายามเชื่อมต่อใหม่ในเวลาเดียวกัน แทนที่จะเป็นการย้อนกลับแบบเอ็กซ์โปเนนเชียลคุณสามารถสุ่มการหน่วงเวลาเช่นระหว่าง 0 ถึง 10 วินาที ที่จะกระจายโหลดบนเซิร์ฟเวอร์ด้วย
Jochem Schulenklopper

29

ฉันใช้แพทเทนนี้มาระยะหนึ่งแล้วสำหรับ 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

1
วิธีนี้รวมถึงเทคนิคการรักษาชีวิตที่อธิบายไว้ที่นี่ดูเหมือนจะใช้ได้ดีสำหรับฉัน
ปีเตอร์

@Peter ไม่แน่ใจว่าสถานะ ws เปิดอยู่คุณจำเป็นต้อง (หรือควร) ping หรือไม่ถ้าฉันถูกต้องมันอยู่ในโปรโตคอล websocket แล้ว overkill นี้เพิ่งโหลดบนเซิร์ฟเวอร์ของคุณ ...
comte

@comte เซิร์ฟเวอร์ ws บางตัวจะตัดการเชื่อมต่อคุณหลังจาก 'ช่วงเวลาว่าง' ที่ไม่มีข้อความใด ๆ ถูกส่งจากไคลเอนต์และเพื่อให้การเชื่อมต่อเปิดอยู่การ ping จึงเป็นความจำเป็นที่ชั่วร้าย
RozzA

2

ด้านล่างนี้คือรหัสที่ฉันใช้ในโครงการของฉันซึ่งใช้งานได้ 100%

  1. ใส่รหัส websocket ทั้งหมดในฟังก์ชัน init
  2. ภายใน onclose callback เรียก init อีกครั้ง
  3. สุดท้ายเรียกฟังก์ชัน init ภายในฟังก์ชันพร้อมเอกสาร

ชื่อ var = sessionStorage.getItem ('ชื่อ');

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

สุดท้ายฉันทำการเชื่อมต่ออัตโนมัติ ws ใน vue + ts ซึ่งคล้ายกับดังต่อไปนี้:

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 ในกรณีที่คอมพิวเตอร์ไคลเอนต์เข้าสู่โหมดสลีปเป็นต้นหรือเมื่อเซิร์ฟเวอร์หยุดทำงานโดยไม่คาดคิดเป็นต้นมันถูกตั้งค่าเป็นเท็จหากคุณปิดซ็อกเก็ตด้วยตนเองซึ่งในกรณีนี้คุณไม่ต้องการเปิด ซ็อกเก็ตโดยอัตโนมัติอีกครั้ง ด้านล่างรหัสจากโครงการ 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);   
        } 
    }
    .....
    .....

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.