我目前正在为网络游戏编写通信框架,通信地图如下所示:代码如下:
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就是&#34; tube&#34;数据传递给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传递给任何有事件监听器的人,一旦读取数据,它将清除数据文件。
整个事情实际上是完美的,除了轻微的ishue可能需要30秒到10分钟,massrelay.php从数据文件发送数据。对于网络游戏,这是完全不可接受的,因为您需要实时操作。我想知道是否由于我的代码存在缺陷或者如果不是我正在思考硬件而花费这么长时间(我自己在戴着sempron的2006戴尔主持)。如果有人发现任何问题,请让我知道谢谢。
如果您有兴趣自己查看测试,可以找到它here
答案 0 :(得分:5)
我不知道写一个平面文件是最好的方法。文件I / O将成为你的主要瓶颈(阅读之上的阅读意味着你将很快达到最大值)。但假设你想继续这样做......
您的应用程序可以从PHP会话中受益,以存储一些数据,因此您不会等待I / O.这是Memcached或Redis等中间软件也可以帮助您的地方。你要做的是将reciver.php
中的数据存储在文本文件中并将其写入内存缓存(或将其放入写入内存存储的会话中)。这使得检索非常快速并减少了文件I / O.
我强烈建议为您的数据提供数据库。 MySQL尤其会将通常访问的数据加载到内存中以加快读取操作。
答案 1 :(得分:5)
我在您的代码中看到的三个问题:
你的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);
}
BTW,关于使用内存数据库的另一个答案中的建议是好的,但是文件系统开销是以毫秒为单位,所以它不会解释&#34; 30秒到10分钟&#34 ;等待时间。
答案 2 :(得分:1)
编辑:我删除了这个答案,因为OP说我建议的测试1.(见下文)工作正常,所以我的输出缓冲理论是错误的。但另一方面他说同样的代码与原生函数fread
fwrite
fclose
flock
不起作用,所以如果缓冲和文件I / O不是解决方案,我不要#39;不知道它是什么。我删除了我的帖子,因为我不认为这是一个有效的答案。让我总结一下:
flush
工作正常fopen
fread
fwrite
flock
并且它没有帮助。 如果flush
正在运行,文件系统正在运行,除了信任OP他是对的并放弃之后,我什么也做不了。
所以我知道我的工作已经完成,如果我不能在OP的系统,配置和代码上自己尝试,我无能为力。
我取消了我的答案,因此OP可以链接到文档,而其他人可以看到我尝试解决问题。
<强> 1。参与测试massrelay.php
while(true) {
echo "test!";
sleep(1);
}
这样您就可以确定问题与文件无关。
<强> 2。确保您已启用error_reporting
和display_errors
。
我猜你在30秒后得到回应,因为PHP脚本在时间限制后被终止。如果您启用了错误,您会看到错误消息通知您。
第3。确保您实际刷新输出并且没有缓冲。
它可能需要30秒到10分钟
30秒后能够看到数据是有意义的,因为30秒是PHP中最长执行时间的默认值。
看起来flush()
在您的screnario中无效,您应该检查php.ini文件中的output_buffering
设置
<强>文档强>
答案 3 :(得分:1)
多年前,我尝试使用平面文件&amp; amp;还将数据存储在数据库中,以便在多个并发用户与服务器之间进行通信(这适用于Flash游戏,但适用相同的原则)。
平面文件提供最差性能,因为您最终会遇到读/写访问问题。
使用数据库时,最终会因为请求过多而导致请求过多,特别是如果您每秒数万次击中数据库并且没有负载均衡。
我的回答并不是解决你当前的问题,而是指导你向不同的方向发展。你真的要看看使用套接字服务器。也许看看像https://github.com/reactphp/socket
这样的东西使用套接字服务器时可能遇到的一些问题是共享主机不允许您运行shell脚本。我的解决方案是使用我的家用PC进行套接字通信,并使用我的域作为托管游戏的公共入口点。显然,我们都没有静态IP指向我们的游戏所以我不得不使用dyndns然后它是免费的:http://dyn.com(可能还有一些其他新服务现在是免费的)。使用家庭服务器时,您还需要设置路由器以进行端口转发,以便将IP /路由器上的任何特定端口请求发送到LAN服务器。确保在路由器和服务器上运行防火墙,以保护其他可能暴露的端口。
我知道这可能看起来很复杂,但相信我这是最理想的解决方案。如果您需要任何帮助PM我,我可以尝试指导您解决您可能遇到的任何问题。