【问题标题】:Would like to run a background PHP script from the main PHP script想从主 PHP 脚本运行后台 PHP 脚本
【发布时间】:2013-08-03 20:08:51
【问题描述】:

在构建返回给用户的页面时,我想提交一个后台脚本来对数据库中的数据进行一些数值分析,并将结果通过电子邮件发送给用户。这个过程可能需要一分钟左右,所以我不想在页面运行时延迟页面服务。

有没有办法从构建页面的脚本中触发另一个 PHP 脚本,以便它可以发送页面并在其他脚本在后台运行时完成?

为了测试,这个TEST.PHP:

<?php
set_time_limit(0);
mail ('myemail@myemail.com','Test Email from ClockPie', 'foobar');
?>

然后我把它放在构建页面服务的脚本中:

...
shell_exec ('test.php'); 
...

我在 Windoze 7 Home Premium 下运行。这有什么明显的问题吗?

是的,我知道这本质上是一个重复的问题,并且还有其他关于同一件事的现有问题,但我在 StackOverflow 上过于peon,无法简单地添加cmets 并参与讨论 :-(

【问题讨论】:

    标签: php windows email shell-exec


    【解决方案1】:

    如果你想 fork 另一个 PHP 进程,你需要使用pcntl_fork。但是我不认为这是这里最好的方法。相反,我会让浏览器访问的脚本将要处理的数据添加到队列中。然后设置一个计划任务以每“x”分钟运行一次,该任务将处理队列并在完成后向用户发送电子邮件。

    当然,我假设如果队列中实际上没有任何内容(即它应该需要几毫秒才能完成),您将使这个预定脚本变得非常轻量级。如果不是这样,您每隔几分钟就会让您的服务器陷入瘫痪,在这种情况下,查看pcntl_fork 之类的内容会是更好的解决方案。

    这样做的一个缺点是,如果您将其设置为运行,例如每 5 分钟运行一次,但处理数据只需 2 分钟,则用户最多需要等待 3 分钟才能接收电子邮件。如果这是一个问题,请将其调整为更频繁地运行。

    【讨论】:

    • 不会 fork 只是创建相同代码的副本吗?我还考虑过将每个请求写成请求表中的一行,然后让一个 cron 进程每小时运行一次左右并满足所有用户请求,但我不知道什么时候能有多少控制权我将完成的网站放置在 GoDaddy 等网络托管公司。我宁愿不必依赖网络主机的技术支持人员为我设置 crons。
    • pcntl_fork() 函数创建一个子进程,它与父进程的区别仅在于它的 PID 和 PPID。这并不意味着你不能有创意,先用相同的代码检查进程队列中是否存在并处理它,或者向进程队列中添加一些东西。
    【解决方案2】:

    shell_exec() 就像坐在命令提示符下输入字符串值并按回车一样。您不能只输入“test.php”并让它触发,您需要运行 PHP,并将其指向您的 php 文件。

    根据您安装 php 的方式,您可以简单地将其更改为 shell_exec('php test.php');,或者您可能需要提供一个 PHP 路径,例如 shell_exec('C:\php\bin\php.exe test.php');。路径将取决于您的环境。

    在您显示的代码中,如果您只是发送一封电子邮件,那应该不会超过几秒钟,所以我什至不会走这条路。但我不认为这是您的代码的最终结果,只是一个示例。

    【讨论】:

    • 我的问题中的示例只是为了测试触发流程。真正的代码将针对 MySql 数据库运行,并且需要一两分钟才能运行。但是,是的,发送给用户的电子邮件是最终结果。
    【解决方案3】:

    您可以做的是输出页面和flush(),以便所有数据都发送给用户。然后你发了邮件。用户将看到该页面,但在后台它仍在加载。这不需要像shell_exec();这样的东西

    这种方法也被 phpBB 之类的软件用来处理需要相当长的时间的 cron 作业。

    flush() 文档:http://php.net/manual/en/function.flush.php

    基本程序架构:

    1. 构建和回显页面
    2. 刷新()
    3. 发送电子邮件

    【讨论】:

    • 这并不适用于所有情况(例如,如果启用 gzip 压缩)
    • 另外取决于您的服务器设置,flush() 不能保证将页面实际输出到浏览器,或者处理起来可能会变得非常非常复杂。 serverfault.com/questions/488767/…
    • 这听起来像是完美的解决方案。无需处理外部流程。我想知道为什么你的答案是-1。我自己试试看……
    • 我试过这个。这看起来可能有效,因为它发送了电子邮件,但我不知道 flush() 导致页面在发送电子邮件之前被发送,或者它只是忽略了 flush()。
    • 睡眠导致整个东西在睡眠期间挂起,所以我猜冲洗没有效果。
    【解决方案4】:

    您可以使用 shell_exec() 运行您的“子 php 脚本”asynchronously,这意味着它将在您的主 php 脚本继续运行时在后台运行,方法是附加触发您的“子 php 脚本”的命令带有 & 符号。所以,它会是这样的:

    $cmd="php /path/to/you/php/sub/script.php &";
    shell_exec($cmd);
    //main script continues running here, while sub script runs in the background...
    

    【讨论】:

    • 注意,OP说这是windows。
    • 谢谢迈克。对不起,我错过了。无视我的回答。
    • 其他方面的想法很好
    • 当我为完成的网站设置托管时,我不知道该网站将在什么操作系统下运行。很可能是 *nix 的某个版本。但我是在 Win7 上开发的,所以这就是它现在必须做的事情。
    【解决方案5】:

    以下代码适用于我的系统。请注意,我只是一个业余爱好者,而不是专家,所以这可能不属于“最佳实践”的范畴,我不知道安全隐患可能是什么,但这绝对有效,创建多个运行的线程同时。不要介意文件夹名称“卡路里”。当我把这个示例代码放在一起时,这恰好是我正在工作的文件夹。

    main.php:
    
    error_log('Hello, world, from main!');
    
    $numberOfThreadsToCreate = 3;
    
    for($i = 0; $i < $numberOfThreadsToCreate; ++$i) {
        error_log("Main starting child {$i}");
    
        $fp = fsockopen('localhost', 8888);
        if(!$fp) {
            error_log("$errstr ($errno)");
            exit;
        }
    
        $firstSleep = $numberOfThreadsToCreate - $i;
        $header = "GET /Calories/thread.php?threadID={$i}&firstSleep={$firstSleep}"
                    . " HTTP/1.1\r\n"
                    . "Host: localhost\r\n"
                    . "Connection: Close\r\n\r\n";
    
        $r = fputs($fp, $header); 
        fclose($fp);
    
        sleep(1);
    }
    
    for($i = 0; $i < 5; ++$i) {
        sleep(1);
        error_log('Main is still running');
    }
    
    error_log("Goodbye, cruel world, from main!");
    
    
    thread.php
    
    $myThreadID = $_GET['threadID'];
    $sleep = $_GET['firstSleep'];
    
    error_log("Hello, world, from child thread, ID={$myThreadID}!");
    for($i = 0; $i < 5; ++$i) {
        error_log("Child {$myThreadID} sleeping for {$sleep} seconds");
        sleep($sleep);
        $sleep = 1;
    }
    
    error_log("Goodbye, cruel world, from child thread, ID={$myThreadID}!");
    

    以及日志文件结果:

    Hello, world, from main!
    Main starting child 0
    Hello, world, from child thread, ID=0!
    Child 0 sleeping for 3 seconds
    Main starting child 1
    Hello, world, from child thread, ID=1!
    Child 1 sleeping for 2 seconds
    Main starting child 2
    Hello, world, from child thread, ID=2!
    Child 2 sleeping for 1 seconds
    Child 1 sleeping for 1 seconds
    Child 2 sleeping for 1 seconds
    Child 0 sleeping for 1 seconds
    Child 1 sleeping for 1 seconds
    Child 2 sleeping for 1 seconds
    Child 0 sleeping for 1 seconds
    Main is still running
    Child 1 sleeping for 1 seconds
    Child 2 sleeping for 1 seconds
    Child 0 sleeping for 1 seconds
    Main is still running
    Child 1 sleeping for 1 seconds
    Child 2 sleeping for 1 seconds
    Child 0 sleeping for 1 seconds
    Main is still running
    Goodbye, cruel world, from child thread, ID=1!
    Goodbye, cruel world, from child thread, ID=2!
    Main is still running
    Goodbye, cruel world, from child thread, ID=0!
    Main is still running
    Goodbye, cruel world, from main!
    

    【讨论】:

    • 问题是主脚本还要等待子脚本完成,所以你什么也没得到。
    • @Mike 实际上,不,我用这种方法取得了成功。效果很好。
    • 所以这是否可行存在争议。嗯。我自己试试看。。。。
    • @Fredashay 没有争议。这对我很有效。我不知道你在做什么不同。如果我没记错的话,我必须对 PHP 进行一些摆弄才能启用套接字。但最后,我得到了它的工作,现在有一个脚本可以启动多个并发运行的线程。
    • 好的,我稍后会尝试“摆弄”套接字,看看是否需要更改 INI 设置以使其正常工作...
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-10
    • 2017-11-04
    相关资源
    最近更新 更多