EventSource(SSE)是否应该尝试无限期重新连接?

rhy*_*yek 21 javascript firefox google-chrome server-sent-events

我正在开发一个利用Server-Sent-Events的项目,并且遇到了一些有趣的事情:Chrome和Firefox之间的连接丢失处理方式不同.

在Chrome 35或Opera 22上,如果您丢失了与服务器的连接,它将每隔几秒尝试无限期重新连接,直到成功为止.另一方面,在Firefox 30上,它只会尝试一次,然后您必须刷新页面或处理引发的错误事件并手动重新连接.

我更喜欢Chrome或Opera的方式,但是阅读http://www.w3.org/TR/2012/WD-eventsource-20120426/#processing-model,似乎一旦EventSource尝试重新连接并失败由于网络错误或其他原因,它不应重试连接.但不确定我是否正确理解规范.

我开始要求用户使用Firefox,主要是因为你不能在Chrome上打开同一个URL的事件流,但是这个新发现可能更多的是一个问题.虽然,如果Firefox的行为符合规范,那么我不妨以某种方式解决它.

编辑:

我现在要继续以Firefox为目标.这就是我处理重新连接的方式:

var es = null;
function initES() {
    if (es == null || es.readyState == 2) { // this is probably not necessary.
        es = new EventSource('/push');
        es.onerror = function(e) {
            if (es.readyState == 2) {
                setTimeout(initES, 5000);
            }
        };
        //all event listeners should go here.
    }
}
initES();
Run Code Online (Sandbox Code Playgroud)

red*_*zee 10

服务器端事件在所有浏览器中的工作方式不同,但它们在某些情况下都会关闭连接。例如,当服务器重新启动时,Chrome 会在出现 502 错误时关闭连接。因此,最好像其他人建议的那样使用 keep-alive 或在每个错误时重新连接。保持活动仅在指定的时间间隔内重新连接,该时间间隔必须保持足够长的时间以避免服务器过载。在每个错误时重新连接具有最低的可能延迟。但是,只有采用将服务器负载降至最低的方法才有可能。下面,我将演示一种以合理速率重新连接的方法。

此代码使用去抖动功能以及重新连接间隔加倍。它运行良好,以 1 秒、4 秒、8 秒、16 秒连接……最多 64 秒,并以相同的速率不断重试。我希望这可以帮助一些人。

function isFunction(functionToCheck) {
  return functionToCheck && {}.toString.call(functionToCheck) === '[object Function]';
}

function debounce(func, wait) {
    var timeout;
    var waitFunc;

    return function() {
        if (isFunction(wait)) {
            waitFunc = wait;
        }
        else {
            waitFunc = function() { return wait };
        }

        var context = this, args = arguments;
        var later = function() {
            timeout = null;
            func.apply(context, args);
        };
        clearTimeout(timeout);
        timeout = setTimeout(later, waitFunc());
    };
}

// reconnectFrequencySeconds doubles every retry
var reconnectFrequencySeconds = 1;
var evtSource;

var reconnectFunc = debounce(function() {
    setupEventSource();
    // Double every attempt to avoid overwhelming server
    reconnectFrequencySeconds *= 2;
    // Max out at ~1 minute as a compromise between user experience and server load
    if (reconnectFrequencySeconds >= 64) {
        reconnectFrequencySeconds = 64;
    }
}, function() { return reconnectFrequencySeconds * 1000 });

function setupEventSource() {
    evtSource = new EventSource(/* URL here */); 
    evtSource.onmessage = function(e) {
      // Handle even here
    };
    evtSource.onopen = function(e) {
      // Reset reconnect frequency upon successful connection
      reconnectFrequencySeconds = 1;
    };
    evtSource.onerror = function(e) {
      evtSource.close();
      reconnectFunc();
    };
}
setupEventSource();
Run Code Online (Sandbox Code Playgroud)


Tom*_*ger 8

我重写了@Wade 的解决方案,经过一些测试后,我得出的结论是,功能保持不变,代码更少,可读性更好(imo)。

我不明白的一件事是,如果每次尝试重新连接时timeout变量都设置回,为什么要清除超时null。所以我只是完全省略了它。我还省略了wait参数是否是函数的检查。我只是假设它是,所以它使代码更清晰。

var reconnectFrequencySeconds = 1;
var evtSource;

// Putting these functions in extra variables is just for the sake of readability
var waitFunc = function() { return reconnectFrequencySeconds * 1000 };
var tryToSetupFunc = function() {
    setupEventSource();
    reconnectFrequencySeconds *= 2;
    if (reconnectFrequencySeconds >= 64) {
        reconnectFrequencySeconds = 64;
    }
};

var reconnectFunc = function() { setTimeout(tryToSetupFunc, waitFunc()) };

function setupEventSource() {
    evtSource = new EventSource("url"); 
    evtSource.onmessage = function(e) {
      console.log(e);
    };
    evtSource.onopen = function(e) {
      reconnectFrequencySeconds = 1;
    };
    evtSource.onerror = function(e) {
      evtSource.close();
      reconnectFunc();
    };
}

setupEventSource();
Run Code Online (Sandbox Code Playgroud)


Dar*_*ook 7

我以与您相同的方式阅读标准,但是即使没有,也要考虑浏览器错误,网络错误,服务器死机但保持套接字打开状态等。因此,我通常在服务器顶部添加一个保持活动状态-SSE提供的连接。

在客户端,我通过几个全局变量和一个辅助函数来实现:

var keepaliveSecs = 20;
var keepaliveTimer = null;

function gotActivity(){
if(keepaliveTimer != null)clearTimeout(keepaliveTimer);
keepaliveTimer = setTimeout(connect,keepaliveSecs * 1000);
}
Run Code Online (Sandbox Code Playgroud)

然后,我gotActivity()在的顶部致电connect(),然后每次收到消息。(connect()基本上只是打给new EventSource()

在服务器端,它可以每隔15秒在正常数据流之上吐出一个时间戳(或某物),或者在正常数据流变得安静的情况下使用计时器本身并吐出一个时间戳(或某物)。 15秒


Mar*_*tin 7

我注意到(至少在 Chrome 中)是,当您使用close()函数关闭 SSE 连接时,它不会再次尝试重新连接。

var sse = new EventSource("...");
sse.onerror = function() {
    sse.close();
};
Run Code Online (Sandbox Code Playgroud)