使用 PHP 处理程序在服务器发送事件中丢失数据
Data getting lost in server-sent event with PHP handler
我正在开发一种使用服务器发送事件的单向消息传递系统。我有一个文件 (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 只显示 "data: Data is",所以来自 server.html 的文本在途中丢失了。我想是 PHP 代码崩溃了,但我无法弄清楚哪里出了问题。如果有人能提供帮助,我将不胜感激。
编辑
我选择使用 SSE 而不是 websockets,因为我只需要单向通信:server.html 应该在其文本区域的内容发生变化时将其推送到 client.html。我看过(而且看过很多!)的所有 SSE 示例都发送 "automatic" 基于时间的数据。我还没有看到任何使用实时用户输入的。所以也许我应该澄清我原来的问题并问,"How can I use SSE to update a DIV (or whatever) in web page B whenever the user types in a textarea in web page A?"
更新
我已将问题缩小到 PHP 文件中的 while 循环,因此提出了一个新问题:
您在 server.html 中第一次呼叫 handler.php,在 client.html 中再次呼叫。两者是不同的过程。变量状态不会保留在 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";
假设您要从 server.html 发送一个值,并且 client.html 的值将自动更新...
您需要将新值存储在某处,因为脚本的多个实例不会像那样共享变量。这个新值可以存储在文件、数据库或作为会话变量等。
步骤:
- 使用 clientScript1 向 phpScript1 发送新值。
- 使用 phpScript1 存储新值。
- 将 clientScript2 连接到 phpScript2。
- 如果更改,则将存储的值发送到 clientScript2。
获取新值'on the fly' 意味着 phpScript2 必须循环执行并在 clientScript1 更改值时向 clientScript2 发送消息。
当然有更多不同的方法可以达到相同的结果。
下面是我在上一个项目中使用的暂存器中的一些代码。
大多数部分来自 class(正在开发中),所以我不得不采用相当多的代码。我也试图将它融入您现有的代码中。
希望我没有引入任何错误。
请注意,我没有考虑任何对您价值的验证!此外,代码未调试或优化,因此尚未准备好投入生产。
客户端(发送新值,例如您的代码):
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);
}
我正在开发一种使用服务器发送事件的单向消息传递系统。我有一个文件 (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 只显示 "data: Data is",所以来自 server.html 的文本在途中丢失了。我想是 PHP 代码崩溃了,但我无法弄清楚哪里出了问题。如果有人能提供帮助,我将不胜感激。
编辑
我选择使用 SSE 而不是 websockets,因为我只需要单向通信:server.html 应该在其文本区域的内容发生变化时将其推送到 client.html。我看过(而且看过很多!)的所有 SSE 示例都发送 "automatic" 基于时间的数据。我还没有看到任何使用实时用户输入的。所以也许我应该澄清我原来的问题并问,"How can I use SSE to update a DIV (or whatever) in web page B whenever the user types in a textarea in web page A?"
更新
我已将问题缩小到 PHP 文件中的 while 循环,因此提出了一个新问题:
您在 server.html 中第一次呼叫 handler.php,在 client.html 中再次呼叫。两者是不同的过程。变量状态不会保留在 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";
假设您要从 server.html 发送一个值,并且 client.html 的值将自动更新...
您需要将新值存储在某处,因为脚本的多个实例不会像那样共享变量。这个新值可以存储在文件、数据库或作为会话变量等。
步骤:
- 使用 clientScript1 向 phpScript1 发送新值。
- 使用 phpScript1 存储新值。
- 将 clientScript2 连接到 phpScript2。
- 如果更改,则将存储的值发送到 clientScript2。
获取新值'on the fly' 意味着 phpScript2 必须循环执行并在 clientScript1 更改值时向 clientScript2 发送消息。
当然有更多不同的方法可以达到相同的结果。
下面是我在上一个项目中使用的暂存器中的一些代码。
大多数部分来自 class(正在开发中),所以我不得不采用相当多的代码。我也试图将它融入您现有的代码中。
希望我没有引入任何错误。
请注意,我没有考虑任何对您价值的验证!此外,代码未调试或优化,因此尚未准备好投入生产。
客户端(发送新值,例如您的代码):
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);
}