我想从服务器到客户端定期发送更新。为此,我使用了服务器发送的事件。我正在粘贴以下代码:
客户端
<script>
if(typeof(EventSource)!="undefined")
{
var source=new EventSource("demo_see.php");
source.onmessage=function(event)
{
document.getElementById("result").innerHTML=event.data + "<br>";
}
}
else
{
document.getElementById("result").innerHTML="Sorry, your browser does not support server-sent events...";
}
</script>
</body>
</html>
服务器端
<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
$x=rand(0,1000);
echo "data:{$x}\n\n";
flush();
?>
代码工作正常,但它会在每个3 seconds
中发送更新。我想以毫秒为单位发送更新。我在sleep(1)
之后尝试flush()
,但它只会将间隔进一步增加1秒。有没有人有一个想法我怎么能做到这一点?
另外,我可以使用服务器发送的事件发送图像吗?
答案 0 :(得分:10)
正如上面的评论中所讨论的那样,在sleep
或usleep
的无限循环中运行PHP脚本是不正确的,原因有两个
正确的做法是让您的PHP脚本响应事件流数据,然后像往常一样正常终止。如果要控制浏览器何时再次尝试,请提供retry
值(以毫秒为单位)。这是一些示例代码
function yourEventData(&$retry)
{
//do your own stuff here and return your event data.
//You might want to return a $retry value (milliseconds)
//so the browser knows when to try again (not the default 3000 ms)
}
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('Access-Control-Allow-Origin: *');//optional
$data = yourEventData($retry);
echo "data:{$str}\n\nretry:{$retry}\n\n";
作为原始问题的答案,这是一个位迟到但仍然是为了完整性:
以这种方式轮询服务器时获得的只是数据。之后你用它做什么完全取决于你。如果您想将这些数据视为图像并更新网页中显示的图像,您只需执行
document.getElementById("imageID").src = "data:image/png;base64," + Your event stream data;
原则如此之多。我偶尔会忘记retry
必须在几毫秒内完成返回,例如retry:5\n\n
,令我惊讶的是,它仍然有用。但是,我会毫不犹豫地使用SSE以100ms的间隔更新浏览器端图像。更典型的用法是沿着以下几行
retry
值,以便浏览器知道何时再看一遍结果。data{"code":-1}\n\n
所以浏览器端代码可以优雅地处理这种情况。还有其他使用场景 - 更新股票报价,新闻标题等。以100毫秒的间隔更新图像感觉 - 纯粹是个人观点 - 就像滥用技术一样。
答案 1 :(得分:6)
解释了此行为的原因(每隔3秒发送一条消息)here:
浏览器尝试在每次连接关闭后大约3秒重新连接到源
因此,每100毫秒获取一条消息的一种方法是改变重新连接时间:(在PHP中)
echo "retry: 100\n\n";
这不是很优雅,但更好的方法是在PHP中无限循环,每次迭代将睡眠100毫秒。有一个很好的示例here,只需将sleep()
更改为usleep()
即可支持毫秒:
while (1) {
$x=rand(0,1000);
echo "data:{$x}\n\n";
flush();
usleep(100000); //1000000 = 1 seconds
}
答案 2 :(得分:3)
我认为接受的答案可能误导。虽然它正确回答了问题(如何设置1秒间隔),但无限循环通常不是一种糟糕的方法。
当实际存在与Ajax轮询相反的更新时,SSE用于从服务器获取更新,该更新在某些时间间隔内不断检查更新(即使没有更新)。这可以通过无限循环来实现,该循环使服务器端脚本始终保持运行,不断检查更新并仅在有更改时回显它们。
不正确:
当该脚本仍在运行时,浏览器不会看到任何事件数据。
您可以在服务器上运行该脚本,并仍然将更新发送到浏览器,而不是像这样结束脚本执行:
while (true) {
echo "data: test\n\n";
flush();
ob_flush();
sleep(1);
}
通过发送没有无限循环的重试参数来完成它将结束脚本,然后再次启动脚本,结束它,再次启动...这类似于Ajax-polling检查更新,即使没有,这不是SSE如何运作。当然,在某些情况下,这种方法是合适的,就像在接受的答案中列出的那样(例如,等待服务器创建PDF并在完成后通知客户端)。
使用无限循环技术将使脚本始终在服务器上运行,因此您应该小心许多用户,因为每个用户都有一个脚本实例,这可能导致服务器过载。另一方面,即使在一些简单的场景中,你突然在网站上获得大量用户(没有SSE),或者如果你使用Web套接字而不是SSE,也会出现同样的问题。一切都有其局限性。
另外要注意的是你在循环中放入的内容。例如,我不建议将数据库查询放在每秒运行一次的循环中,因为这样你也会使数据库面临超载的风险。我建议在这种情况下使用某种缓存(Redis甚至是简单的文本文件)。
答案 3 :(得分:0)
SSE是一种有趣的技术,但它对使用 APACHE / PHP 后端的实现产生了令人窒息的副作用。
当我第一次了解SSE时,我感到非常兴奋,以至于我将所有Ajax轮询代码都替换为SSE实现。这样做只有几分钟,我注意到我的CPU使用率升至 99/100 ,并且担心我的服务器很快将被关闭,迫使我将更改恢复到友好的旧版本Ajax轮询。我喜欢 PHP ,尽管我知道SSE在Node.is上可以更好地工作,但我还没有准备好走这条路!
经过一段时间的批判性思考,我想出了一个 SSE APACHE / PHP 实现可以正常运行,而不会导致服务器死机。
我将与您分享我的SSE服务器端代码,希望它可以帮助某人克服用PHP实现SSE的挑战。
<?php
/* This script fetches the lastest posts in news feed */
header("Content-Type: text/event-stream");
header("Cache-Control: no-cache");
// prevent direct access
if ( ! defined("ABSPATH") ) die("");
/* push current user in session data into global space so
we can release session lock */
$GLOBALS["exported_user_id"] = user_id();
$GLOBALS["exported_user_tid"] = user_tid();
/* now release session lock having exported session data
in global space. if we don't do this, then no other scripts
will run thus causing the website to lag even when
opening in a new tab */
session_commit();
/* how long should this connection be maintained -
while we want to wait on the server long enoug for
update, holding the connection forever burn CPU
resources, depending on the server resources you have
available you can tweak this higher or lower. Typically, the
higher the closer your implementation stays as an SSE
otherwise it will be equivalent to Ajax polling. However, an
higher time burns CPU resource especially when there's
more users on your website */
$time_to_stay = strtotime("1 minute 30 seconds");
/* if no data is sent, we wait 2 seconds then abort
connection. You can use this to test when a data you
require for script operation is not passed along. Typically
SSE reconnects after 3 seconds */
if ( ! isset( $_GET["id"] ) ){
exit;
}
/* if "HTTP_LAST_EVENT_ID" is set, then this is a
continue of temporily terminated script operation. This is
important if your SSE is maintaining state you can use
the header to get last event ID sent */
$last_postid = ( ( isset(
$_SERVER["HTTP_LAST_EVENT_ID"] ) ) ? intval(
$_SERVER["HTTP_LAST_EVENT_ID"] ) :
intval( $_GET["id"] ) );
/* keep the connection active until there's data to send to
client */
while (true) {
/* You can assume this function perform some database
operations to get latest posts */
$data = fetch_newsfeed( $last_postid );
/* if data is not empty, we want to push back to the client
then there must have been some new posts to push to
client */
if ( ! empty( trim( $data ) ) ){
/* With SSE its my common practice to Json encode all
data because I notice that not doing so, sometimes
cause SSE to lose the data packet and only deliver a
handful of the data on the client. This is bad since we are
returning a structured HTML data and loosing some part
of it will cause our HTML page to break when the data is
inserted in our page */
$data = json_encode(array("result" => $data));
echo "id: $last_postid \n"; // this is the lastEventID
echo "data: $data\n\n"; // our data
/* flush to avoid waiting for script to terminate - make
sure its in the same order */
@ob_flush(); flush();
}
// the amount of time that has been spent on this script
$time_stayed = intval(floor($time_to_stay) - time());
/* if we have stayed more than time to stay, then abort
this connection to free up CPU resource */
if ( $time_stayed <= 0 ) { exit; }
/* we simply wait 5 seconds and continue again from
start . We don't want to keep pounding our DB since we
are in a tight loop so we sleep a few seconds and start
from top*/
sleep(5);
}