我目前正在处理一个聊天,该聊天使用服务器发送的事件来接收消息。然而,我遇到了一个问题。服务器发送的事件从不连接,并且由于页面未加载而处于挂起状态。
例如:
<?php
while(true) {
echo "data: This is the message.";
sleep(3);
ob_flush();
flush();
}
?>
我预计每3秒钟就会输出一次"数据:这就是消息。"。相反,页面就是不加载。但是,对于服务器发送的事件,我需要这种行为。有办法解决这个问题吗?
编辑:
完整代码:
<?php
session_start();
require "connect.php";
require "user.php";
session_write_close();
echo $data["number"];
header("Content-Type: text/event-stream'n'n");
header('Cache-Control: no-cache');
set_time_limit(1200);
$store = new StdClass(); // STORE LATEST MESSAGES TO COMPARE TO NEW ONES
$ms = 200; // REFRESH TIMING (in ms)
$go = true; // MESSAGE CHANGED
function formateNumber ($n) {
$areaCode = substr($n, 0, 3);
$part1 = substr($n, 3, 3);
$part2 = substr($n, 6, 4);
return "($areaCode) $part1-$part2";
}
function shorten ($str, $mLen, $elp) {
if (strlen($str) <= $mLen) {
return $str;
} else {
return rtrim(substr($str, 0, $mLen)) . $elp;
}
}
do {
$number = $data["number"];
$sidebarQ = "
SELECT *
FROM (
SELECT *
FROM messages
WHERE deleted NOT LIKE '%$number%'
AND (
`from`='$number'
OR
`to`='$number'
)
ORDER BY `timestamp` DESC
) as mess
GROUP BY `id`
ORDER BY `timestamp` DESC";
$query = $mysqli->query($sidebarQ);
if ($query->num_rows == 0) {
echo 'data: null' . $number;
echo "'n'n";
} else {
$qr = array();
while($row = $query->fetch_assoc()) {
$qr[] = $row;
}
foreach ($qr as $c) {
$id = $c["id"];
if (!isset($store->{$id})) {
$store->{$id} = $c["messageId"];
$go = true;
} else {
if ($store->{$id} != $c["messageId"]) {
$go = true;
$store->{$id} = $c["messageId"];
}
}
}
if($go == true) {
$el = $n = "";
foreach ($qr as $rows) {
$to = $rows["to"];
$id = $rows["id"];
$choose = $to == $number ? $rows["from"] : $to;
$nameQuery = $mysqli->query("SELECT `savedname` FROM `contacts` WHERE `friend`='$choose' AND `number`='$number'");
$nameGet = $nameQuery->fetch_assoc();
$hasName = $nameQuery->num_rows == 0 ? formateNumber($choose) : $nameGet["savedname"];
$new = $mysqli->query("SELECT `id` FROM `messages` WHERE `to`='$number' AND `tostatus`='0' AND `id`='$id'")->num_rows;
if ($new > 0) {
$n = "<span class='new'>" . $new . "</span>";
}
$side = "<span style='color:#222'>" . ($to == $number ? "To you:" : "From you:") . "</span>";
$el .= "<div class='messageBox sBox" . ($nameQuery->num_rows == 0 ? " noname" : "") . "' onclick='"GLOBAL.load($id, $choose)'" data-id='$id'><name>$hasName</name><div>$side " . shorten($rows["message"], 25, "...") . "</div>$n</div>";
}
echo 'data: '. $el;
echo "'n'n";
$go = false;
}
}
echo " ";
ob_flush();
flush();
sleep(2);
} while(true);
?>
我还想指出,这个无限循环不应该导致这种情况发生。这就是SSE通常的设置方式,甚至在MDN网站上也是如此。
毫无疑问,现在你已经明白了这一点,但很可能你还没有明白。下面的代码是通用的,没有您的sql或记录集处理功能,但想法是合理的(!?)
<?php
set_time_limit( 0 );
ini_set('auto_detect_line_endings', 1);
ini_set('mysql.connect_timeout','7200');
ini_set('max_execution_time', '0');
date_default_timezone_set( 'Europe/London' );
ob_end_clean();
gc_enable();
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Allow-Methods: GET');
header('Access-Control-Expose-Headers: X-Events');
if( !function_exists('sse_message') ){
function sse_message( $evtname='chat', $data=null, $retry=1000 ){
if( !is_null( $data ) ){
echo "event:".$evtname."'r'n";
echo "retry:".$retry."'r'n";
echo "data:" . json_encode( $data, JSON_FORCE_OBJECT|JSON_HEX_QUOT|JSON_HEX_TAG|JSON_HEX_AMP|JSON_HEX_APOS );
echo "'r'n'r'n";
}
}
}
$sleep=1;
$c=1;
$pdo=new dbpdo();/* wrapper class for PDO that simplifies using PDO */
while( true ){
if( connection_status() != CONNECTION_NORMAL or connection_aborted() ) {
break;
}
/* Infinite loop is running - perform actions you need */
/* Query database */
/*
$sql='select * from `table`';
$res=$pdo->query($sql);
*/
/* Process recordset from db */
/*
$payload=array();
foreach( $res as $rs ){
$payload[]=array('message'=>$rs->message);
}
*/
/* prepare sse message */
sse_message( 'chat', array('field'=>'blah blah blah','id'=>'XYZ','payload'=>$payload ) );
/* Send output */
if( @ob_get_level() > 0 ) for( $i=0; $i < @ob_get_level(); $i++ ) @ob_flush();
@flush();
/* wait */
sleep( $sleep );
$c++;
if( $c % 1000 == 0 ){/* I used this whilst streaming twitter data to try to reduce memory leaks */
gc_collect_cycles();
$c=1;
}
}
if( @ob_get_level() > 0 ) {
for( $i=0; $i < @ob_get_level(); $i++ ) @ob_flush();
@ob_end_clean();
}
?>
虽然这不是问题的直接答案,但请尝试使用此方法查找错误。。你没有得到错误,但这可能会帮助你找到它们?
基本上,你想要一个简单的PHP脚本,其中包括你的主脚本,但这个页面会出现错误。。。以下示例。。
index.php/简单错误包含程序
<?php
ini_set('display_errors',1);
ini_set('display_startup_errors',1);
error_reporting(-1);
require "other.php";
?>
other.php/您的主脚本
<?php
ini_set('display_errors',1);
ini_set('display_startup_errors',1);
error_reporting(-1);
weqwe qweqeq
qweqweqweqwe
?>
如果您创建这样的设置,如果您查看index.php,您将看到以下错误Parse error: syntax error, unexpected 'qweqeq' (T_STRING) in /var/www/html/syntax_errors/other.php on line 5
,因为它在主页面上没有无效语法,并且允许对任何include进行错误检查。。
但是,如果你在哪里查看other.php,你只会得到一个白色/空白的页面,因为它无法验证整个页面/脚本。
我在我的项目中使用这种方法,这样无论我在其他php或任何链接的php页面中做什么,我都会看到它们的错误报告。
请在评论前理解代码说这禁用错误控制意味着你没有麻烦RTM
填充缓冲区
我记得过去的另一个问题是在缓冲区输出到浏览器之前填充缓冲区。所以在循环之前尝试这样的方法。
echo str_repeat("'n",4096); // Exceed the required browser threshold
for($i=0;$i<70;$i++) {
echo "something as normal";
flush();
sleep(1);
}
示例位于http://www.sitepoint.com/php-streaming-output-buffering-explained/
似乎睡眠功能正在干扰输出。把睡眠功能放在后面确实有效:
<?php
while(true) {
echo "data: This is the message.";
ob_flush();
flush();
sleep(3);
}
正如其他人所建议的那样,我鼓励使用AJAX而不是无限循环,但这不是你的问题。
以下代码在这里运行良好,还使用Mayhem的str_repeak函数添加4k数据(这通常是php刷新tcp数据包的最小值)
echo str_repeat(' ', 4096);
while(true)
{
echo "data: This is the message.";
flush();
sleep(3);
}
我在这里注意到的一件事是sleep()
函数与ob_start()
和-在完整的代码示例中的任何地方都没有-ob_start(),但有flush()
和ob_flush()
。。
你到底在冲什么?为什么不简单地ob_end_flush()
呢?
问题是,当打开输出缓冲时,sleep()
比echo()
、再次比sleep()
、再次比echo()
等都没有效果。当输出缓冲不在播放时,睡眠功能可以正常工作。事实上,它可能会产生非常出乎意料的结果,而这些结果不会是我们想要看到的。
我遇到了同样的问题,最终在kevin choppin的博客上找到了简单快速的解决方案:
会话锁定
首先也是最重要的一点是,如果您出于任何原因使用会话,您都需要将它们设置为流上的只读会话。如果它们是可写的,这将在其他地方锁定它们,因此在服务器等待它们再次变为可写时,任何页面加载都将挂起。这很容易通过调用来解决;session_write_close();
不要使用循环,而是尝试下面给出的代码,它可以根据您的要求正常工作(我自己测试)
echo "data: This is the message.";
$url1="<your-page-name>.php";
header("Refresh: 5; URL=$url1");
它将每5秒调用一次自己(在您的情况下,将其设置为3而不是5)并回显输出。
您可以每隔3秒查询一次服务器,然后让客户端进行等待。。。
这可以通过javascript 轻松完成
例如,如果file.php
,请尝试此代码和名称
<?php
$action='';
if (array_key_exists('action',$_GET))
{$action=$_GET['action'];}
if ($action=='poll')
{
echo "this message will be sent every 3 sec";
}
else
{
?><HTML><HEAD>
<SCRIPT SRC="http://code.jquery.com/jquery-2.1.3.min.js"></SCRIPT>
<SCRIPT>
function doPoll()
{
$('#response').append($.get("file.php?action=poll"));
setTimeout(doPoll, 3000);
}
doPoll();
</SCRIPT>
</HEAD><BODY><DIV id="response"></DIV></BODY></HTML><?php
}
它会像脚本超时一样简单吗?
如果运行时间过长,PHP脚本最终会自行终止。当你不想发生这种情况时,解决方案是不断重置超时。
因此,一个简单的添加可能就是你所需要的:
<?php
while(true) {
echo "data: This is the message.";
set_time_limit(30);
sleep(3);
ob_flush();
flush();
}
?>
当然,可能不是这样,但我的直觉是这就是问题所在。
- http://php.net/manual/en/function.set-time-limit.php
更新:我在评论中注意到你正在使用一些免费主机。如果他们在safe mode
中运行PHP,则无法重置超时。
我建议使用if()语句而不是while。在你的情况下,你的条件总是真的,因此它处于无限循环中。