PHP7.4中register_shutdown_function与ignore_user_abort失效问题求助
问题
我有一个用于流式输出ping结果的PHP页面,功能正常,但当用户关闭连接后,ping命令仍会在后台持续运行。我已尝试使用ignore_user_abort()和register_shutdown_function()函数,但均未生效。该服务运行在搭载uhttpd的OpenWRT系统中。
类实现代码
class process { private $proc; private $pid; private $descriptorspec; function start($cmd) { $cwd = '/tmp'; $this->proc = proc_open($cmd, $this->descriptorspec, $pipes, $cwd); $this->pid = proc_get_status($this->proc)['pid']; session_write_close(); ignore_user_abort(true); set_time_limit(0); ini_set('zlib.output_compression', 0); ini_set('implicit_flush', 1); while (!feof($pipes[1])) { echo ">" . fread($pipes[1], 4096); flush(); ob_flush(); while (ob_get_level()) { ob_end_clean(); } if (connection_status() != CONNECTION_NORMAL) { shell_exec("killall ping &"); break; } } pclose($this->proc); } function __construct() { register_shutdown_function(array($this, "onShutdown")); $this->descriptorspec = array( 0 => array("pipe", "r"), // stdin是子进程读取的管道 1 => array("pipe", "w"), // stdout是子进程写入的管道 2 => array("file", "/tmp/app/proc.error.output.txt", "a") // stderr写入到文件 ); } function onShutdown() { shell_exec("killall ping &"); } function __destruct() { shell_exec("killall ping"); // posix_kill($this->pid, SIGKILL); // proc_terminate($this->proc); // pclose($this->proc); } }
函数式实现代码
global $proc, $pid; while (@ ob_end_flush()) ; $descriptorspec = array( 0 => array("pipe", "r"), // stdin是子进程读取的管道 1 => array("pipe", "w"), // stdout是子进程写入的管道 2 => array("file", "/tmp/app/proc.error.output.txt", "a") // stderr写入到文件 ); $cwd = '/tmp'; $proc = proc_open($cmd, $descriptorspec, $pipes, $cwd); $pid = proc_get_status($proc)['pid']; session_write_close(); ignore_user_abort(true); set_time_limit(0); register_shutdown_function('on_shutdown'); while (ob_get_level()){ ob_get_contents(); ob_end_clean(); } //ob_start(); while (!feof($pipes[1])) { echo fread($pipes[1], 4096); flush(); ob_flush(); if (connection_aborted() != 0) { posix_kill($pid, SIGKILL); pclose($proc); } } //ob_end_flush(); pclose($proc);
解决方案
问题根源在于uhttpd的特性和PHP连接检测机制的配合问题,以下是针对性修复方案:
关闭
ignore_user_abort(true)
你设置的ignore_user_abort(true)会强制PHP在用户断开连接后继续执行脚本,直接阻止了自动终止逻辑。改为ignore_user_abort(false),让PHP在连接断开时触发终止流程。用
proc_terminate()替代killallkillall ping会杀掉所有ping进程,容易误杀其他用户的任务。直接用proc_terminate()终止当前启动的子进程,配合proc_close()清理资源,精准且安全。优化连接检测逻辑
uhttpd不会实时传递连接断开信号,在循环中加入短延时(如usleep(100000)),给PHP留出检测连接状态的时间。同时确保每次输出后都执行连接检测。彻底清理输出缓冲
残留的输出缓冲会导致PHP无法及时感知连接断开,启动脚本前需彻底清空所有缓冲层。
修改后的类实现示例
class process { private $proc; private $pid; private $descriptorspec; function start($cmd) { $cwd = '/tmp'; $this->proc = proc_open($cmd, $this->descriptorspec, $pipes, $cwd); $this->pid = proc_get_status($this->proc)['pid']; session_write_close(); // 关闭ignore_user_abort,允许PHP在连接断开时终止脚本 ignore_user_abort(false); set_time_limit(0); ini_set('zlib.output_compression', 0); ini_set('implicit_flush', 1); // 彻底清空输出缓冲 while (ob_get_level()) { ob_end_clean(); } while (!feof($pipes[1])) { $output = fread($pipes[1], 4096); if ($output) { echo ">" . $output; flush(); } // 检测连接是否断开 if (connection_aborted()) { proc_terminate($this->proc, SIGKILL); proc_close($this->proc); break; } // 短延时,让PHP有机会检测连接状态 usleep(100000); } // 确保资源被清理 if (is_resource($this->proc)) { proc_close($this->proc); } } function __construct() { register_shutdown_function(array($this, "onShutdown")); $this->descriptorspec = array( 0 => array("pipe", "r"), 1 => array("pipe", "w"), 2 => array("file", "/tmp/app/proc.error.output.txt", "a") ); } function onShutdown() { if (is_resource($this->proc)) { proc_terminate($this->proc, SIGKILL); proc_close($this->proc); } } function __destruct() { $this->onShutdown(); } }
修改后的函数式实现示例
global $proc, $pid; // 彻底清空输出缓冲 while (@ ob_end_flush()) ; $descriptorspec = array( 0 => array("pipe", "r"), 1 => array("pipe", "w"), 2 => array("file", "/tmp/app/proc.error.output.txt", "a") ); $cwd = '/tmp'; $proc = proc_open($cmd, $descriptorspec, $pipes, $cwd); $pid = proc_get_status($proc)['pid']; session_write_close(); // 关闭ignore_user_abort ignore_user_abort(false); set_time_limit(0); register_shutdown_function('on_shutdown'); while (ob_get_level()){ ob_end_clean(); } while (!feof($pipes[1])) { $output = fread($pipes[1], 4096); if ($output) { echo $output; flush(); } if (connection_aborted()) { proc_terminate($proc, SIGKILL); proc_close($proc); break; } usleep(100000); } if (is_resource($proc)) { proc_close($proc); } function on_shutdown() { global $proc; if (is_resource($proc)) { proc_terminate($proc, SIGKILL); proc_close($proc); } }
额外注意事项
- 确保OpenWRT的PHP环境中
output_buffering设置为Off,避免缓冲影响连接检测。 - 测试时用
ps | grep ping验证用户断开连接后ping进程是否被终止。 - 若使用
posix_kill,需确保PHP安装了posix扩展,推荐用proc_terminate兼容性更好。
内容的提问来源于stack exchange,提问作者lordkian
相关产品推荐
相关产品推荐

