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_reporting
和 display_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();
解决了问题。
目前正在写一个网页游戏的通信框架,通信图如下:
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_reporting
和 display_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();
解决了问题。