虽然服务器发送事件的循环导致页面冻结

时间:2015-04-06 22:34:12

标签: php server-sent-events

我目前正在进行一项使用Server-Sent Events接收消息的聊天。但是,我遇到了一个问题。服务器发送的事件永远不会连接并保持挂起状态,因为页面无法加载。

例如:

<?php
    while(true) {
        echo "data: This is the message.";
        sleep(3);
        ob_flush();
        flush();
    }
?>

我希望每隔3秒,&#34;数据:这就是消息。&#34;将被输出。相反,页面不会加载。但是,对于服务器发送的事件,我需要这种行为。有办法解决这个问题吗?

修改

完整代码:

<?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网站上也是如此。

9 个答案:

答案 0 :(得分:7)

毫无疑问,到目前为止你已经想到了这一点,但是在你没有使用的情况下我没有像以下几个sse脚本那样使用代码,它就像一个魅力。下面的代码是通用的,并没有以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();
    }
?>

答案 1 :(得分:5)

虽然这不是问题的直接答案,但尝试使用此方法查找错误..您没有收到错误,但这可能会帮助您找到它们吗?

基本上你想要一个简单的PHP脚本,其中包含你的主脚本,但是这个页面启用了错误...下面的示例..

index.php / Simple Error Includer

<?php
    ini_set('display_errors',1);
    ini_set('display_startup_errors',1);
    error_reporting(-1);
    require "other.php";
?> 

other.php / You Main Script

<?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,因为它在主页上没有无效语法,并且允许对任何包含进行错误检查。

但是,如果您在哪里查看other.php,您只需获得一个白色/空白页面,因为它无法验证整个页面/脚本。

我在我的项目中使用这个方法,不管我在other.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/

的示例

答案 2 :(得分:4)

我将抓住机会并陈述显而易见的,

你可以每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
}

答案 3 :(得分:4)

我注意到的一件事是sleep()函数与ob_start()结合使用,并且 - 完全代码示例中的任何地方都没有 - ob_start(),但是flush()ob_flush() ..

你还在冲脸什么? 为什么不简单地ob_end_flush()

事情是sleep()而不是echo(),而不是sleep(),而不是echo()等,等等......当输出缓冲打开时无效。当输出缓冲不起作用时,睡眠功能按预期工作。事实上,它可能*(并且会)产生非常意外的结果,而这些结果不会成为我们想要看到的结果。

答案 4 :(得分:4)

似乎睡眠功能正在干扰输出。将AFTERWARDS的睡眠功能确实起作用了:

<?php
while(true) {
    echo "data: This is the message.";
    ob_flush();
    flush();
    sleep(3);
    }

正如其他人的建议,我鼓励使用AJAX而不是无限循环,但这不是你的问题。

答案 5 :(得分:4)

而不是使用循环尝试下面给出的代码,根据您的要求正常工作(自己测试)

echo "data: This is the message."; $url1="<your-page-name>.php"; header("Refresh: 5; URL=$url1");

这将会做什么,它将每5秒调用一次(在你的情况下将其设置为3而不是5)并回显输出。

希望这能解决您的问题

答案 6 :(得分:3)

它可以像脚本超时一样简单吗?

如果PHP脚本运行时间太长,它们最终会自动终止。您不希望发生这种情况的解决方案是继续重置超时。

所以你需要一个简单的添加:

<?php
    while(true) {
        echo "data: This is the message.";
        set_time_limit(30);
        sleep(3);
        ob_flush();
        flush();
    }
?>

当然,这可能不是它,但我的直觉是这就是问题所在。

更新:我在评论中注意到您正在使用一些免费托管。如果他们在safe mode中运行PHP,那么您无法重置超时。

答案 7 :(得分:3)

以下代码在这里工作正常,也使用Mayhem他的str_repeat函数添加4k数据(这通常是php刷新tcp数据包的最小值)

update to revision...

答案 8 :(得分:1)

我建议使用if()语句而不是while。在你的情况下,你的情况总是正确的,因此它处于无限循环中。