14

我正在尝试掌握服务器端事件,因为它们完全符合我的要求并且看起来应该很容易实现,但是我无法克服一个模糊的错误,并且看起来连接反复被关闭并重新-打开。我所尝试的一切都是基于这个和其他教程。

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 脚本来推送到已经连接的浏览器吗?)

如果我在 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 看起来非常简单。此外,对我上面的两个编号问题的任何答案都会非常有帮助。

谢谢。

4

8 回答 8

21

问题是你的 php.ini 文件。

通过编写 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 秒(按照此处找到的示例:使用服务器发送的事件)。

这种类型的循环是我目前正在使用的,它消除了持续的连接断开并每 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 是您希望脚本运行的秒数。
于 2012-02-20T12:22:38.783 回答
2

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

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

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

一个问题是,一些 Java MVC 框架没有能力在数据发送后保持连接打开,因此您最终编码为裸 Servlet API。在用 Java 编写原型代码 24 小时后,我或多或少地感到失望,因为与传统的 jQuery-Ajax 循环相比的收益并不多。并且填充 SSE 功能的问题也存在。

于 2014-09-17T15:15:08.513 回答
1

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

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

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

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

我看不出这里有任何错误需要,因此我修改了您的 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"); } }
于 2012-02-09T21:20:46.363 回答
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 流。

于 2012-06-08T14:10:53.423 回答
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();
    }
}

PS : _e 是一个调用 document.getElementById(id); 的函数

于 2012-06-21T21:59:33.860 回答
0

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

发送响应后,标准的 http 响应应关闭连接。您可以使用标题“connection: keep-alive”来更改它,它应该告诉浏览器连接应该保持打开状态,尽管如果您使用代理,这可能会导致问题。

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

于 2012-10-24T11:04:46.053 回答
0

顾名思义,服务器发送事件表明数据应该从服务器传输到客户端,如果它必须每三秒重新连接一次以从服务器检索数据,那么它与其他轮询机制没有什么不同。SSE 的目的是尽快提醒客户端是客户端不知道的新数据。由于即使标头保持活动状态,服务器也会关闭连接,因此除了在无限循环中运行 php 脚本之外别无他法,但有相当多的线程睡眠以防止服务器负担。直到现在我没有看看任何其他出路,它比每 3 秒向服务器发送一次垃圾邮件来获取新数据要好。

于 2014-04-20T16:17:20.090 回答
0

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

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

读到我需要一个 Nginx 的“推送”模块,需要从源代码重新构建。

所以这绝对是 Nginx 的问题。

  1. 使用“C”中的套接字,我能够按预期将数据推送到 Firefox,并且套接字保持打开状态,并且没有丢失任何消息。但是,这样做的缺点是我需要为 page.html 提供服务,并且由于跨站点 URL 问题,来自同一套接字或 firefox 的事件/流将无法连接。在某些情况下有一些方法可以解决这个问题,但不适用于菜单系统中的 iframe。这种方法确实证明了 SSE 确实可以与 firefox 一起使用,并且在 wireshark 日志中有推送的数据包。其中选项 1 只有请求/回复数据包。

说了这么多,我还是没有解决办法。我试图删除 PHP 和 Nginx 上的缓冲。但在 PHP 完成之前仍然一无所获。尝试了不同的标题选项,例如块也没有帮助。我不想在'C'中编写一个完整的http服务器,但这似乎是目前对我有用的唯一选择。我即将尝试 Apache,但大多数文章表明这​​比 Nginx 在这项工作上更糟糕。

于 2015-11-27T01:12:02.757 回答