使用while循环时无法加载服务器端PHP事件页面

时间:2018-10-04 15:44:02

标签: php server-side server-sent-events

我有一个名为handler.php的文件,该文件从文本文件读取数据并将其推送到客户端页面。

相关客户代码:

<script>
if(typeof(EventSource) !== "undefined") {
    var source = new EventSource("handler.php");
    source.onmessage = function(event) {
        var textarea = document.getElementById("subtitles");
        textarea.value += event.data;
        textarea.scrollTop = textarea.scrollHeight;

    };
} else {
    document.getElementById("subtitles").value = "Server-sent events not supported.";
}
</script>

Handler.php代码:

$id = 0;
$event = 'event1';
$oldValue = null;

header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('X-Accel-Buffering: no');

while(true){

    try {
        $data = file_get_contents('liveData.txt');
    } catch(Exception $e) {
        $data = $e->getMessage();
    }

    if ($oldValue !== $data) {
        $oldValue = $data;
        echo 'id: '    . $id++  . PHP_EOL;
        echo 'event: ' . $event . PHP_EOL;
        echo 'retry: 2000'      . PHP_EOL;
        echo 'data: '  . json_encode($data) . PHP_EOL;
        echo PHP_EOL;

        @ob_flush();
        @flush();
        sleep(1);  
        }

  }

使用循环时,永远不会加载handler.php,因此不会向客户端发送任何数据。在Chrome开发者网络标签中,handler.php显示为“待处理”,然后显示为“已取消”。该文件本身会保持锁定状态约30秒。

但是,如果我删除while循环(如下所示),则将处理handler.php 加载,并且客户端接收数据(即使liveData仅一次) .txt文件会不断更新)。

不带循环的Handler.php:

$id = 0;
$event = 'event1';
$oldValue = null;

header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('X-Accel-Buffering: no');

try {
    $data = file_get_contents('liveData.txt');
} catch(Exception $e) {
    $data = $e->getMessage();
}

if ($oldValue !== $data) {
    $oldValue = $data;
    echo 'id: '    . $id++  . PHP_EOL;
    echo 'event: ' . $event . PHP_EOL;
    echo 'retry: 2000'      . PHP_EOL;
    echo 'data: '  . json_encode($data) . PHP_EOL;
    echo PHP_EOL;

    @ob_flush();
    @flush();

    }

我正在使用SSE,因为我只需要单向通信(因此websocket可能会过分杀人),我真的不想使用轮询。如果我无法解决这个问题,则可能必须这样做。

4 个答案:

答案 0 :(得分:2)

据我所知,尽管我将var textarea.....移到了onmessage处理程序的外部,但SSE连接的客户端看起来还不错。

更新:我应该仔细看一下,但是要监视的事件是event1,所以我们需要为该事件设置一个事件侦听器。

<script>
    if( typeof( EventSource ) !== "undefined" ) {
        var url = 'handler.php'

        var source = new EventSource( url );
        var textarea = document.getElementById("subtitles");


        source.addEventListener('event1', function(e){
            textarea.value += e.data;
            textarea.scrollTop = textarea.scrollHeight;
            console.info(e.data);
        },false );

    } else {
        document.getElementById("subtitles").value = "Server-sent events not supported.";
    }
</script>

对于SSE服务器脚本,我倾向于采用这样的方法

<?php
    /* make sure the script does not timeout */
    set_time_limit( 0 );
    ini_set('auto_detect_line_endings', 1);
    ini_set('max_execution_time', '0');

    /* start fresh */
    ob_end_clean();


    /* ultility function for sending SSE messages */
    function sse( $evtname='sse', $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";
        }
    }



    $id = 0;
    $event = 'event1';
    $oldValue = null;

    header('Content-Type: text/event-stream');
    header('Cache-Control: no-cache');
    header('X-Accel-Buffering: no');





    while( true ){
        try {
            $data = @file_get_contents( 'liveData.txt' );
        } catch( Exception $e ) {
            $data = $e->getMessage();
        }

        if( $oldValue !== $data ) {

            /* data has changed or first iteration */
            $oldValue = $data;

            /* send the sse message */
            sse( $event, $data );

            /* make sure all buffers are cleansed */
            if( @ob_get_level() > 0 ) for( $i=0; $i < @ob_get_level(); $i++ ) @ob_flush();
            @flush();           
        }



        /* 
            sleep each iteration regardless of whether the data has changed or not.... 
        */
        sleep(1);
    }



    if( @ob_get_level() > 0 ) {
        for( $i=0; $i < @ob_get_level(); $i++ ) @ob_flush();
        @ob_end_clean();
    }
?>

答案 1 :(得分:1)

  

使用循环时,永远不会加载handler.php,因此客户端不会   得到发送任何数据。在Chrome开发者网络标签中,handler.php是   显示为“待处理”,然后显示为“已取消”。文件本身保持锁定   大约30秒。

这是因为在30秒内没有响应时,Web服务器(Apache)或浏览器甚至PHP本身都会取消请求。

所以我想刷新不起作用,请尝试在不使用@函数的情况下主动启动和结束缓冲区,以便在出现错误时获得提示。

// Start output buffer
ob_start();

// Write content
echo ''; 

// Flush output buffer
ob_end_flush();

答案 2 :(得分:0)

我认为您的网络运作方式存在问题。该PHP代码不会在您的浏览器中运行-只是创建一些内容,然后网络服务器通过电线将其移交给浏览器。

一旦从服务器上加载页面。您将需要实施一些轮询更改的事情。

我执行此操作的一种方法是将页面放入一个刷新的循环中,并因此每隔一秒钟左右用新数据再次获取页面(但是如果有很多人这样做,可能会严重导致服务器超载)页)。

唯一的其他解决方案是使用推技术和可以进行推并重新填充页面相关部分的javascript框架,或者使用计时器上的javascript循环来提取数据。

答案 3 :(得分:0)

(代表问题作者发布的解决方案)

成功!在第n次调试时,我决定回到基础并重新开始。我取消了循环,将PHP代码减少到最低限度,但保留了RamRaider提供的客户端代码。现在,一切都很棒!通过使用重试值,我可以确切指定数据推送的频率。

PHP(服务器端):

 <?php

    $id = 0;
    $event = 'event1';
    $oldValue = null;

    header('Content-Type: text/event-stream');
    header('Cache-Control: no-cache');
    header('X-Accel-Buffering: no');

    try {
        $data = file_get_contents('liveData.txt');
    } catch(Exception $e) {
        $data = $e->getMessage();
    }

    if ($oldValue !== $data) {
        $oldValue = $data;
        echo 'id: '    . $id++  . PHP_EOL;
        echo 'event: ' . $event . PHP_EOL;
        echo 'retry: 500'      . PHP_EOL;
        echo "data: {$data}\n\n";
        echo PHP_EOL;

        @ob_flush();
        @flush();

        }

  ?>

JavaScript(客户端):

<script>
    if ( typeof(EventSource ) !== "undefined") {
        var url = 'handler.php'

        var source = new EventSource( url );
        var textarea = document.getElementById("subtitles");


        source.addEventListener('event1', function(e){
            textarea.value += e.data;
            textarea.scrollTop = textarea.scrollHeight;
            console.info(e.data);
        }, false );

    } else {
        document.getElementById("subtitles").value = "Server-sent events not supported.";
    }
</script>