Подтвердить что ты не робот

Хотя циклы для событий, отправленных сервером, заставляют страницы замораживать

В настоящее время я работаю над чатом, который использует Server-Sent Events для получения сообщений. Однако я столкнулся с проблемой. Событие, отправленное сервером, никогда не подключается и остается в ожидании, потому что страница не загружается.

Например:

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

Я ожидаю, что каждые 3 секунды "данные: это сообщение". будет выведено. Вместо этого страница просто не загружается. Однако мне нужно это поведение для событий, отправленных сервером. Есть ли способ исправить это?

Edit:

Полный код:

<?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.

4b9b3361

Ответ 1

Нет сомнений, что теперь вы это поняли, но на обочине вы не использовали код, похожий на следующий, на несколько сценариев 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();
    }
?>

Ответ 2

Хотя это не прямой ответ на проблему, попробуйте использовать этот метод, чтобы найти ошибку. Вы не получаете ошибок, но это должно помочь вам найти их, возможно?

В основном вы хотите иметь простой PHP script, который включает в себя ваш основной script, но эта страница допускает ошибки... Пример ниже..

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, вы просто получите белую/пустую страницу, потому что не сможете проверить всю страницу / script.

Я использую этот метод в своих проектах таким образом, независимо от того, что я делаю в 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/

Ответ 3

Я собираюсь рискнуть и указать очевидное,

вы можете запрашивать сервер каждые 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
}

Ответ 4

Одна вещь, которую я заметил здесь, - это sleep() функция в сочетании с ob_start() и - В этом примере всего нет кода - ob_start(), но есть flush() и ob_flush()..

Что ты все равно смываешь? И почему не просто ob_end_flush()?

Дело в том, что sleep() чем echo(), чем sleep() снова, чем echo() снова и т.д. и т.д. не имеет никакого эффекта, когда буферизация вывода включена. Функция сна работает так, как ожидалось, когда буферизация вывода не находится в режиме воспроизведения. Фактически, он может * (и это будет) давать совершенно неожиданные результаты, и эти результаты не будут теми, которые мы хотим видеть.

Ответ 5

Кажется, что функция сна мешает выходу. Установка функции сна AFTERWARDS работала:

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

Как утверждают другие люди, я бы рекомендовал использовать AJAX вместо бесконечного цикла, но это не ваш вопрос.

Ответ 6

Вместо использования цикла попробуйте этот код, приведенный ниже, который работает (проверял себя) в соответствии с вашим требованием

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

что это будет делать, он будет называть себя каждые 5 секунд (в вашем случае установите его вместо 3 вместо 5) и эхо выводит результат.

Надеюсь, что это решит вашу проблему.

Ответ 7

Не может ли быть так же просто, как время ожидания script?

В конечном итоге скрипты PHP сами заканчиваются, если они работают слишком долго. Решение, когда вы этого не хотите, это сохранить сброс тайм-аута.

Таким образом, вам может понадобиться простое дополнение:

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

Конечно, может быть, это не мой инстинкт кишки, так это то, что это проблема.

UPDATE: я заметил в комментариях, что вы используете бесплатный хостинг. Если они запускают PHP в safe mode, то вы не можете reset ваш таймаут.

Ответ 8

Следующий код отлично работает здесь, также используя функцию Mayhem his str_repeat для добавления 4k данных (как правило, это минимальный для пакета tcp, который должен быть сброшен php)

echo str_repeat(' ', 4096);
while(true)
{
    echo "data: This is the message.";
    flush();
    sleep(3);
}

Ответ 9

Я предлагаю использовать инструкцию if() вместо использования while. И в вашем случае ваше условие всегда истинно, поэтому оно находится в бесконечном цикле.