我使用 uas 在评论中发送值来实现长轮询并进行断开连接检查
例如对于 JSON
在连接断开之前发送 0 并刷新。
例如:{"x":"000000
结果准备好后,发送 ", 并移除第一个 { 后发送 JSON。
因此它看起来像
{"x":"00000000000","y":"结果","z":"结果2"}
(PHP 4、PHP 5、PHP 7、PHP 8)
connection_aborted — 检查客户端是否断开连接
此函数没有参数。
如果客户端断开连接,则返回 1,否则返回 0。
我使用 uas 在评论中发送值来实现长轮询并进行断开连接检查
例如对于 JSON
在连接断开之前发送 0 并刷新。
例如:{"x":"000000
结果准备好后,发送 ", 并移除第一个 { 后发送 JSON。
因此它看起来像
{"x":"00000000000","y":"结果","z":"结果2"}
一个检测连接是否关闭的技巧,无需发送可能破坏数据流(例如二进制文件)的数据,可以使用 HTTP/1.1 上的数据分块组合,发送一个 "0"(零)作为前导块大小,不包含其他内容。
*注意* 重要的是要注意,每隔几秒检查一次流并不是一个好主意。通过这样做,您可能会在没有给用户带来任何好处的情况下增加发送给用户的数据。
这样做的好处是,如果您正在生成一个需要很长时间才能运行且占用大量服务器资源的报告。这将允许服务器检测到用户是否取消了下载并进行任何清理,而不会破坏正在下载的文件。
以下是一个示例
<?php
ignore_user_abort(true);
header('Transfer-Encoding:chunked');
ob_flush();
flush();
$start = microtime(true);
$i = 0;
// 使用此函数将任何内容回显到浏览器。
function vPrint($data){
if(strlen($data))
echo dechex(strlen($data)), "\r\n", $data, "\r\n";
ob_flush();
flush();
}
// 您必须在完成向浏览器流式传输信息后执行此函数。
function endPacket(){
echo "0\r\n\r\n";
ob_flush();
flush();
}
do{
echo "0";
ob_flush();
flush();
if(connection_aborted()){
// 当连接关闭时会发生这种情况
file_put_contents('/tmp/test.tmp', sprintf("Conn Closed\nTime spent with connection open: %01.5f sec\nLoop itterations: %s\n\n", microtime(true) - $start, $i), FILE_APPEND);
endPacket();
exit;
}
usleep(50000);
vPrint("我每迭代(每 0.5 秒)都会被回显<br />\n");
}while($i++ < 200);
endPacket();
?>
为了在脚本中检测断开连接,我们需要刷新缓冲区(只有当服务器尝试发送缓冲区内容时,它才会发现连接已断开)。
因此,我们需要使用 ob_implicit_flush() 函数自动刷新缓冲区。
这是一个断开连接检测的示例
<?php
// 启用自动刷新
ob_implicit_flush();
function verifyCommunication() {
$status = (connection_aborted()?"not ":"") ."sent";
persistCommunication(new Communication($status));
}
// 当脚本完成时,我们将检查通信状态
register_shutdown_function(verifyCommunication);
echo "blabla";
// 此 sleep 用于在客户端侧断开连接时留出时间
sleep(10);
echo"tata";
?>
我使用此过程来取消请求,如果客户端没有收到确认,因为他们会重新发出请求...
即使使用 echo、flush() 和其他缓冲区操作技巧,我也无法从这里获得准确的结果。以下函数在 Linux 中从 OS netstat(1) 检测连接状态,即使没有回显任何内容。您可能需要更改 Windows 或旧版 netstat 的解析。它调用外部命令,因此会消耗一些资源,谨慎使用,例如,您可以在等待长轮询请求时每隔几秒调用一次。未在 ipv6 上测试。
// 返回连接状态 (ESTABLISHED、TIME_WAIT 等) 或 NOT_FOUND
function getConnectionStatus() {
$remote_ip = $_SERVER['REMOTE_ADDR']?:($_SERVER['HTTP_X_FORWARDED_FOR']?:$_SERVER['HTTP_CLIENT_IP']);
$remote_port=$_SERVER['REMOTE_PORT'];
$cmd="netstat -tn | fgrep ' $remote_ip:$remote_port '";
$pfp=popen($cmd,"r");
if(!$pfp) {
error_log ("getConnectionStatus: $cmd");
}
$buf = fgets($pfp, 1024);
pclose($pfp);
$buf=preg_replace('!\s+!', ' ', $buf); // 移除多个空格
$buf=trim($buf);
$buf_r=explode(" ",$buf);
if (count($buf_r)) {
$state=$buf_r[count($buf_r)-1];
return trim($state);
}
return "NOTFOUND";
}
// 检查它
if (getConnectionStatus() != "ESTABLISHED") { ...}
来自 ignore_user_abort 函数
PHP 不会检测到用户已中止连接,直到尝试向客户端发送信息。简单地使用 echo 语句不能保证信息已发送,请参见 flush()。
此规则似乎也适用于此处。
声明:我是一个新手。
我想要的操作是在浏览器或标签页关闭时触发,而不是网络连接断开时。通过禁用 apache2 的 mod_deflate 模块解决了这个问题。其他我更改的配置设置也可能很重要(超时?)。因此,在我的 page.html 页面底部有以下代码:
<script>
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
}
};
xhttp.open("GET", "Caller.php", true);
xhttp.send();
</script>
然后,在 Caller.php 中,我有以下代码:
<?php
ignore_user_abort(TRUE);
Header('Location: monitor.php');
?>
在 monitor.php 中,我有以下代码:
<?php
ignore_user_abort(TRUE);
set_time_limit(0);
echo connection_status();
while(1)
{
echo "something - anything - to go here - need a good few lines of it";
ob_flush();
flush();
sleep(1);
if(connection_status()!=0)
{
break;
}
}
shell_exec("DESIRED ACTION HERE");
?>
虽然文档表明它返回一个整数,但我发现将返回值与数字值进行比较似乎不起作用。
示例(不起作用)
<?php
if (connection_aborted()==1) {
fwrite($filehandle, 'aborted!');
}
?>
最好假设它返回布尔值
示例(起作用)
<?php
if (connection_aborted()) {
fwrite($filehandle, 'aborted!');
}
?>
(connection_aborted 不起作用)
几年前我遇到了这个问题,现在在升级 php 后问题又出现了... 我尝试了所有我找到的方法,最后在 php bug 跟踪中发布的脚本顶部使用函数 ob_end_flush(); 解决了问题。我使用的是 Windows 7 x64 / php 5.2.4 / apache 2.2.14 (win32)
尝试在脚本顶部添加 ob_end_flush();
我读过一些关于这个的内容,但我记不起在哪里了,它与一个 bug 或其他东西有关。
希望这有帮助。