7

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

测试.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 添加了一个事件侦听器。

接收器.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 戴尔上)。如果有人看到它有任何问题,请告诉我,谢谢。

4

5 回答 5

6

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

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

于 2016-07-30T22:52:35.173 回答
5

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

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

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

于 2016-07-30T17:28:42.510 回答
1

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

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

如果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()在您的脚本中不起作用,您应该检查output_bufferingphp.ini 文件中的设置

请看这个:php flush not working

文档:

于 2016-07-30T17:35:38.470 回答
1

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

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

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

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

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

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

于 2016-07-30T18:07:05.690 回答
0

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

于 2021-11-21T12:16:28.093 回答