服务器发送事件可以工作,但存在巨大的时间延迟。

10

首先我要说,这在我的本地机器上完美运行。下面的js示例连接到stream.php,并每秒接收一次服务器当前时间的连续更新。

index.php

var source = new EventSource("stream.php");

source.addEventListener('message', function(e) {
    console.log(e);
}, false);

source.addEventListener('open', function(e) {
    console.log(e);
}, false);

source.addEventListener('error', function(e) {
    if (e.readyState == EventSource.CLOSED) {
        console.log('closed');
    }
}, false);

stream.php

while(true)
{
    // Headers must be processed line by line.
    header('Content-Type: text/event-stream');
    header('Cache-Control: no-cache');

    // Set data line
    print "data: " . date( 'G:H:s', time() ) . PHP_EOL . PHP_EOL;

    // Toilet
    flush();

    // Wait one second.
    sleep(1);
}

我上传到实时开发服务器后,预计会有一些延迟。但在我看到第一条记录之前,需要等待约15至20分钟的时间。

连接没有中断。(可能已经进行了40多分钟)。这只是 Apache 循环问题(意味着是时候查看 WebSockets 了),还是我可以采取某些措施来解决这个问题?


我不确定这是否是有效的方法?server.php只是在运行,而且虽然我相信睡眠时间不计入总运行时间(如果它被计算为“系统调用”,那么您的脚本允许运行的时间可能有限制(默认为30秒)。因此,虽然大部分时间都花在了sleep上,但仍存在限制:http://www.php.net/manual/en/info.configuration.php#ini.max-execution-time - Nanne
我知道时间限制,这不会是问题。更重要的是要把第一步做对。 - CodeChap
看起来你似乎在使用不适合这项工作的工具...建议查看:http://nodejs.org/ - George Reith
1
只是一个提醒:我发现很多例子中,那些人只是重新连接到他们的server.php或stream.php文件。这是错误的。连接应该保持打开状态,以便在数据发生时进行流式传输。如果您不断重新连接,则正在进行轮询,这可以通过ajax完成,您将失去服务器发送事件的目的。 - CodeChap
@Derrick 当你的服务器端语言被设计为持续运行时,连接和重新连接是不好的,但不幸的是PHP并不是这样的。请参考此链接:http://software-gunslinger.tumblr.com/post/47131406821/php-is-meant-to-die... 一个更好的方法可能是保持连接10-30秒钟,然后关闭连接让客户端重新连接。 - Don McCurdy
5个回答

11

Server.php 需要如下所示:

stream.php

while(true)
{
    // Headers must be processed line by line.
    header('Content-Type: text/event-stream');
    header('Cache-Control: no-cache');

    // Set data line
    print "Event: server-time" . PHP_EOL;
    print "data: " . date( 'G:H:s', time() ) . PHP_EOL;
    print PHP_EOL;

    ob_end_flush();     // Strange behaviour, will not work
    flush();            // Unless both are called !

    // Wait one second.
    sleep(1);
}

2
谢谢。虽然 ob_flush()flush() 的组合对我没用,但加上 ob_end_flush() 就可以了。 - JohnP
奇怪的是,我看过的所有教程都使用 ob_flush,但这里使用 ob_end_flush 的答案是最好的。 - Michael
不幸的是,这也取决于客户端。在我的情况下,这个hack没有起作用(即使使用PHP的输出缓冲区和在.ini中禁用zlib输出)。唯一有效的方法是传输足够多的填充垃圾以最终让浏览器写入页面。在Chrome中,这似乎发生在4K时,而FF则等待更长时间(8K?)才会出现第一条消息。 - Sz.
可能有帮助:php flush not working - Avatar
刷新在一开始对我没有起作用,因为你需要一定量的数据才能让PHP真正地刷新。请参见此处:https://dev59.com/Qm445IYBdhLWcg3wws8u#4978642 “这是为了使缓冲区达到最小大小以便刷新数据”。 - Avatar
显示剩余2条评论

2
睡眠阻塞了SSE。我也遇到过同样的问题。有人建议我使用事件驱动编程。

2

@Derrick,你建议的ob_end_flush();代码行使我接近成功,但在比“hello world”代码更复杂的PHP中,我的SSE连接仍然会出现不必要的重新打开(我仍然不完全理解为什么ob_end_flush()会对我产生这样的影响)。因此,这是我现在正在使用的模式(与你的stream.php完全相同)。简而言之,我在进入我的无限循环之前关闭了PHP输出缓冲:

// per http://www.php.net/manual/en/book.outcontrol.php:
//     Clean (erase) the output buffer and turn off output buffering
ob_end_clean();

// how long PHP script stays running/SSE connection stays open (seconds)
set_time_limit(60);
while (true) {

  // use @Derrick's header/send code here

  ob_flush();  // note I don't turn off output buffering here again
  flush();
  sleep(1);
}

你确定代码中没有其他地方输出数据吗?任何奇怪的输出都会导致不断重新连接。但如果这个方法可行,那就太棒了。 - CodeChap

0
您可以通过`echo retry`来设置延迟,例如:

header('Content-Type: text/event-stream');
header('Cache-Control: no-cache'); //to prevent caching of event data
header('Access-Control-Allow-Origin: *'); //allows stream access from current domain only

$time = date('h:i:s');
$id = time(); //to set id with current timestamp

echo "data:$time\n";
echo "retry:2000\n";  //2 seconds
echo "id=$id \n\n";

ob_flush();
flush();

0

在stream.php的flush()函数之前添加ob_flush();即可。

更新后的stream.php脚本如下,注意在flush()函数之前添加了ob_flush()函数。

while(true)
{
    // Headers must be processed line by line.
    header('Content-Type: text/event-stream');
    header('Cache-Control: no-cache');

    // Set data line
    print "data: " . date( 'G:H:s', time() ) . PHP_EOL . PHP_EOL;

    // Toilet
    **ob_flush();**
    flush();

    // Wait one second.
    sleep(1);
}

我认为如果您能够扩展相关的代码片段并添加您自己的代码,那么对于原帖作者和后续访问者来说会更有帮助。 - Reporter

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