HTML5 Server-Sent Events原型设计 - 模糊错误和重复轮询?

时间:2012-01-30 21:26:36

标签: apache html5 server-push server-sent-events

我正在努力掌握服务器端事件,因为它们完全符合我的要求,看起来它们应该很容易实现,但是我无法通过一个模糊的错误,看起来连接的重复是什么关闭并重新开放。我尝试过的所有内容都基于this和其他教程。

PHP是一个单一的脚本:

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

function sendMsg($id, $msg) {
  echo "id: $id" . PHP_EOL;
  echo "data: $msg" . PHP_EOL;
  echo PHP_EOL;
  ob_flush();
  flush();
}

$serverTime = time();
sendMsg($serverTime, 'server time: ' . date("h:i:s", time()));
?>

,JavaScript看起来像这样(在正文上运行):

function init() {

    var source;
    if (!!window.EventSource) {
        source = new EventSource('events.php');
        source.addEventListener('message', function(e) {
            document.getElementById('output').innerHTML += e.data + '<br />';
        }, false);
        source.addEventListener('open', function(e) {
            document.getElementById('output').innerHTML += 'connection opened<br />';
        }, false);
        source.addEventListener('error', function(e) {
            document.getElementById('output').innerHTML += 'error<br />';
        }, false);
    }
    else {
        alert("Browser doesn't support Server-Sent Events");
    }
}

我搜索了一下但找不到关于

的信息
  1. 如果Apache需要任何特殊配置来支持服务器发送的事件,
  2. 如何使用这种设置从服务器启动推送(例如,我可以简单地从CLI执行PHP脚本以推送已连接的浏览器吗?)
  3. 如果我在Chrome(16.0.912.77)中运行此JS,则会打开连接,接收时间,然后出现错误(错误对象中没有有用信息),然后在3秒内重新连接并完成相同的过程。在Firefox(10.0)中,我得到了相同的行为。

    编辑1 :我认为这个问题可能与我使用的服务器有关,所以我在一个vanilla XAMPP安装上测试了同样的错误。基本服务器配置是否能够在没有修改/额外配置的情况下处理此问题?

    编辑2 :以下是浏览器输出的示例:

    connection opened
    server time: 01:47:20
    error
    connection opened
    server time: 01:47:23
    error
    connection opened
    server time: 01:47:26
    error
    

    谁能告诉我哪里出错了?我看到的教程使它看起来像SSE非常简单。此外,我上面两个编号问题的任何答案都会非常有用。

    感谢。

8 个答案:

答案 0 :(得分:21)

问题是你的PHP。

使用php脚本的编写方式,每次执行只发送一条消息。如果你直接访问php文件,那就是它的工作方式,如果你使用EventSource访问该文件,那就是它的工作原理。所以为了让你的php脚本发送多条消息,你需要一个循环。

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

function sendMsg($id, $msg) {
  echo "id: $id" . PHP_EOL;
  echo "data: $msg" . PHP_EOL;
  echo PHP_EOL;
  ob_flush();
  flush();
}
while(true) {
  $serverTime = time();
  sendMsg($serverTime, 'server time: ' . date("h:i:s", time()));
  sleep(1);
}
?>

我已将您的代码更改为包含一个无限循环,该循环在发送每条消息后等待1秒(遵循此处的示例:Using server-sent events)。

这种类型的循环是我目前正在使用的,它消除了持续的连接丢失并每隔3秒重新连接一次。然而(我只在chrome中测试过这个),现在连接只保持活动30秒。我将继续弄清楚为什么会出现这种情况,我会在找到解决方案时发布解决方案,但在此之前,这至少应该让您更接近目标。

希望有所帮助,

编辑:

为了使用php保持连接长时间打开,你需要设置max_execution_time(感谢tomfumb)。这可以通过至少三种方式实现:

  1. 如果您可以更改php.ini,请更改“max_execution_time”的值。这将允许所有脚本在您指定的时间内运行。
  2. 在您希望运行很长时间的脚本中,使用函数ini_set(key,value),其中key是'max_execution_time',value是您希望脚本运行的时间(以秒为单位)。
  3. 在您希望长时间运行的脚本中,使用函数set_time_limit(n),其中n是您希望脚本运行的秒数。

答案 1 :(得分:2)

服务器发送事件只有在Javascript部分才容易。首先,互联网上关于SSE的许多教程正在关闭他们在服务器部分的连接。无论是PHP还是Java示例。这真是令人惊讶,因为你得到的只是实现一个&#34; Ajax Polling&#34;系统具有严格定义的有效负载结构(以及一些次要功能,如服务器端设置的客户端重试值)。您可以使用几行jQuery轻松实现它。那时不需要SSE。

根据SSE的规范,我会说重试不应该是实现客户端循环的正常方式。对我来说,SSE是一种单向流方法,它依赖于服务器后端,该服务器后端在将第一个数据推送到客户端后不会关闭连接。

在Java中,使用Servlet3 Async规范以便立即释放请求线程并在不同的线程中进行处理/流传输是很有用的。这工作到目前为止,但我仍然不喜欢EventSource请求的30秒连接生命周期。即使我每隔5秒推送一次数据,连接也会在30秒后终止(chrome,firefox)。当然SSE将在3秒后重新连接,但我仍然不认为这是应该的方式。

一个问题是,某些Java MVC框架无法在数据发送后保持连接处于打开状态,因此最终编码为裸Servlet API。在使用Java编写原型的24小时后,我或多或少感到失望,因为传统的jQuery-Ajax循环的收益并不多。而填充SSE功能的问题也是存在的。

答案 2 :(得分:1)

问题不是服务器方面的问题,这一切都发生在客户端上并且是规范的一部分(我知道这听起来很奇怪)。

http://dev.w3.org/html5/eventsource/

“当用户代理要重新建立连接时,用户代理必须执行以下步骤。这些步骤是异步运行的,而不是作为任务的一部分。(当然,它排队的任务像正常一样运行任务而不是异步。)“

      
  1.     将任务排队以执行以下步骤:     
            
    1. 如果readyState属性设置为CLOSED,则中止任务。
    2.       
    3. 将readyState属性设置为CONNECTING。
    4.       
    5. 在EventSource对象上触发一个名为error的简单事件。
    6.        

      我在这里看不到任何错误,所以我修改了你的Init函数来过滤掉连接时触发的错误事件。

      function init() {
                      var CONNECTING = 0;
                      var source;
                      if (!!window.EventSource) {
                          source = new EventSource('events.php');
                          source.addEventListener('message', function (e) {
                              document.getElementById('output').innerHTML += e.data + '
      '; }, false); source.addEventListener('open', function (e) { document.getElementById('output').innerHTML += 'connection opened
      '; }, false); source.addEventListener('error', function (e) { if (source.readyState != CONNECTING) { document.getElementById('output').innerHTML += 'error
      '; } }, false); } else { alert("Browser doesn't support Server-Sent Events"); } }

答案 3 :(得分:0)

我可以看到代码没有实际问题。选择正确的答案是不正确的。

这总结了问题中提到的行为(http://www.w3.org/TR/2009/WD-html5-20090212/comms.html):

“如果此类资源(具有正确的MIME类型)完成加载(即接收到整个HTTP响应正文或连接本身关闭),则用户代理应在延迟等于重新连接后再次请求事件源资源事件源的时间。这不适用于下面列出的错误情况。“

问题在于流。我在perl之前成功地保留了一个EventStream;只需发送相应的HTTP头,然后开始发送流数据;永远不要关闭流服务器端。问题是,似乎大多数HTTP库在打开后尝试关闭流。这将导致客户端尝试重新连接到完全符合标准的服务器。

这意味着看起来问题是通过运行while循环来解决的,原因有两个:

A)代码将继续发送数据,就好像它正在推出一个大文件一样 B)代码(php服务器)永远不会有机会尝试关闭连接

然而,这里的问题显而易见:为了保持流存活,必须发送恒定的数据流。这导致浪费资源利用,并否定了SSE流应该提供的任何好处。

我还不足以知道一个php专家,但我想象一下php服务器/稍后代码中的某些内容过早地关闭了流;我不得不使用Perl在Socket级别操作流以使其保持打开状态,因为HTTP :: Response正在关闭连接,并导致客户端浏览器尝试重新打开连接。在Mojolicious(另一个Perl Web框架)中,可以通过打开Stream对象并将超时设置为零来完成,这样流就不会超时。

所以,这里适当的解决方案是不使用while循环;它是调用适当的php函数来打开,并保持打开,一个PHP流。

答案 4 :(得分:0)

我能够通过实现自定义事件循环来实现。似乎这个html5功能还没有准备好,即使使用最新版本的谷歌浏览器也存在兼容性问题。在这里,使用firefox(无法在chrome上正确发送消息):

var source;

function Body_Load(event) {
    loopEvent();
}

function loopEvent() {
    if (source == undefined) {
        source = new EventSource("event/message.php");
    }
    source.onmessage = function(event) {
        _e("out").value = event.data;
        loopEvent();
    }
}

P.S。 :_e是一个调用document.getElementById(id);

的函数

答案 5 :(得分:0)

根据规范,当连接关闭时,3秒重新连接是设计的。带有循环的PHP理论上应该停止这个,但PHP脚本将无限期地运行并浪费资源。由于此问题,您应该尽量避免使用apache和php进行SSE。

标准http响应应在发送响应后关闭连接。您可以使用标题&#34; connection:keep-alive&#34;更改此设置。这应该告诉浏览器该连接是否保持打开,尽管如果您使用代理服务器会导致问题。

node.js或类似的东西是用于SSE而不是apache / php的更好的引擎,因为它基本上是JavaScript,它很容易掌握。

答案 6 :(得分:0)

服务器发送事件名称表明数据应该从服务器传播到客户端如果必须每三秒重新连接一次以从服务器检索数据,那么它与其他轮询机制没有什么不同.SSE的目的是提醒客户端一旦有新的数据,客户端不知道。即使标头保持活动,服务器关闭连接除了在无限循环中运行PHP脚本没有别的办法,但有相当多的线程睡眠,以防止服务器的负担。现在我没有看到任何其他方法,它比每3秒发送一次垃圾邮件更好的新数据。

答案 7 :(得分:0)

我正在尝试同样的事情。取得了不同程度的成功。

  1. 与Firefox有同样的问题,运行与上述相同的js代码。 使用Nginx服务器和一些退出的PHP(即没有连续循环),只有在PHP退出后,我才能从firefox中将消息返回到“请求”。 在PHP.exe中将PHP作为脚本运行,并且在concole上一切都很好,刷新时会打印出stings。但是,在PHP完成之前,Nginx不会发送数据。尝试添加额外的\ r \ n \ r \ n和flush()或ob_flush()没有帮助。 没有推送数据,如Wireshark日志中所示,只是延迟响应数据包到GET。
  2. 请注意,我需要一个Nginx的“推送”模块,需要从源代码重新构建。

    所以这绝对是一个Nginx问题。

    1. 在'C'中使用套接字我能够按预期将数据推送到Firefox,并且套接字保持打开状态,并且没有错过任何消息。然而,这有一个缺点,我需要服务于page.html,并且由于跨站点Url问题,来自同一套接字或firefox的事件/流将无法连接。在某些情况下,有一些方法,但不适用于菜单系统中的iframe。这种方法确实证明了SSE确实可以与firefox一起使用,并且在wireshark日志中有推送的数据包。选项1只有请求/回复数据包。
    2. 所有这些说,我仍然没有解决方案。我试图删除PHP和Nginx上的缓冲。但是在PHP完成之前仍然没有。尝试了不同的标题选项,例如块也没有帮助。 我不想在'C'中编写一个完整的http服务器,但这似乎是目前唯一适用于我的选项。 我即将尝试Apache,但大多数写作表明这比Nginx在这项工作上更糟糕。