HTML5 服务器发送事件原型 - 不明确的错误和重复轮询?

发布于 2025-01-01 00:13:59 字数 1979 浏览 2 评论 0原文

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

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:我认为问题可能与我使用的服务器有关,因此我在普通 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 非常简单。另外,对我上面两个编号问题的任何答案都会非常有帮助。

谢谢。

I'm trying to get to grips with Server-Side Events as they fit my requirements perfectly and seem like they should be simple to implement, however I can't get past a vague error and what looks like the connection repeatedly being closed and re-opened. Everything I have tried is based on this and other tutorials.

The PHP is a single script:

<?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()));
?>

and the JavaScript looks like this (run on body load):

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");
    }
}

I have searched around a bit but can't find information on

  1. If Apache needs any special configuration to support server-sent events, and
  2. How I can initiate a push from the server with this kind of setup (e.g. can I simply execute the PHP script from CLI to give a push to the already-connected-browser?)

If I run this JS in Chrome (16.0.912.77) it opens the connection, receives the time, then errors (with no useful information in the error object), then reconnects in 3 seconds and goes through the same process. In Firefox (10.0) I get the same behaviour.

EDIT 1: I thought the issue could be related to the server I was using, so I tested on a vanilla XAMPP install and the same error comes up. Should a basic server configuration be able to handle this without modification / extra configuration?

EDIT 2: The following is an example of output from the browser:

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

Can anyone tell me where this is going wrong? The tutorials I have seen make it look like SSE is very straightforward. Also any answers to my two numbered questions above would be really helpful.

Thanks.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

帝王念 2025-01-08 00:14:00

问题是你的 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 是您希望脚本运行的秒数。

The problem is your php.

With the way your php script is written, only one message is sent per execution. That's how it works if you access the php file directly, and that's how it works if you access the file with an EventSource. So in order to make your php script send multiple messages, you need a loop.

<?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);
}
?>

I have altered your code to include an infinite loop that waits 1 second after every message sent (following an example found here: Using server-sent events).

This type of loop is what I'm currently using and it eliminated the constant connection drop and reconnect every 3 seconds. However (and I've only tested this in chrome), the connections are now only kept alive for 30 seconds. I will be continuing to figure out why this is the case and I'll post a solution when I find one, but until then this should at least get you closer to your goal.

Hope that helps,

Edit:

In order to keep the connection open for ridiculously long times with php, you need to set the max_execution_time (Thanks to tomfumb for this). This can be accomplished in at least three ways:

  1. If you can alter your php.ini, change the value for "max_execution_time." This will allow all of your scripts to run for the time you specify though.
  2. In the script you wish to run for a long time, use the function ini_set(key, value), where key is 'max_execution_time' and value is the time in seconds you wish your script to run for.
  3. In the script you wish to run for a long time, use the function set_time_limit(n) where n is the number of seconds that you wish your script to run.
合久必婚 2025-01-08 00:14:00

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

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

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

一个问题是,某些 Java MVC 框架无法在数据发送后保持连接打开,因此您最终只能编写裸露的 Servlet API。在用 Java 编写原型 24 小时后,我或多或少感到失望,因为相对于传统的 jQuery-Ajax-loop 的收益并没有那么大。并且对SSE特性进行polyfill的问题也同样存在。

Server Sent Events are easy only when it comes to the Javascript part. First of all a lot of tutorials on SSE in the internet are closing their connections in the server part. Be it PHP or Java examples. This is really astonishing because what you get then is just a different way of implementing a "Ajax Polling" system with a strictly defined payload structure (and some minor features like client retry values set by server side). You can easily implement that with a few lines of jQuery. No need for SSE then.

According to the spec of SSE, i would say that the retry shouldnt be the normal way of implementing a client side loop. For me SSE is a one way streaming method which relies on a server backend which does not close the connection after pushing the first data to the client.

In Java its useful to use Servlet3 Async spec in order to free the request thread immediately and do the processing / streaming in a different thread. This works so far but still i dont like the 30 seconds connection lifetime for the EventSource request. Even i am pushing data every 5 seconds, the connection will be terminated after 30 seconds (chrome, firefox). Of course SSE will reconnect per default after 3 seconds but still i dont think this is the way it should be.

One problem is that some Java MVC frameworks dont have the ability to keep the connection open after data sending, so that you end up coding to the bare Servlet API. After on 24hours on coding prototypes in Java, i am more or less dissapointed because the gain over a traditional jQuery-Ajax-loop is not THAT much. And the problem with polyfilling the SSE feature is also existant.

红颜悴 2025-01-08 00:14:00

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

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"); } }

The problem is not a server side issue, this all happens on the client and is part of the spec (I know it sounds weird).

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

"When a user agent is to reestablish the connection, the user agent must run the following steps. These steps are run asynchronously, not as part of a task. (The tasks that it queues, of course, are run like normal tasks and not asynchronously.)"

  1. Queue a task to run the following steps:

    1. If the readyState attribute is set to CLOSED, abort the task.
    2. Set the readyState attribute to CONNECTING.
    3. Fire a simple event named error at the EventSource object.

I can't see any need to have an error here, so I have modified your Init function to filter out the error event fired whilst connecting.

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"); } }
溇涏 2025-01-08 00:14:00

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

这总结了问题(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 流。

There is no actual issue with the code, that I can see. The answer selected as correct, is then, incorrect.

This sums up the behavior mentioned in the question (http://www.w3.org/TR/2009/WD-html5-20090212/comms.html):

"If such a resource (with the correct MIME type) completes loading (i.e. the entire HTTP response body is received or the connection itself closes), the user agent should request the event source resource again after a delay equal to the reconnection time of the event source. This doesn't apply for the error cases that are listed below."

The problem lies with the stream. I've successfully kept a single EventStream open before in perl; just send the appropriate HTTP headers, and start sending stream data; never shutdown the stream server side. The issue is that it seems most HTTP libraries attempt to close the stream after its been opened. This will cause the client to attempt to reconnect to the server, which is fully standard compliant.

This means that it will appear that the problem is solved by running a while loop, for a couple of reasons:

A) The code will continue to send data, as if it were pushing out a large file
B) The code (php server) will never have the chance to attempt to close the connection

However, the problem here is obvious: to keep the stream alive, a constant stream of data must be sent. This results in wasteful utilization of resources, and negates any benefits the SSE stream is supposed to provide.

I'm not enough of a php guru to know, but I'd imagine that something in the php server/later in the code is prematurely closing the stream; I had to manipulate the stream at Socket level with Perl to keep it open, since HTTP::Response was closing the connection, and causing the client browser to attempt to re-open the connection. In Mojolicious (another Perl web framework), this can be done by opening a Stream object and setting the timeout to zero, so that the stream never times out.

So, the proper solution here is not to use a while loop; it is to call the appropriate php functions for opening, and keeping open, a php stream.

兮颜 2025-01-08 00:14:00

我能够通过实现自定义事件循环来做到这一点。看来这个 html5 功能根本没有准备好,即使是最新版本的 google chrome 也存在兼容性问题。在这里,在 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); 的函数;

I was able to do it by implementing a custom event loop. It seems that this html5 feature is not ready at all and has compatibility issues even with the latest version of google chrome. Here it is, working on firefox (can't get the message sent correctly on 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 is a function that calls document.getElementById(id);

野却迷人 2025-01-08 00:14:00

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

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

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

According to the Spec, the 3 second reconnection is by design when the connection is closed. PHP with a loop should theoretically stop this but the PHP script will be running indefinitely and wasting resources. You should try to avoid using apache and php for SSE because of this issue.

The standard http response should close a connection once the response is sent. You can change this with the header "connection: keep-alive" which should tell the browser that the connection is meant to stay open although this can cause problems if you're using proxies.

node.js or something similar is a better engine to use for SSE rather than apache/php and since it's basically JavaScript, its pretty easy to get to grips with.

南渊 2025-01-08 00:14:00

服务器发送事件顾名思义,如果数据必须每三秒重新连接一次以从服务器检索数据,那么数据应该从服务器传输到客户端,那么它与其他轮询机制没有什么不同。SSE 的目的是一旦出现这种情况就向客户端发出警报是客户端不知道的新数据。由于服务器关闭连接,即使标头保持活动状态,除了在无限循环中运行 php 脚本之外别无选择,但要进行大量线程睡眠以防止服务器负担。到目前为止,我不这样做看到任何其他出路及其比每 3 秒向服务器发送一次新数据的垃圾邮件要好。

Server Sent Event as name suggests the data should be traveling from server to client if it has to reconnect every three seconds to retrieve data from server then it is no different than other polling mechanisms.The purpose of SSE is to alert client as soon as there is new data which client is unaware of.Since server closes connection even if header is keep-alive there is no other way than to run php script in infinite loop but with considerable thread sleep to prevent burden on server.Till now i don't see any other way out and its better than spamming server every 3 seconds for new data.

萌无敌 2025-01-08 00:14:00

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

  1. Firefox 也有同样的问题,运行与上述相同的 js 代码。
    使用Nginx服务器和一些退出的PHP(即没有连续循环),只有在PHP退出后,我才能从firefox获取返回“请求”的消息。
    在 PHP.exe 中将 PHP 作为脚本运行,控制台上一切正常,刷新时会打印字符串。但是,Nginx 在 PHP 完成之前不会发送数据。尝试添加额外的\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 更糟糕。

I'm trying the same thing. With varying degrees of success.

  1. Had the same problem with Firefox, running the same js code as mentioned.
    Using the Nginx server and some PHP that exited(ie no continual loop), I could get messages back to a "Request" from firefox only once the PHP exited.
    Run the PHP as a script in PHP.exe and all is good on the concole, stings are printed when flushed. However, Nginx doesn't send the data until the PHP has completed. Tried adding extra \r\n\r\n and flush() or ob_flush() did not help.
    There is no pushing of data, as shown in Wireshark logs, just a delayed response packet to the GET.

Read that I need a "push" module for Nginx that requires a re-build from source.

So this is definitely an Nginx problem.

  1. Using a socket in 'C' I was able to push data to Firefox as expected, and the socket was kept open, and no messages were missed. However this has the disadvantage that I need to server the page.html and the events/stream from the same socket or firefox will not connect due to Cross Site Url problems. There are some ways around this in certain situations, but not for a iframe in a menu system. This approach did prove the point that the SSE does work with firefox and there are pushed packets in the wireshark log. Where option 1 only had request/reply packets.

All this said, I still don't have a solution. I've tried to remove the buffering on the PHP and Nginx. But still nothing until PHP finishes. Tried different header options, eg chunks didn't help either.
I don't feel like writing a full blown http server in 'C' but this seems to be the only option that is working for me at the moment.
I'm about to try Apache, but most write ups suggest that this is worse than Nginx at this job.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文