php执行后台进程
Posted
技术标签:
【中文标题】php执行后台进程【英文标题】:php execute a background process 【发布时间】:2010-09-07 22:32:48 【问题描述】:我需要在用户操作时执行目录复制,但目录非常大,所以我希望能够在用户不知道完成复制所需时间的情况下执行这样的操作。
任何建议将不胜感激。
【问题讨论】:
这个***.com/questions/5367261/…解释了如何在windows下做到这一点 【参考方案1】:假设这是在 Linux 机器上运行,我总是这样处理它:
exec(sprintf("%s > %s 2>&1 & echo $! >> %s", $cmd, $outputfile, $pidfile));
这会启动命令$cmd
,将命令输出重定向到$outputfile
,并将进程ID 写入$pidfile
。
这让您可以轻松监控进程正在做什么以及它是否仍在运行。
function isRunning($pid)
try
$result = shell_exec(sprintf("ps %d", $pid));
if( count(preg_split("/\n/", $result)) > 2)
return true;
catch(Exception $e)
return false;
【讨论】:
sudo setup 是否可以在不提示输入密码的情况下运行?任何需要用户输入的命令都不起作用。 这个***.com/questions/5367261/…解释了如何在windows下做同样的事情 我使用了这个,但稍微更新了一下,所以我不必将 PID 写入文件。所以我使用这种格式: exec(sprintf("$s > $s 2>&1 & echo $1", $cmd, $outputfile),$pidArr);
生成的进程 PID 在 $pidArr[0]
@Kaiesh:应该是“echo $!”
Kaiesh 犯了另一个错字,'$' 而不是 '%'。更正版本: exec(sprintf("%s > %s 2>&1 & echo $!", $cmd, $outputfile),$pidArr)【参考方案2】:
用任何方便的语言 (php/bash/perl/etc) 将进程编写为服务器端脚本,然后从 php 脚本中的进程控制函数调用它。
该函数可能会检测是否使用标准 io 作为输出流,如果是,则设置返回值。如果不是,则结束
proc_close( proc_open( "./command --foo=1 &", array(), $foo ) );
我使用“sleep 25s”作为命令从命令行快速测试了它,它就像一个魅力。
(Answer found here)
【讨论】:
这是我可以让它在 centos 上工作的唯一方法。谢谢! 谢谢! PHP 和 BASH 不兼容。如果您运行较新的系统,它将无法使用 system() 或 exec() 在后台启动。即使您重定向,文件描述符似乎也会卡住。你的方法奏效了。另一种选择是为 PHP 使用旧的 bash 二进制文件,但这太疯狂了【参考方案3】:您可能想尝试将其附加到您的命令中
>/dev/null 2>/dev/null &
例如。
shell_exec('service named reload >/dev/null 2>/dev/null &');
【讨论】:
我的意思是 >/dev/null 2>/dev/null & 它对我有用,我将使用makefile在服务器站点做一些事情,谢谢! (例如 make、make 重启) 这比公认的答案要简单得多! (只要你不需要更复杂的东西,比如检查进程是否仍在运行。)【参考方案4】:我只想添加一个非常简单的示例来在 Windows 上测试此功能:
创建以下两个文件并将它们保存到 web 目录:
foreground.php:
<?php
ini_set("display_errors",1);
error_reporting(E_ALL);
echo "<pre>loading page</pre>";
function run_background_process()
file_put_contents("testprocesses.php","foreground start time = " . time() . "\n");
echo "<pre> foreground start time = " . time() . "</pre>";
// output from the command must be redirected to a file or another output stream
// http://ca.php.net/manual/en/function.exec.php
exec("php background.php > testoutput.php 2>&1 & echo $!", $output);
echo "<pre> foreground end time = " . time() . "</pre>";
file_put_contents("testprocesses.php","foreground end time = " . time() . "\n", FILE_APPEND);
return $output;
echo "<pre>calling run_background_process</pre>";
$output = run_background_process();
echo "<pre>output = "; print_r($output); echo "</pre>";
echo "<pre>end of page</pre>";
?>
背景.php:
<?
file_put_contents("testprocesses.php","background start time = " . time() . "\n", FILE_APPEND);
sleep(10);
file_put_contents("testprocesses.php","background end time = " . time() . "\n", FILE_APPEND);
?>
授予 IUSR 写入您创建上述文件的目录的权限
授予 IUSR 读取和执行 C:\Windows\System32\cmd.exe 的权限
从网络浏览器点击foreground.php
以下内容应在输出数组中使用当前时间戳和本地资源# 呈现给浏览器:
loading page
calling run_background_process
foreground start time = 1266003600
foreground end time = 1266003600
output = Array
(
[0] => 15010
)
end of page
你应该会看到 testoutput.php 与上述文件保存在同一目录中,并且应该是空的
您应该会在保存上述文件的同一目录中看到 testprocesses.php,并且它应该包含以下带有当前时间戳的文本:
foreground start time = 1266003600
foreground end time = 1266003600
background start time = 1266003600
background end time = 1266003610
【讨论】:
【参考方案5】:如果您只需要在后台执行某项操作,而不需要 PHP 页面等待它完成,您可以使用另一个(后台)PHP 脚本,该脚本通过 wget 命令“调用”。当然,这个后台 PHP 脚本将像您系统上的任何其他 PHP 脚本一样以特权执行。
这是一个在 Windows 上使用 gnuwin32 包中的 wget 的示例。
以后台代码(文件test-proc-bg.php)为例...
sleep(5); // some delay
file_put_contents('test.txt', date('Y-m-d/H:i:s.u')); // writes time in a file
前台脚本,调用 ...
$proc_command = "wget.exe http://localhost/test-proc-bg.php -q -O - -b";
$proc = popen($proc_command, "r");
pclose($proc);
您必须使用 popen/pclose 才能正常工作。
wget 选项:
-q keeps wget quiet.
-O - outputs to stdout.
-b works on background
【讨论】:
【参考方案6】:我发现了一个更快更容易使用的版本
shell_exec('screen -dmS $name_of_screen $command');
它有效。
【讨论】:
@Alpha.Dev 我只在linux上测试过我不知道它是否可以在另一个系统上工作。【参考方案7】:这是一个在 PHP 中启动后台进程的函数。经过大量阅读和测试不同的方法和参数后,终于创建了一个也可以在 Windows 上实际运行的软件。
function LaunchBackgroundProcess($command)
// Run command Asynchroniously (in a separate thread)
if(PHP_OS=='WINNT' || PHP_OS=='WIN32' || PHP_OS=='Windows')
// Windows
$command = 'start "" '. $command;
else
// Linux/UNIX
$command = $command .' /dev/null &';
$handle = popen($command, 'r');
if($handle!==false)
pclose($handle);
return true;
else
return false;
注意 1:在 Windows 上,不要像其他地方建议的那样使用 /B
参数。它强制进程运行与start
命令本身相同的控制台窗口,从而同步处理进程。要在单独的线程中(异步)运行进程,请不要使用/B
。
注意 2:如果命令是带引号的路径,则start ""
后面的空双引号是必需的。 start
命令将第一个引用的参数解释为窗口标题。
【讨论】:
【参考方案8】:您能否安排分叉一个单独的进程,然后在后台运行您的副本?自从我做任何 PHP 以来已经有一段时间了,但函数 pcntl-fork 看起来很有希望。
【讨论】:
这没有提供问题的答案。要批评或要求作者澄清,请在其帖子下方发表评论。 谢谢 - 08 年没有评论,但我会记住这一点 :)【参考方案9】:使用此功能在后台运行您的程序。它跨平台且完全可定制。
<?php
function startBackgroundProcess(
$command,
$stdin = null,
$redirectStdout = null,
$redirectStderr = null,
$cwd = null,
$env = null,
$other_options = null
)
$descriptorspec = array(
1 => is_string($redirectStdout) ? array('file', $redirectStdout, 'w') : array('pipe', 'w'),
2 => is_string($redirectStderr) ? array('file', $redirectStderr, 'w') : array('pipe', 'w'),
);
if (is_string($stdin))
$descriptorspec[0] = array('pipe', 'r');
$proc = proc_open($command, $descriptorspec, $pipes, $cwd, $env, $other_options);
if (!is_resource($proc))
throw new \Exception("Failed to start background process by command: $command");
if (is_string($stdin))
fwrite($pipes[0], $stdin);
fclose($pipes[0]);
if (!is_string($redirectStdout))
fclose($pipes[1]);
if (!is_string($redirectStderr))
fclose($pipes[2]);
return $proc;
注意,命令启动后,该函数默认关闭正在运行的进程的标准输入和标准输出。您可以通过 $redirectStdout 和 $redirectStderr 参数将进程输出重定向到某个文件。
Windows 用户注意事项:
您不能通过以下方式将 stdout/stderr 重定向到 nul
:
startBackgroundProcess('ping yandex.com', null, 'nul', 'nul');
但是,您可以这样做:
startBackgroundProcess('ping yandex.com >nul 2>&1');
*nix 用户注意事项:
1) 如果要获取实际的 PID,请使用 exec shell 命令:
$proc = startBackgroundProcess('exec ping yandex.com -c 15', null, '/dev/null', '/dev/null');
print_r(proc_get_status($proc));
2) 如果您想将一些数据传递给程序的输入,请使用 $stdin 参数:
startBackgroundProcess('cat > input.txt', "Hello world!\n");
【讨论】:
【参考方案10】:您可以尝试像Resque 这样的排队系统。然后,您可以生成一个作业,处理信息并使用“处理”图像快速返回。使用这种方法,您将不知道它何时完成。
此解决方案适用于大型应用程序,您不希望前端机器承担繁重的工作,因此它们可以处理用户请求。 因此,它可能适用于文件和文件夹等物理数据,也可能不适用,但对于处理更复杂的逻辑或其他异步任务(即新的注册邮件)来说,它是很好的,并且非常具有可扩展性。
【讨论】:
【参考方案11】:适用于 Windows 和 Linux 的有效解决方案。在My github page 上查找更多信息。
function run_process($cmd,$outputFile = '/dev/null', $append = false)
$pid=0;
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') //'This is a server using Windows!';
$cmd = 'wmic process call create "'.$cmd.'" | find "ProcessId"';
$handle = popen("start /B ". $cmd, "r");
$read = fread($handle, 200); //Read the output
$pid=substr($read,strpos($read,'=')+1);
$pid=substr($pid,0,strpos($pid,';') );
$pid = (int)$pid;
pclose($handle); //Close
else
$pid = (int)shell_exec(sprintf('%s %s %s 2>&1 & echo $!', $cmd, ($append) ? '>>' : '>', $outputFile));
return $pid;
function is_process_running($pid)
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') //'This is a server using Windows!';
//tasklist /FI "PID eq 6480"
$result = shell_exec('tasklist /FI "PID eq '.$pid.'"' );
if (count(preg_split("/\n/", $result)) > 0 && !preg_match('/No tasks/', $result))
return true;
else
$result = shell_exec(sprintf('ps %d 2>&1', $pid));
if (count(preg_split("/\n/", $result)) > 2 && !preg_match('/ERROR: Process ID out of range/', $result))
return true;
return false;
function stop_process($pid)
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') //'This is a server using Windows!';
$result = shell_exec('taskkill /PID '.$pid );
if (count(preg_split("/\n/", $result)) > 0 && !preg_match('/No tasks/', $result))
return true;
else
$result = shell_exec(sprintf('kill %d 2>&1', $pid));
if (!preg_match('/No such process/', $result))
return true;
【讨论】:
【参考方案12】:感谢this answer:运行后台进程的完美工具是Symfony Process Component,它基于proc_*
函数,但更易于使用。请参阅its documentation 了解更多信息。
【讨论】:
【参考方案13】:与其启动后台进程,不如创建一个触发器文件并让 cron 或 autosys 等调度程序定期执行查找触发器文件并对其执行操作的脚本?触发器可以包含指令甚至原始命令(更好的是,只需将其设为 shell 脚本即可)。
【讨论】:
【参考方案14】:如果使用 PHP,则使用 pcntl_fork 可以更简单地执行此操作:
http://www.php.net/manual/en/function.pcntl-fork.php
【讨论】:
【参考方案15】:我大量使用fast_cgi_finish_request()
结合闭包和 register_shutdown_function()
$message ='job executed';
$backgroundJob = function() use ($message)
//do some work here
echo $message;
然后注册这个闭包在关闭之前执行。
register_shutdown_function($backgroundJob);
最后,当响应发送到客户端时,您可以关闭与客户端的连接并继续使用 PHP 进程:
fast_cgi_finish_request();
闭包将在 fast_cgi_finish_request 之后执行。
$message 在任何时候都将不可见。您可以根据需要注册尽可能多的闭包,但要注意脚本执行时间。 这仅在 PHP 作为快速 CGI 模块运行时才有效(对吗?!)
【讨论】:
【参考方案16】:如果您希望通过 PHP 执行后台进程,请将命令的输出通过管道传输到 /dev/null
并将 &
添加到命令末尾。
exec("bg_process > /dev/null &");
请注意,您可以不使用exec()
的$output
参数,否则PHP 将挂起(可能直到进程完成)。
【讨论】:
【参考方案17】:PHP 脚本不同于其他桌面应用程序开发语言。在桌面应用程序语言中,我们可以设置守护线程来运行后台进程,但在 PHP 中,当用户请求页面时会发生进程。但是,可以使用 php 脚本运行的服务器的 cron 作业功能设置后台作业。
【讨论】:
【参考方案18】:对于我们这些使用 Windows 的人来说,看看这个:
参考:http://php.net/manual/en/function.exec.php#43917
我也为让程序在后台运行而苦苦挣扎 Windows 同时脚本继续执行。这种方法不同于 其他解决方案允许您以最小化、最大化、 或者根本没有窗户。 llbra@phpbrasil 的解决方案确实有效,但它 有时会在桌面上产生一个不需要的窗口 希望任务隐藏运行。
在后台最小化启动Notepad.exe:
<?php
$WshShell = new COM("WScript.Shell");
$oExec = $WshShell->Run("notepad.exe", 7, false);
?>
启动一个后台不可见的shell命令:
<?php
$WshShell = new COM("WScript.Shell");
$oExec = $WshShell->Run("cmd /C dir /S %windir%", 0, false);
?>
最大化启动 MSPaint 并等待您将其关闭,然后再继续执行脚本:
<?php
$WshShell = new COM("WScript.Shell");
$oExec = $WshShell->Run("mspaint.exe", 3, true);
?>
有关 Run() 方法的更多信息,请访问: http://msdn.microsoft.com/library/en-us/script56/html/wsMthRun.asp
修改后的网址:
请转至https://technet.microsoft.com/en-us/library/ee156605.aspx,因为上面的链接已不存在。
【讨论】:
运行这段代码需要做什么?我得到Fatal error: Class 'COM' not found
附言。与 MSDN 的链接已损坏
@Alph.Dev:msdn 文档似乎已被暂时删除。它包含更多关于如何使用 WScript.Shell 的示例和说明。
@Alph.Dev:对于找不到类 COM,这完全是另一回事。在谷歌上查看。您也可以查看此 php.net/manual/en/com.installation.php
@Alph.Dev 请查看technet.microsoft.com/en-us/library/ee156605.aspx 以获取有关 Run() 函数的更新信息【参考方案19】:
我知道这是一篇已有 100 年历史的帖子,但无论如何,我认为它可能对某人有用。您可以在页面的某个位置放置一个不可见的图像,指向需要在后台运行的 url,如下所示:
<img src="run-in-background.php" border="0" alt="" width="1" height="1" />
【讨论】:
非常糟糕的解决方案。如果用户离开页面,则进程将被中断。 “隐形图片”及其链接暴露在页面源代码中。以上是关于php执行后台进程的主要内容,如果未能解决你的问题,请参考以下文章