【问题标题】:SSE incredibly slow上证所慢得令人难以置信
【发布时间】:2016-12-05 05:05:13
【问题描述】:

我目前正在写一个网页游戏的通讯框架,通讯图如下:代码如下:

test.php:

<!DOCTYPE html>
<html>
    <head>
        <title> Test </title>
        <script>
            function init()
            {
                var source = new EventSource("massrelay.php");
                source.onmessage = function(event)
                {
                    console.log("massrelay sent: " + event.data);
                    var p = document.createElement("p");
                    var t = document.createTextNode(event.data);
                    p.appendChild(t);
                    document.getElementById("rec").appendChild(p);
                };
            }

            function test()
            {
                var xhr = new XMLHttpRequest();
                xhr.onreadystatechange = function () 
                {
                    if(xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) 
                    {
                        console.log("reciver responded: " + xhr.responseText);
                    }
                }
                xhr.open("GET", "reciver.php?d=" + document.getElementById("inp").value , true);
                xhr.send();
                console.log("you sent: " + document.getElementById("inp").value);
            }
        </script>
    </head>
    <body>
        <button onclick="init()">Start Test</button> 
        <textarea id="inp"></textarea>
        <button onclick="test()">click me</button>
        <div id="rec"></div>
    </body>
</html>

这需要用户输入(目前是一个用于测试的文本框)并将其发送到接收器,然后将接收器的响应写回控制台,我从未收到来自接收器的错误。它还为发送的 SSE 添加了一个事件侦听器。

reciver.php:

<?php 
    $data = $_REQUEST["d"];
    (file_put_contents("data.txt", $data)) ? echo $data : echo "error writing";
?>

如您所见,这非常简单,只有在返回写入成功之前将数据写入 data.txt 的功能。 data.txt 只是传递给 massrelay.php 的“管”数据。

massrelay.php:

<?php
    header('Content-Type: text/event-stream');
    header('Cache-Control: no-cache');
    while(1)
    {
        $data = file_get_contents("data.txt");
        if ($data != "NULL")
        {
            echo "data: " . $data . "\n\n";
            flush();
            file_put_contents("data.txt", "NULL");
        }
    }
?>

massrelay.php 检查 data.txt 中是否有任何数据,如果有,将使用 SSE 将其传递给具有事件监听器的任何人,一旦读取数据,它将清除数据文件。

除了 massrelay.php 从数据文件发送数据可能需要 30 秒到 10 分钟的时间之外,整个事情实际上运行良好。对于网页游戏,这是完全不能接受的,因为您需要实时操作。我想知道是否由于我的代码中的缺陷而花费了这么长时间,或者我不是在考虑硬件(我自己将它托管在带有 sempron 的 2006 戴尔上)。如果有人发现它有任何问题,请告诉我。谢谢。

【问题讨论】:

  • 我建议使用某种带时间戳的日志函数来查看哪种方法花费了这么多时间。
  • @Polyov 听起来不错,我该怎么做?
  • 如果将usleep(1000) 放入massrelay.php 会发生什么?我只能猜测 while(1) { file_get_contents(); } 正在给你的机器施加压力
  • 如果你像while(1) { echo "test"; flush(); sleep(3); }这样做一个测试massreceiver会发生什么

标签: javascript php html server-sent-events eventsource


【解决方案1】:

我不知道写入平面文件是最好的方法。文件 I/O 将成为您的最大瓶颈(在写入之上阅读意味着您将很快达到该最大值)。但是假设你想继续这样做......

您的应用程序可以从 PHP 会话中受益,以存储一些数据,这样您就无需等待 I/O。这就是像MemcachedRedis 这样的中间软件也可以帮助你的地方。您要做的是将来自reciver.php 的数据存储在您的文本文件中并将其写入内存缓存(或将其放入写入内存存储的会话中)。这使得检索非常快速并减少了文件 I/O。

我强烈建议为您的数据建立一个数据库。 MySQL 尤其会将经常访问的数据加载到内存中以加快读取操作。

【讨论】:

  • 这应该是现在的评论回答。
  • @Peter 对于一个简单的评论来说有点过于宽泛了。如果不构建完整的工作模型,就无法构建代码示例
  • 他想使用平面文件和 SSE,所以让我们帮助他解决这个问题,而不是让他使用 memcached reddit 和数据库。我认为他的问题在于输出缓冲而不是平面文件
  • 其实我接下来打算尝试做一个数据库,这个问题是在游戏过程中你可以有大量数据进来,每个人都想同时移动,这个想法文本文件是在被massrelay读取之前可以写入许多命令,这将传递许多命令由用户打包以进行显示处理。从理论上讲,您可以使用 1 x 1 MYSQL 表执行此操作,但我认为使用文件 I/O 会更快。
  • @Peter 我实际上已经自己完成了平面文件的工作,虽然它在小规模上工作,但如果你尝试扩大规模,它会死得很惨。因此,为什么内存缓存或数据库更有意义。
【解决方案2】:

编辑:我删除了这个答案,因为 OP 说我建议的测试 1.(见下文)工作正常,所以我关于输出缓冲的理论是错误的。但另一方面,他说与本机函数相同的代码freadfwritefcloseflock 不起作用,所以如果缓冲和文件 I/O 不是解决方案,我不知道它是什么。我删除了我的帖子,因为我认为这不是一个有效的答案。让我总结一下:

  • 错误显示已启用 E_ALL
  • flush 工作正常
  • OP 说他正确使用了本机文件功能fopenfreadfwriteflock,但它没有帮助。

如果flush 工作正常,文件系统工作正常,我只能相信 OP 他是对的并放弃。

所以知道我在这里的工作已经完成了,如果我不能自己在 OP 的系统、配置和代码上尝试它,我就无能为力了。

我取消删除了我的答案,因此 OP 可以获得文档链接,并且其他人可以看到我尝试提出解决方案。

我删除了我的旧帖子


1.测试 massrelay.php

while(true) {
    echo "test!";
    sleep(1);
} 

这样您就可以确定问题与文件无关。

2。确保您已启用 error_reportingdisplay_errors

我猜你会在 30 秒后得到响应,因为 PHP 脚本在时间限制后被终止。如果您启用了错误,您会看到错误消息通知您。

3.确保你确实刷新了你的输出并且它没有被缓冲。

可能需要 30 秒到 10 分钟

您能够在 30 秒后看到数据是有意义的,因为 30 秒是 PHP 中最大执行时间的默认值。

看起来flush() 在您的脚本中不起作用,您应该检查 php.ini 文件中的output_buffering 设置

请看这个:php flush not working

文档:

【讨论】:

  • while(true) { echo "test!"; sleep(1); } 我已经实际尝试过了,它在 5-10 毫秒 id 内完美运行。
  • 其实我试过fopen(); fread(); fclose(); 现在试试flock();
【解决方案3】:

几年前,我尝试使用平面文件并将数据存储在数据库中,以便多个并发用户与服务器进行通信(这是针对 Flash 游戏,但适用相同的原则)。

平面文件的性能最差,因为您最终会遇到读/写访问问题。

对于 DB,它最终也会因请求过多而崩溃,尤其是当您每秒访问 DB 数千次并且没有适当的负载平衡时。

我的答案不是解决您当前的问题,而是将您引向不同的方向。你真的要看看使用套接字服务器。也许看看类似的东西:https://github.com/reactphp/socket

您在使用套接字服务器时可能遇到的一些问题是共享主机不允许您运行 shell 脚本。我的解决方案是使用我的家用 PC 进行套接字通信,并将我的域用作托管游戏的公共入口点。显然,我们并不是所有人都有静态 IP 来指向我们的游戏,所以我不得不使用 dyndns,当时它是免费的:http://dyn.com(可能还有一些其他新服务现在是免费的)。使用家庭服务器时,您还需要设置路由器以进行端口转发,以将 IP/路由器上的任何特定端口请求发送到 LAN 服务器。确保您在路由器和服务器上都运行防火墙,以保护其他可能暴露的端口。

我知道这可能看起来很复杂,但相信我,这是最理想的解决方案。如果您需要任何帮助,请 PM 我,我可以尝试指导您解决您可能遇到的任何问题。

【讨论】:

  • 啊,谢谢,我会试着调查一下,如果我遇到任何问题,请 PM 你。
【解决方案4】:

我看到您的代码存在三个问题:

  • 不睡觉
  • 没有 ob_flush
  • 会话

您的 while() 循环不断读取文件系统。你需要放慢速度。我在下面睡了半秒钟;试验可接受延迟的最大值。

PHP 有自己的输出缓冲区。您使用@ob_flush() 刷新它们(@ 抑制错误)并使用flush() 刷新 Apache 缓冲区。两者都需要,顺序也很重要。

最后,PHP 会话锁定,因此如果您的客户端可能正在发送会话 cookie,即使您的 SSE 脚本不使用会话数据,您也必须在进入无限循环之前关闭会话。

我已将所有这三个更改添加到您的代码中,如下所示。

<?php
    header('Content-Type: text/event-stream');
    header('Cache-Control: no-cache');
    session_write_close();
    while(1)
    {
        $data = file_get_contents("data.txt");
        if ($data != "NULL")
        {
            echo "data: " . $data . "\n\n";
            @ob_flush();flush();
            file_put_contents("data.txt", "NULL");
        }
        usleep(500000);
    }

顺便说一句,另一个答案中关于使用内存数据库的建议很好,但文件系统开销以毫秒为单位,因此无法解释“30 秒到 10 分钟”的延迟。

【讨论】:

  • 不要忘记file_get_contentsfile_put_contents 的“魔法”文件锁定。它不可靠的AF
  • @Peter 你能提供一个关于这个“魔法”的好链接吗?你只是说上面的代码很危险,因为它没有做任何显式锁定,所以在调用file_get_contents() 和调用file_put_contents() 之间存在竞争条件?
  • P.S.我可以插入我的书“使用 HTML5 SSE 的数据推送应用程序”吗?尽管它已经成立两年了,但 SSE 没有任何变化,甚至浏览器也没有太大变化。这些示例主要使用 PHP 编写,@ob_flush();flush(); 成语之类的内容在第一章 IIRC 中进行了介绍。
  • @DarrenCook 果然,我插入了您修改后的 massrelay.php 版本,它运行良好。通信时间几乎是瞬时的。我不完全确定它是如何工作的,但结果几乎让我想读你的书(不幸的是它 3 美元而且不是免费的)。不过还是可以买一本。
  • @JamesOswald 与您因不知道自己在做什么而浪费的时间相比,或者与我花费大量时间编写这本书相比,这本书的成本很小。让您的公司购买它。如果这是一个爱好项目,请考虑尝试让您当地的图书馆收藏它。但是请不要发布非法下载的链接:-)
【解决方案5】:

在不得不调试 SSE 的几个单独实例之一中,我发现 if (ob_get_level() &gt; 0) {ob_end_clean();} 具有讽刺意味地导致了这个问题。如果没有任何级别,这就是防止 PHP 错误产生所需的代码。恢复到ob_end_clean(); 解决了这个问题。

【讨论】:

    猜你喜欢
    • 2014-12-28
    • 2013-11-21
    • 2019-06-22
    • 1970-01-01
    • 2013-01-06
    • 1970-01-01
    • 2016-12-10
    • 1970-01-01
    • 2018-10-26
    相关资源
    最近更新 更多