PHP 使用 nohup 等效触发后台进程



我一直在浏览各种讨论和文章,介绍如何让PHP使用Windows等效的nohup在后台运行进程;我的代码需要同时使用Unix和Windows,但我现在专注于Windows。

我的问题是,所有的例子似乎都建议使用execpclose/popen系统,但是当我尝试它们时,我看到后台进程在调用时正在运行,但它似乎会阻止调用者直到它完成......

。我正在寻找的是调用方快速完成,同时后台进程继续执行它需要做的事情(在这种情况下是几秒钟的处理,但在最终版本中可能是几分钟)。

我提供了一个示例程序,如果您将 [在脚本底部找到的] $option更改为 210、220、230 或 240,那么您可以看到结果。在 $this->logfn 设置的日志文件中查看完整结果。

<?php
error_reporting(E_ALL);
ini_set('display_errors', TRUE);
ini_set('display_startup_errors', TRUE);
class Test  {
var $logfn="C:/test.log";
// ------------------------------------------------
public function doTrace(string $msgTx) {
$now = date('Y-m-d H:i:s', gmdate(time()));
error_log($now."::".$msgTx."n", 3, $this->logfn);
echo "<LI>$now:$msgTx";
}
// ------------------------------------------------
public function doTest(int $option, array $args = null) {
$this->doTrace("0 ".php_sapi_name());
if ( strcmp(php_sapi_name(), "cli") == 0 ) {
// Command line execution
if ( isset($args) and count($args) > 0 ) $x = $args[1];
else $x="-";
$this->doTrace("   900---------------- background cli starting ($x)");
sleep(1);
$this->doTrace("   901---------------- tick");
sleep(1);
$this->doTrace("   902---------------- tick");
sleep(1);
$this->doTrace("   903---------------- tick");
sleep(1);
$this->doTrace("   904---------------- tick");
sleep(1);
$this->doTrace("   905---------------- tick");
$this->doTrace("   910 background sleep complete");
}
else {
// Web execution
unlink($this->logfn);
$this->doTrace("100---------------- www starting option $option - " . __FILE__);
$script = __FILE__;
if ( !file_exists($script) ) echo "<LI>$script NOT FOUND</LI>";
$php = "php ";
$cmd = "-f $script ARG1";

$this->doTrace("200 Starting background process...");
switch($option){
case 210:$this->execInBackground210($php, $cmd); break;
case 220:$this->execInBackground220($php, $cmd); break;
case 230:$this->execInBackground230($php, $cmd); break;
case 240:$this->execInBackground240($php, $cmd); break;
}
$this->doTrace("300 cmd ran in background");
}
}
// ------------------------------------------------
// exec
public function execInBackground210($php, $cmd) {
if ( substr(php_uname(), 0, 7) == "Windows" ) {
$this->doTrace("210a - Spawning $php $cmd");
exec("start /B $php $cmd", $output, $return);
echo "<HR>nn";
echo "<UL>";
echo "<LI>o:" . nl2br(print_r($output, true)) . "</LI>";
echo "<LI>r:$return</LI>";
echo "</UL>";
$this->doTrace("210b - Spawn finished");
}
else {
exec("nohup $cmd > /dev/null &");
}
}
// ------------------------------------------------
// popen
public function execInBackground220($php, $cmd) {
if ( substr(php_uname(), 0, 7) == "Windows" ) {
echo "<LI>$cmd";
$this->doTrace("220a - Spawning $php $cmd");
pclose(popen("start /B " . "$php $cmd", "r"));
$this->doTrace("220b - Spawn finished");
}
else {
exec("$php $cmd" . " > /dev/null &");
}
}
// -------------------------------------------------------------------------------------------------------
// COM/run
public function execInBackground230($php, $cmd) {
if ( substr(php_uname(), 0, 7) == "Windows" ) {
$this->doTrace("230a - Spawning $php $cmd");
$shell = new COM("WScript.Shell");
$shell->run("$php $cmd", 0, false);
$this->doTrace("230b - Spawn finished");
}
else {
exec($cmd . " > /dev/null &");
}
}
// -------------------------------------------------------------------------------------------------------
// system
public function execInBackground240($php, $cmd) {
if ( substr(php_uname(), 0, 7) == "Windows" ) {
$this->doTrace("240a - Spawning $php $cmd");
system("start /B $php $cmd"." 2>&1", $return);
$this->doTrace("240b - Spawn done [$return]");
}
else {
exec($cmd . " > /dev/null &");
}
}
}
//$option=210;
//$option=220;
//$option=230;
$option=240;
$try = new Test();
echo "<html>";
echo "<body>";
if ( isset($argv) ) {
echo $try->doTest($option,$argv);
}
else {
echo $try->doTest($option,null);
}
echo "</body>";
echo "</html>";
?>

我希望在日志文件中看到 900 系列日志在其他日志(来自网页调用)完成后运行,但我看到的是网页在将控制权传递回前端之前等待后台进程完成 - 导致 900 系列日志位于日志文件的中间而不是末尾。

日志文件当前读取如下(运行时 $option=240)

2019-03-31 16:53:37::100---------------- www starting option 240 - C:.....testSpawn.php
2019-03-31 16:53:37::200 Starting background process...
2019-03-31 16:53:37::240a - Spawning php  -f C:.... testSpawn.php ARG1
2019-03-31 16:53:37::0 cli
2019-03-31 16:53:37::   900---------------- background cli starting (ARG1)
2019-03-31 16:53:38::   901---------------- tick
2019-03-31 16:53:39::   902---------------- tick
2019-03-31 16:53:40::   903---------------- tick
2019-03-31 16:53:42::   904---------------- tick
2019-03-31 16:53:43::   905---------------- tick
2019-03-31 16:53:43::   910 background sleep complete
2019-03-31 16:53:43::240b - Spawn done [0]
2019-03-31 16:53:43::300 cmd ran in background

那么,要使Windows PHP环境在后台异步运行生成的进程,需要做些什么呢?

(我在PHP5.7上)

start /B很好。但是,通常php不在Windows上的%PATH%中。您应该确定完整路径。尝试PHP_BINARY常量,该常量应包含 PHP 可执行文件的绝对路径。

当您想要读取进程的stdout流时,可以使其非阻塞:

$cmd = PHP_BINARY;
if(is_resource($proc = proc_open("start /B $cmd script.php", [['pipe', 'r'], ['pipe', 'w'], ['pipe', 'w']], $pipes)))
{
echo '<h1>Process Stream</h1>', PHP_EOL;
stream_set_blocking($pipes[1], false);
stream_set_blocking($pipes[2], false);

然后,您无需等待即可访问流EOF/EOL.

最新更新