提交表单后如何在后台运行PHP脚本?

时间:2011-01-07 15:05:21

标签: php scripting background

问题
我有一个表单,在提交时,将运行基本代码来处理提交的信息并将其插入数据库以显示在通知网站上。另外,我有一个已经注册通过电子邮件和短信接收这些通知的人员列表。这个列表是微不足道的(只推动了大约150),但这足以导致需要花费一分钟才能在整个订阅者表中循环并发送150多封电子邮件。 (由于大量的电子邮件策略,电子邮件按照我们的电子邮件服务器的系统管理员的要求单独发送。)

在此期间,发布提醒的个人将在表格的最后一页上停留近一分钟而没有任何正面强化他们的通知被发布。这导致了其他潜在的问题,所有这些问题都有可能我认为不太理想的解决方案。

  1. 首先,海报可能认为服务器滞后并再次单击“提交”按钮,导致脚本重新开始或运行两次。我可以通过使用JavaScript来禁用按钮并将文本替换为“正在处理...”之类的内容来解决这个问题,但这并不理想,因为用户在脚本执行的长度上仍会卡在页面上。 (另外,如果禁用JavaScript,则此问题仍然存在。)

  2. 其次,海报可能会在提交表单后提前关闭标签页或浏览器。该脚本将继续在服务器上运行,直到它尝试写回浏览器,但是如果用户随后浏览到我们域中的任何页面(当脚本仍在运行时),则浏览器会挂起加载页面,直到脚本结束。 (仅当浏览器的选项卡或窗口关闭而不是整个浏览器应用程序时才会发生这种情况。)但这仍然不太理想。

  3. (可能)解决方案
    我已经决定将脚本的“电子邮件”部分分成一个单独的文件,我可以在发布通知后调用它。我最初想到在通知成功发布后将其放在确认页面上。但是,用户不会知道此脚本正在运行,并且任何异常都不会对他们显而易见;这个脚本不会失败。

    但是,如果我可以将此脚本作为后台进程运行,该怎么办?所以,我的问题是:如何执行PHP脚本作为后台服务触发并完全独立于用户在表单级别完成的操作?

    编辑:无法成为cron'ed。它必须在提交表单的瞬间运行。这些是高优先级通知。此外,运行我们服务器的系统管理员不允许crons运行超过5分钟。

16 个答案:

答案 0 :(得分:81)

使用execshell_exec进行一些实验我发现了一个完美无缺的解决方案!我选择使用shell_exec,这样我就可以记录发生(或不发生)的每个通知过程。 (shell_exec以字符串形式返回,这比使用exec更容易,将输出分配给变量,然后打开要写入的文件。)

我正在使用以下行来调用电子邮件脚本:

shell_exec("/path/to/php /path/to/send_notifications.php '".$post_id."' 'alert' >> /path/to/alert_log/paging.log &");

注意命令末尾的&非常重要(正如@netcoder所指出的那样)。此UNIX命令在后台运行进程。

在脚本路径之后用单引号括起来的额外变量被设置为我可以在我的脚本中调用的$_SERVER['argv']变量。

然后,电子邮件脚本使用>>输出到我的日志文件,并输出如下内容:

[2011-01-07 11:01:26] Alert Notifications Sent for http://alerts.illinoisstate.edu/2049 (SCRIPT: 38.71 seconds)
[2011-01-07 11:01:34] CRITICAL ERROR: Alert Notifications NOT sent for http://alerts.illinoisstate.edu/2049 (SCRIPT: 23.12 seconds)

答案 1 :(得分:16)

在Linux / Unix服务器上,您可以使用proc_open

在后台执行作业
$descriptorspec = array(
   array('pipe', 'r'),               // stdin
   array('file', 'myfile.txt', 'a'), // stdout
   array('pipe', 'w'),               // stderr
);

$proc = proc_open('php email_script.php &', $descriptorspec, $pipes);

&是重要的一点。即使原始脚本已经结束,脚本也将继续。

答案 2 :(得分:6)

PHP exec("php script.php")可以做到。

来自Manual

  

如果程序以此启动   功能,以便继续   在后台运行,输出   该程序必须重定向到一个   文件或其他输出流。失败   这样做会导致PHP挂起,直到   程序的执行结束。

因此,如果您将输出重定向到日志文件(无论如何都是个好主意),您的调用脚本将不会挂起,您的电子邮件脚本将在bg中运行。

答案 3 :(得分:5)

为什么不在脚本上发出HTTP请求并忽略响应?

http://php.net/manual/en/function.httprequest-send.php

如果您在脚本上发出请求,则需要调用您的Web服务器将在后台运行它,您可以(在主脚本中)显示一条消息,告诉用户脚本正在运行。

答案 4 :(得分:4)

这个怎么样?

  1. 保存表单的PHP脚本会将标志或某些值保存到数据库或文件中。
  2. 第二个PHP脚本会定期轮询此值,如果已设置,则会以同步方式触发电子邮件脚本。
  3. 应该将第二个PHP脚本设置为以cron身份运行。

答案 5 :(得分:4)

我知道你不能这么简单地做到这一点(参见fork exec等(不在windows下工作)),可能是你可以逆转方法,使用浏览器的背景在ajax中发布表单,所以如果这个帖子还在工作,你没有等待时间 即使您需要做一些详细的阐述,这也会有所帮助
关于发送邮件,它总是建议使用假脱机程序,可能是本地&快速smtp服务器接受你的请求,并将它们假脱机到真正的MTA或全部放入数据库,而不是使用一个假脱机队列的cron。
cron可能在另一台调用假脱机程序的机器上作为外部URL:

* * * * * wget -O /dev/null http://www.example.com/spooler.php

答案 6 :(得分:4)

在后台运行PHP脚本的简单方法是

php script.php >/dev/null &

脚本将在后台运行,页面也会更快地到达操作页面。

答案 7 :(得分:4)

在所有答案中,没有人考虑过一个非常简单的fastcgi_finish_request函数,当调用时,将所有剩余的输出刷新到浏览器并关闭Fastcgi会话和HTTP连接,同时让脚本在后台运行。

一个例子:

uuid == <The-workout-UUID>

答案 8 :(得分:3)

背景cron工作对此来说听起来不错。

您需要ssh访问该计算机才能以cron身份运行该脚本。

$ php scriptname.php来运行它。

答案 9 :(得分:3)

如果你可以通过ssh访问服务器并且可以运行自己的脚本,你可以使用php创建一个简单的fifo服务器(尽管你必须重新编译php,posix支持fork)。

服务器可以用任何东西编写,你可能很容易在python中完成。

或者最简单的解决方案是发送HttpRequest而不读取返回数据,但服务器可能会在完成处理之前销毁脚本。

示例服务器:

<?php
define('FIFO_PATH', '/home/user/input.queue');
define('FORK_COUNT', 10);

if(file_exists(FIFO_PATH)) {
    die(FIFO_PATH . ' exists, please delete it and try again.' . "\n");
}

if(!file_exists(FIFO_PATH) && !posix_mkfifo(FIFO_PATH, 0666)){
    die('Couldn\'t create the listening fifo.' . "\n");
}

$pids = array();
$fp = fopen(FIFO_PATH, 'r+');
for($i = 0; $i < FORK_COUNT; ++$i) {
    $pids[$i] = pcntl_fork();
    if(!$pids[$i]) {
        echo "process(" . posix_getpid() . ", id=$i)\n";
        while(true) {
            $line = chop(fgets($fp));
            if($line == 'quit' || $line === false) break;
            echo "processing (" . posix_getpid() . ", id=$i) :: $line\n";
        //  $data = json_decode($line);
        //  processData($data);
        }
        exit();
    }
}
fclose($fp);
foreach($pids as $pid){
    pcntl_waitpid($pid, $status);
}
unlink(FIFO_PATH);
?>

客户端示例:

<?php
define('FIFO_PATH', '/home/user/input.queue');
if(!file_exists(FIFO_PATH)) {
    die(FIFO_PATH . ' doesn\'t exist, please make sure the fifo server is running.' . "\n");
}

function postToQueue($data) {
    $fp = fopen(FIFO_PATH, 'w+');
    stream_set_blocking($fp, false); //don't block
    $data = json_encode($data) . "\n";
    if(fwrite($fp, $data) != strlen($data)) {
        echo "Couldn't the server might be dead or there's a bug somewhere\n";
    }
    fclose($fp);
}
$i = 1000;
while(--$i) {
    postToQueue(array('xx'=>21, 'yy' => array(1,2,3)));
}
?>

答案 10 :(得分:2)

假设您在* nix平台上运行,请使用cronphp可执行文件。

编辑:

在SO上有很多问题要求“在没有cron的情况下运行php”。这是一个:

Schedule scripts without using CRON

那就是说,上面的exec()回答听起来非常有希望:)

答案 11 :(得分:2)

如果您使用的是Windows,请研究proc_openpopen ...

但是,如果我们在同一台服务器上,那么......&#34; Linux&#34;运行cpanel然后这是正确的方法:

#!/usr/bin/php 
<?php
$pid = shell_exec("nohup nice php -f            
'path/to/your/script.php' /dev/null 2>&1 & echo $!");
While(exec("ps $pid"))
{ //you can also have a streamer here like fprintf,        
 // or fgets
}
?>

如果您怀疑自己可以处理它们,请不要使用fork()curl,这就像滥用您的服务器一样

最后,在上面调用的script.php文件中,请注意这一点,请确保您写道:

<?php
ignore_user_abort(TRUE);
set_time_limit(0);
ob_start();
// <-- really optional but this is pure php

//Code to be tested on background

ob_flush(); flush(); 
//this two do the output process if you need some.        
//then to make all the logic possible


str_repeat(" ",1500); 
//.for progress bars or loading images

sleep(2); //standard limit

?>

答案 12 :(得分:1)

对于后台工作者我认为您应该尝试这种技术,它将有助于调用您喜欢的页面,所有页面将立即独立运行,而无需等待每个页面响应异步。

<强> form_action_page.php

     <?php

    post_async("http://localhost/projectname/testpage.php", "Keywordname=testValue");
    //post_async("http://localhost/projectname/testpage.php", "Keywordname=testValue2");
    //post_async("http://localhost/projectname/otherpage.php", "Keywordname=anyValue");
    //call as many as pages you like all pages will run at once //independently without waiting for each page response as asynchronous.

  //your form db insertion or other code goes here do what ever you want //above code will work as background job this line will direct hit before //above lines response     
                ?>
                <?php

                /*
                 * Executes a PHP page asynchronously so the current page does not have to wait for it to     finish running.
                 *  
                 */
                function post_async($url,$params)
                {

                    $post_string = $params;

                    $parts=parse_url($url);

                    $fp = fsockopen($parts['host'],
                        isset($parts['port'])?$parts['port']:80,
                        $errno, $errstr, 30);

                    $out = "GET ".$parts['path']."?$post_string"." HTTP/1.1\r\n";//you can use POST instead of GET if you like
                    $out.= "Host: ".$parts['host']."\r\n";
                    $out.= "Content-Type: application/x-www-form-urlencoded\r\n";
                    $out.= "Content-Length: ".strlen($post_string)."\r\n";
                    $out.= "Connection: Close\r\n\r\n";
                    fwrite($fp, $out);
                    fclose($fp);
                }
                ?>

<强> testpage.php

    <?
    echo $_REQUEST["Keywordname"];//case1 Output > testValue
//here do your background operations it will not halt main page
    ?>

PS:如果您想将url参数作为循环发送,请遵循以下答案:https://stackoverflow.com/a/41225209/6295712

答案 13 :(得分:0)

在我的情况下,我有3个参数,其中一个是字符串(mensaje):

exec("C:\wamp\bin\php\php5.5.12\php.exe C:/test/N/trunk/api/v1/Process.php $idTest2 $idTest3 \"$mensaje\" >> c:/log.log &");

在我的Process.php中,我有这段代码:

if (!isset($argv[1]) || !isset($argv[2]) || !isset($argv[3]))
{   
    die("Error.");
} 

$idCurso = $argv[1];
$idDestino = $argv[2];
$mensaje = $argv[3];

答案 14 :(得分:0)

这对我有用。

exec(“php asyn.php”.” > /dev/null 2>/dev/null &“);

答案 15 :(得分:0)

使用Amphp并行和异步执行作业。

安装库

git remote add origin ...

代码示例

composer require amphp/parallel-functions

针对您的用例,您可以执行以下操作

<?php

require "vendor/autoload.php";

use Amp\Promise;
use Amp\ParallelFunctions;


echo 'started</br>';

$promises[1] = ParallelFunctions\parallel(function (){
    // Send Email
})();

$promises[2] = ParallelFunctions\parallel(function (){
    // Send SMS
})();


Promise\wait(Promise\all($promises));

echo 'finished';