SSE 非常慢

SSE incredibly slow

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

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 只是将 "tube" 数据传递给 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 Dell 上)。如果有人发现它有任何问题,请告诉我,谢谢。

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

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

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

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

  • 启用错误显示E_ALL
  • flush 工作正常
  • OP 说他正确地使用了本机文件函数 fopen fread fwrite flock 但它没有帮助。

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

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

我取消删除了我的答案,这样 OP 就可以链接到文档,其他人也可以看到我的解决方案。

我原来的POST我删除了


1.进行测试 massrelay.php

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

所以您可以确定问题与文件无关。

2。确保您已启用 error_reportingdisplay_errors

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

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

that it can take anywhere from 30 seconds to 10 minutes

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

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

请看这个:php flush not working

文档:

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

平面文件性能最差,因为您最终会 运行 进入 read/write 访问问题。

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

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

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

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

我发现您的代码存在三个问题:

  • 睡不着
  • 没有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 分钟”的延迟。

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