Хотя циклы для событий, отправленных сервером, заставляют страницы замораживать
В настоящее время я работаю над чатом, который использует 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.
Ответы
Ответ 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. И в вашем случае ваше условие всегда истинно, поэтому оно находится в бесконечном цикле.