服务器推送事件:如何以跨浏览器方式自动重新连接?

16

我编写了一些代码来查询数据库中的更改并发送事件。 这是我的PHP脚本代码

header("Content-Type: text/event-stream");
header('Cache-Control: no-cache');

//****Some code here to query the database

echo "event: message\n";
echo "data: change_from_database \n";
echo "\n\n";
ob_flush();
flush();

我依赖浏览器自动重新连接每次连接关闭,所以我没有在服务器代码中实现任何循环。此外,我从这个线程了解到,实现一个无限循环有很多缺点。

因此,客户端一切都正常运作:每当连接关闭时,浏览器会重新连接并在服务器发送事件时触发一个事件;但是除了Firefox(40.0.2),它不会重新连接。我知道它不会重新连接,因为我编写了一些JavaScript错误检查代码来测试它:

var evtSource = new EventSource("../sse.php");
evtSource.onerror = function(event){
    var txt;
    switch( event.target.readyState ){
    case EventSource.CONNECTING:
        txt = 'Reconnecting...';
        break;
    }
    console.log(txt);
}

比如,在每秒钟的时间间隔里,Chrome浏览器的控制台会打印出"Reconnecting"。而火狐浏览器只会重新连接一次并且不会再次尝试。

我该如何编写代码来使其在火狐浏览器上正常工作,并且对于其他支持服务器发送事件但不会自动重新连接的浏览器也能起作用呢?


2
https://dev59.com/6mgu5IYBdhLWcg3wy518 - Matt
谢谢,@mkaatman,但是那个帖子解释了SSE之类的技术是做什么的。我了解每种技术所做的一点事情,这就是为什么我在其中一个方面遇到问题的原因。 - Cedric Ipkiss
你有没有可能切换到Web套接字? - Matt
我了解到使用 Node.js,WebSockets更容易且更佳。问题是,由于我的 Web 项目都没有在专用服务器上运行,因此我在使用 Node.js 时遇到了困难。但如果这是一项好的技术,我愿意并准备全身心地投入其中。 - Cedric Ipkiss
你能用header('Transfer-Encoding: identity');测试一下你的代码吗?我不知道它是否有帮助,只是建议。 - Wizard
开发者控制台中是否有错误消息? - max
3个回答

6

最近版本的Firefox浏览器(44.0.2)能够完美地运行您的代码。然而,您可以在错误处理程序中重新初始化EventSource对象,如下所示:

var evtSource = new EventSource("../sse.php");
var evtSourceErrorHandler = function(event){
    var txt;
    switch( event.target.readyState ){
        case EventSource.CONNECTING:
            txt = 'Reconnecting...';
            break;
        case EventSource.CLOSED:
            txt = 'Reinitializing...';
            evtSource = new EventSource("../sse.php");
            evtSource.onerror = evtSourceErrorHandler;
            break;
    }
    console.log(txt);
}

但我强烈不建议您这样做,因为您的代码没有利用保持连接的好处(正如您所写的,您知道无限循环),所以浏览器会进行简单的轮询(您可以在网络选项卡中看到)。我认为,在没有保持永久连接的情况下,使用SSE而不是AJAX没有任何理由,这显然很难在PHP中维护。因此,我建议在这种情况下使用简单的AJAX轮询。


1
很遗憾,您关于避免传输数据开销的假设是错误的,因为使用SSE重新连接服务器会发送与简单的AJAX调用一样多的头信息。您可以在浏览器的“网络”选项卡中自行检查。 - max
1
@che-azeh 这是正确的,直到脚本重新连接每个请求并使用“单个持久的HTTP连接”。每次脚本重新连接时,它都会发送头部作为AJAX轮询,并且您可以使用浏览器的开发者工具轻松验证,就像我所做的一样。 - max
1
感谢澄清,@max。完全明白!:)我授予奖励主要是因为你的答案是唯一一个除了解决我的问题外,还帮助我提高了SSE知识的答案。 - Cedric Ipkiss
但我强烈不建议您这样做,因为您的代码没有利用保持连接活动的好处。我不明白。看起来您的代码将通过“忽略”第一个Firefox重试连接,然后在第二次失败时重新建立新的EventSource而正常工作。这有什么不利之处吗?这看起来会像Chrome本地一样运行。您能帮我理解这个问题吗? - Josh Johnson
实际上,在提到的Firefox版本(以及其他最近的版本)上,它不会在第二次失败时重新建立连接。这就是它与Chrome不同的地方;您不能依赖它自动重新连接。此外,正如我的问题所述,我们无法通过“睡眠”服务器来保持从PHP打开的连接。 - Cedric Ipkiss
显示剩余2条评论

3

好的,我找到了。让我下载并测试我的代码在使用SSE时的所有情况。 - Cedric Ipkiss

1
在我的网站上使用您的代码进行测试,在Firefox 44.0.2和php 5.5下一切按预期工作。然而,我在Mozilla开发者网络上遇到了一些有趣的事情,它指出你不能确切地知道Firefox v22之后的错误信息。也许你的错误检查中的switch语句是导致问题的原因。这里article是一个链接,请查看错误处理部分。
我的php代码与你的完全相同。以防万一我做了一些不同的事情,这是我的html代码。
<!DOCTYPE>
<html>
<head>
    <title>SSE Test</title>
    <meta charset="utf-8" />
    <script>
      var evtSource = new EventSource("sse.php");
      evtSource.onerror = function(event){
        var txt;
        switch( event.target.readyState ){
           case EventSource.CONNECTING:
               txt = 'Reconnecting...';
               break;
        }
        console.log(txt);
      };
    </script>
  </head>
  <body></body>
</html>

谢谢,@OffTheBricks;不知道我是否遗漏了什么,但我的Firefox已经更新,当我运行代码时,它重新连接一次,之后再也没有重新连接过。问题在于它必须始终重新连接,就像Chrome一样。因此,每次重新连接时,它都会检查是否有可用的更新。 - Cedric Ipkiss

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接