连接处理

在 PHP 内部,维护着连接状态。共有 4 种可能的状态

  • 0 - NORMAL
  • 1 - ABORTED
  • 2 - TIMEOUT
  • 3 - ABORTED 和 TIMEOUT

当 PHP 脚本正常运行时,NORMAL 状态处于活动状态。如果远程客户端断开连接,则 ABORTED 状态标志将被打开。远程客户端断开连接通常是由用户点击其 STOP 按钮造成的。如果 PHP 强制的时间限制(参见 set_time_limit())已到期,则 TIMEOUT 状态标志将被打开。

您可以决定是否希望客户端断开连接导致您的脚本中止。有时,即使没有远程浏览器接收输出,也方便始终让您的脚本运行到完成。但是,默认行为是在远程客户端断开连接时中止您的脚本。此行为可以通过 php.ini 指令中的 ignore_user_abort 设置,也可以通过相应的 php_value ignore_user_abort Apache httpd.conf 指令或使用 ignore_user_abort() 函数设置。如果您没有告诉 PHP 忽略用户中止,并且用户中止了,您的脚本将终止。唯一的例外是,如果您已使用 register_shutdown_function() 注册了关闭函数。使用关闭函数,当远程用户点击他的 STOP 按钮时,您的脚本下次尝试输出内容时,PHP 将检测到连接已中止,并且将调用关闭函数。此关闭函数还将在您的脚本正常终止时被调用,因此,要对客户端断开连接的情况进行不同的处理,您可以使用 connection_aborted() 函数。如果连接已中止,此函数将返回 true

您的脚本还可以由内置脚本计时器终止。默认超时时间为 30 秒。可以使用 max_execution_time php.ini 指令或相应的 php_value max_execution_time Apache httpd.conf 指令以及 set_time_limit() 函数进行更改。计时器到期后,脚本将中止,与上面的客户端断开连接的情况一样,如果已注册了关闭函数,则将调用该函数。在该关闭函数中,您可以通过调用 connection_status() 函数来检查是否由超时导致调用了关闭函数。如果由超时导致调用了关闭函数,此函数将返回 2。

需要注意的是,ABORTED 和 TIMEOUT 状态可以同时处于活动状态。如果您告诉 PHP 忽略用户中止,则这种情况是可能的。PHP 仍然会记录用户可能已断开连接的事实,但脚本将继续运行。如果随后它达到时间限制,它将中止,并且您的关闭函数(如果有)将被调用。此时,您会发现 connection_status() 返回 3。

添加注释

用户贡献注释 12 个注释

tom lgold2003 at gmail dot com
14 年前
嘿,感谢 arr1,这对我很有用,当我需要快速返回给用户然后做其他事情时。

使用这些代码时,它几乎让我抓狂,我发现了可能影响这些代码的另一件事

Content-Encoding: gzip

这是因为 zlib 处于开启状态,并且内容将被压缩。但这不会输出缓冲区,直到所有输出完成。

因此,可能需要发送标头来防止此问题。

现在,代码变为

<?php
ob_end_clean
();
header("Connection: close\r\n");
header("Content-Encoding: none\r\n");
ignore_user_abort(true); // 可选
ob_start();
echo (
'Text user will see');
$size = ob_get_length();
header("Content-Length: $size");
ob_end_flush(); // 奇特的行为,不会起作用
flush(); // 除非两者都被调用!
ob_end_clean();

// 在这里进行处理
sleep(5);

echo(
'Text user will never see');
// 进行一些处理
?>
arr1 at hotmail dot co dot uk
17 年前
在保持 PHP 脚本运行的同时关闭用户浏览器连接一直是一个问题,从 4.1 版本开始,register_shutdown_function() 的行为被修改,因此它不会自动关闭用户的连接。

sts at mail dot xubion dot hu
发布了原始解决方案

<?php
header
("Connection: close");
ob_start();
phpinfo();
$size=ob_get_length();
header("Content-Length: $size");
ob_end_flush();
flush();
sleep(13);
error_log("do something in the background");
?>

这可以正常工作,直到您用
echo ('text I want user to see'); 代替 phpinfo(),在这种情况下,标头永远不会被发送!

解决方案是在发送标头信息之前明确关闭输出缓冲并清除缓冲区。

示例

<?php
ob_end_clean
();
header("Connection: close");
ignore_user_abort(); // 可选
ob_start();
echo (
'Text the user will see');
$size = ob_get_length();
header("Content-Length: $size");
ob_end_flush(); // 奇特的行为,不会起作用
flush(); // 除非两者都被调用!
// 在这里进行处理
sleep(30);
echo(
'Text user will never see');
?>

我花了 3 个小时才弄明白这一点,希望它能帮助到其他人 :)

已在以下环境中测试
IE 7.5730.11
Mozilla Firefox 1.81
Lee
19 年前
上一条评论中提到的情况并不总是如此。

如果用户的连接在订单处理脚本(正在确认用户的信用卡/将他们添加到数据库等)的中间丢失(由于他们的 ISP 故障、网络故障……无论什么),并且您的脚本尝试发送回输出(例如,“正在预处理订单”或任何其他类型的确认),那么您的脚本将中止,这可能会导致您的处理出现问题。

我有一个订单脚本,它将数据添加到 InnoDB 数据库(通过 MySQL),并且仅在成功完成时才提交事务。如果没有 ignore_user_abort(),我有时会在处理阶段用户的连接断开时遇到问题……他们的卡被扣款,但他们没有添加到我的本地数据库。

因此,如果您正在处理应该继续进行的敏感事务,无论用户是否“在另一端观看”,忽略任何中止都是安全的。
mheumann at comciencia dot cl
11 年前
我遇到了很多问题,无法让重定向正常工作,之后我的脚本打算在后台继续工作。重定向到我网站的另一个页面只会在我原始页面完成处理后才能正常工作。

我终于发现了问题所在
会话只有在脚本的最后才会被 PHP 关闭,并且由于对会话数据的访问被锁定以防止多个页面同时写入它,因此新页面必须等到原始处理完成后才能加载。

解决方案
使用 session_write_close() 在重定向时手动关闭会话。

<?php
ignore_user_abort
(true);
set_time_limit(0);

$strURL = "将您的重定向地址放在这里";
header("Location: $strURL", true);
header("Connection: close", true);
header("Content-Encoding: none\r\n");
header("Content-Length: 0", true);

flush();
ob_flush();

session_write_close();

// 继续处理...

sleep(100);
exit;
?>

但请注意
确保您的脚本在 session_write_close() 之后不会写入会话,例如,在您的后台处理代码中。这将不起作用。另外请避免读取,记住,下一个脚本可能已经修改了数据。

因此,请尝试在重定向之前读取所需数据。
a1n2ton at gmail dot com
14 年前
PHP 在连接中止时会更改目录,因此像这样的代码将不会按照您的预期执行

<?php
function abort()
{
if(
connection_aborted())
unlink('file.ini');
}
register_shutdown_function('abort');
?>

实际上,它会删除 Apache 的根目录中的文件,因此,如果您想在中止时解除链接脚本目录中的文件或写入该文件,您需要存储目录。
<?php
function abort()
{
global
$dsd;
if(
connection_aborted())
unlink($dsd.'/file.ini');
}
register_shutdown_function('abort');
$dsd=getcwd();
?>
Marco
7 年前
由于某种原因,这里没有列出的 CONNECTION_XXX 常量是

0 = CONNECTION_NORMAL
1 = CONNECTION_ABORTED
2 = CONNECTION_TIMEOUT
3 = CONNECTION_ABORTED & CONNECTION_TIMEOUT

数字 3 实际上是这样测试的
if (CONNECTION_ABORTED & CONNECTION_TIMEOUT)
echo '连接已中止并且超时';
Ilya Penyaev
11 年前
当我尝试让我的脚本将客户端重定向到另一个 URL 然后继续处理时,我遇到了很大的麻烦。原因是 php-fpm。所有可能的缓冲区刷新都不起作用,除非我调用 fastcgi_finish_request();

例如

<?php
// 重定向...
ignore_user_abort(true);
header("Location: ".$redirectUrl, true);
header("Connection: close", true);
header("Content-Length: 0", true);
ob_end_flush();
flush();
fastcgi_finish_request(); // 使用 php-fpm 时很重要!

sleep (5); // 用户不会感觉到这种睡眠,因为他已经离开了

// 在用户重定向后执行一些工作
?>
Anonymous
16 年前
关于从
arr1 at hotmail dot co dot uk

发布
如果您使用/写入会话,则需要在

(否则它将不起作用)

session_write_close();

之前执行此操作
如果需要
ignore_user_abort(TRUE);
而不是 ignore_user_abort();
Anonymous

12 年前

此简单函数输出字符串并关闭连接。它考虑使用“ob_gzhandler”进行压缩
我花了一点时间将这一切整合在一起,主要是因为将编码设置为 none(如某些人在此处指出的那样)不起作用。
<?php
function outputStringAndCloseConnection2($stringToOutput)
{
set_time_limit(0);
ignore_user_abort(true);
// 缓冲所有即将输出的内容 - 确保我们关心压缩:
if(!ob_start("ob_gzhandler"))
ob_start();
echo
$stringToOutput;
// 获取输出的大小
$size = ob_get_length();
// 发送标头以告诉浏览器关闭连接
header("Content-Length: $size");
header('Connection: close');
// 刷新所有输出
ob_end_flush();
ob_flush();
flush();
// 关闭当前会话
if (session_id()) session_write_close();
}
?>
pulstar at mail dot com
21 年前
这些函数非常有用,例如,如果您需要控制网站访问者何时下订单,并且您需要检查访问者是否没有两次点击提交按钮,或者在点击提交按钮后立即取消提交。
如果您的访问者在提交后点击停止按钮,您的脚本可能会在注册产品的过程中停止,并且不会完成列表,从而导致数据库中的不一致。
使用 ignore_user_abort() 函数,您可以让您的脚本正常完成所有操作,然后您可以使用 register_shutdown_function() 和 connection_aborted() 检查访问者是否取消了提交或断开了连接。如果访问者确实取消了提交或断开了连接,您可以将订单设置为未确认,当访问者返回时,您可以再次显示旧订单。
为了防止两次点击提交按钮,您可以使用 JavaScript 禁用它,或者在您的脚本中,您可以为该订单设置一个标志,该标志将被记录到数据库中。在接受新的提交之前,脚本将检查之前是否已放置了相同的订单,如果存在,则拒绝提交。这将正常工作,因为脚本在完成工作之前已经完成了。
16 年前
请注意,如果您在脚本开头使用 ob_start("callback_function"),则可以指定一个回调函数,该函数在脚本结束时将起作用,还可以让您在将页面发送给访问者之前处理生成的页面。
Jean Charles MAMMANA

connection_status() 函数仅在客户端优雅地断开连接(使用停止按钮)时返回 ABORTED 状态。在这种情况下,浏览器将发送 RST TCP 数据包,通知 PHP 连接已关闭。

但是……如果连接因网络故障而停止(例如,WiFi 链接断开),脚本将不知道客户端已断开连接 :(
我尝试使用 fopen("php://output") 和 stream_select() 在写入时检测写入锁(由于缓冲区已满),但 PHP 给出了以下错误:“无法将类型为 Output 的流表示为可选择的描述符”。
因此,我不知道如何正确检测网络故障连接……
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
/*
robert at go dot rw
*/

8 年前
* 反模式

# 连接
mysql_connect('localhost', 'username', 'password') or die('无法连接:' . mysql_error());

# 选择数据库
mysql_select_db('someDatabase') or die('无法选择数据库');
# 执行数据库查询

$query = "SELECT * from someTable";
$result = mysql_query($query) or die('查询失败:' . mysql_error());
# 筛选行并回显所需信息
}
To Top