【问题标题】:Data getting lost in server-sent event with PHP handler使用 PHP 处理程序在服务器发送的事件中丢失数据
【发布时间】:2018-10-02 19:15:33
【问题描述】:

我正在开发一种使用服务器发送事件的单向消息传递系统。我有一个文件(server.html),它将文本区域的内容发送到 PHP 文件(handler.php)。

function sendSubtitle(val) {
    var xhr = new XMLHttpRequest();
    var url = "handler.php";
    var postdata = "s=" + val;
    xhr.open('POST', url, true);
    xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");  
    xhr.send(postdata);
    //alert(val);
}

这有效(alert(val) 显示文本区域中的文本)。

我的 handler.php 代码如下所示:

header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
$stringData = $_POST['s'];

echo "data: Data is {$stringData}\n\n";
flush();

而我的SSE接收器文件(client.html)的相关部分如下:

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

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

问题在于 client.html 只显示“数据:数据是”,因此 server.html 中的文本在途中会丢失。我想是 PHP 代码崩溃了,但我不知道出了什么问题。如果有人可以提供帮助,我将不胜感激。

编辑

我选择使用 SSE 而不是 websockets,因为我只需要单向通信:server.html 应该在其 textarea 的内容发生变化时将其推送到 client.html。我看过的所有 SSE 示例(而且我看过很多!)发送“自动”基于时间的数据。我还没有看到任何使用实时用户输入的方法。所以也许我应该澄清我最初的问题并问:“每当用户在网页 A 中输入文本区域时,我如何使用 SSE 更新网页 B 中的 DIV(或其他)?”

更新

我已将问题缩小到 PHP 文件中的 while 循环,因此提出了一个新问题:Server-side PHP event page not loading when using while loop

【问题讨论】:

  • 您通过 2 个不同的请求从 2 个不同的文件调用 handler.php。所以 handler.php 将在 2 个不共享普通变量的独立进程中运行。
  • 谢谢,dn Fer。请在下面查看我对 Prakash 回答的评论。

标签: php server-sent-events


【解决方案1】:

假设您要从 server.html 发送一个值,并且 client.html 中的一个值将自动更新...

您需要将新值存储在某处,因为脚本的多个实例不会像那样共享变量。这个新值可以存储在文件、数据库或会话变量等中。

步骤:

  1. 使用 clientScript1 向 phpScript1 发送新值。
  2. 用 phpScript1 存储新值。
  3. 将 clientScript2 连接到 phpScript2。
  4. 如果已更改,则将存储的值发送到 clientScript2。

“即时”获取新值意味着 phpScript2 必须循环执行并在 clientScript1 更改值时向 clientScript2 发送消息。

当然,有更多不同的方法可以达到相同的结果。

下面是我在之前项目中使用的暂存器中的一些代码。 大多数部分来自一个类(正在开发中),所以我不得不采用相当多的代码。我也尝试将它融入您现有的代码中。 希望我没有引入任何错误。
请注意,我没有对您的价值进行任何验证!此外,代码没有经过调试或优化,因此还没有准备好投入生产。

客户端(发送新值,例如您的代码):

function sendSubtitle(val) {
    var xhr = new XMLHttpRequest();
    var url = "handler.php";
    var postdata = "s=" + val;
    xhr.open('POST', url, true);
    xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");  
    xhr.send(postdata);
    //alert(val);
}

服务器端(存储新值):

<?php
session_start();
$_SESSION['s'] = $_POST['s'];

客户端(获取新值):

//Check for SSE support at client side.
if (!!window.EventSource) {
    var es = new EventSource("SSE_server.php");
} else {
    console.log("SSE is not supported by your client");
    //You could fallback on XHR requests.
}

//Define eventhandler for opening connection.
es.addEventListener('open', function(e) {
  console.log("Connection opened!");
}, false);

//Define evenhandler for failing SSE request.
es.addEventListener('error', function(event) {
    /*
     * readyState defines the connection status:
     * 0 = CONNECTING:  Connecting
     * 1 = OPEN:        Open
     * 2 = CLOSED:      Closed
     */
  if (es.readyState == EventSource.CLOSED) {
    // Connection was closed.
  } else {
      es.close(); //Close to prevent a reconnection.
      console.log("EventSource failed.");
  }
});

//Define evenhandler for any response recieved.
es.addEventListener('message', function(event) {
    console.log('Response recieved: ' + event.data);
}, false);

// Or define a listener for named event: event1
es.addEventListener('event1', function(event) {
    var response = JSON.parse(event.data);
    var textarea = document.getElementById("subtitles");
    textarea.value += response + "<br>";
    textarea.scrollTop = textarea.scrollHeight;
});

服务器端(发送新值):

<?php
$id = 0;
$event = 'event1';
$oldValue = null;
session_start();

//Validate the clients request headers.
if (headers_sent($file, $line)) {
    header("HTTP/1.1 400 Bad Request");
    exit('Headers already sent in %s at line %d, cannot send data to client correctly.');
}
if (isset($_SERVER['HTTP_ACCEPT']) && $_SERVER['HTTP_ACCEPT'] != 'text/event-stream') {
    header("HTTP/1.1 400 Bad Request");
    exit('The client does not accept the correct response format.');
}

//Disable time limit
@set_time_limit(0);

//Initialize the output buffer
if(function_exists('apache_setenv')){
    @apache_setenv('no-gzip', 1);
}
@ini_set('zlib.output_compression', 0);
@ini_set('implicit_flush', 1);
while (ob_get_level() != 0) {
    ob_end_flush();
}
ob_implicit_flush(1);
ob_start();

//Send the proper headers
header('Content-Type: text/event-stream; charset=UTF-8');
header('Cache-Control: no-cache');
header('X-Accel-Buffering: no'); // Disables FastCGI Buffering on Nginx

//Record start time
$start = time();

//Keep the script running
while(true){
    if((time() - $start) % 300 == 0){
        //Send a random message every 300ms to keep the connection alive.
        echo ': ' . sha1( mt_rand() ) . "\n\n";
    }

    //If a new value hasn't been sent yet, set it to default.
    session_start();
    if (!array_key_exists('s', $_SESSION)) {
        $_SESSION['s'] = null;
    }

    //Check if value has been changed.
    if ($oldValue !== $_SESSION['s']) {
        //Value is changed
        $oldValue = $_SESSION['s'];
        echo 'id: '    . $id++  . PHP_EOL;  //Id of message
        echo 'event: ' . $event . PHP_EOL;  //Event Name to trigger the client side eventhandler
        echo 'retry: 5000'      . PHP_EOL;  //Define custom reconnection time. (Default to 3000ms when not specified)
        echo 'data: '  . json_encode($_SESSION['s']) . PHP_EOL; //Data to send to client side eventhandler
        //Note: When sending html, you might need to encode with flags: JSON_HEX_QUOT | JSON_HEX_TAG
        echo PHP_EOL;
        //Send Data in the output buffer buffer to client.
        @ob_flush();
        @flush();
    }

    //Close session to release the lock
    session_write_close();

    if ( connection_aborted() ) {
        //Connection is aborted at client side.
        break;
    }
    if((time() - $start) > 600) {
        //break if the time exceeds the limit of 600ms.
        //Client will retry to open the connection and start this script again.
        //The limit should be larger than the time needed by the script for a single loop.
        break;
    }

    //Sleep for reducing processor load.
    usleep(500000);
}

【讨论】:

  • 我现在明白了。非常感谢您的详细解答!
  • @MarkRaishbrook 没问题。祝你发展顺利。
  • 我已经更正了服务器端发送代码中的几个错误:$_SESION,例如,缺少 ) 在 exit('Headers already sent... 行的末尾,我将 echo 'retry: 5000' 上方的大括号移动到文件末尾。所以现在我让客户端发送代码如上所述,但发送到 store.php、store.php(根据您的代码),客户端得到代码(其中 EventSource = handler.php),最后是编辑后的服务器端发送代码(handler.php)。它工作了一次,但随后出现“客户端不接受正确的响应格式”的错误。你能看到我在哪里发送格式错误?
  • 对错误感到抱歉,我也已在我的回答中更正了它们。客户端应该只调用一次 parser.php 并保持连接打开。所以它不应该工作一次然后给出错误,因为脚本卡在 while 循环中。也许您移到末尾的花括号现在位于错误的位置。它应该已被删除。 @flush() 下面的花括号是需要的。请使用新代码重试。仅供参考:当您的客户端脚本执行 SSE 请求时,请求标头之一应该是“文本/事件流”。如果不是,您将收到有关响应格式的错误。
  • 好的,现在再试一次!
【解决方案2】:

您第一次在 server.html 和 client.html 中调用了 handler.php。两者是不同的过程。变量状态不会保留在 Web 服务器中。如果您希望在另一个 PHP 进程中使用该值,则需要将其存储在某处。可能您可以使用会话或数据库。

在使用会话时,您可以将值存储在两个文件中,例如:

<?php
//server.php
session_start();
$_SESSION['s'] = $_POST['s'];

在client.php中

<?php
//client.php
session_start();
echo "data: Data is ".$_SESSION['s']."\n\n";

【讨论】:

  • 感谢您的回答,Prakash,但我不确定这是问题所在。我只是在w3schools.com/html/html5_serversentevents.asp 处改编了这个例子。 Client.html 使用 handler.php 作为事件源,所以当 handler.php 更新时,它会将数据推送到 client.html。
  • 我已经编辑了我的问题以澄清我想要做什么,@Prakash S.
  • 问题仍然存在......示例的 php 文件定义了自己发回的数据。我会看看我是否存储了关于 sse 的要点,服务器在其中循环并发送有关存储变量更改的数据。
  • @PrakashS 你实际上是在正确的轨道上,所以非常感谢你。不幸的是,我没有足够的声望来支持你,所以请接受虚拟 +1!
猜你喜欢
  • 2010-11-28
  • 2019-04-16
  • 1970-01-01
  • 1970-01-01
  • 2010-11-18
  • 1970-01-01
  • 2021-10-08
  • 2018-01-25
  • 1970-01-01
相关资源
最近更新 更多