php 通过 popen() 执行脚本,脚本中 echo 后脚本中止的原因?

需求:通过远程访问地址,异步执行一段服务器上的脚本

框架版本

php artisan --version
Laravel Framework 5.2.45

定义脚本
app/Console/Commands/Out.php

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Log;

class Out extends Command
{
    protected $signature = 'laravel_out {a=0}';

    protected $description = 'Out number from a.';

    public function handle()
    {
        $a = $this->argument('a');
        if (!is_numeric($a) || $a >= 10) {
            Log::info('out', ["{$a} format error."]);
            echo "{$a} format error.";
        }

        for ($i = $a; $i < 10; $i++) {
            Log::info('out', ["{$i}"]);
            echo $i.PHP_EOL;
        }

        Log::info('out', ["Out from {$a} to 9."]);
        echo "Out from {$a} to 9.".PHP_EOL;
        return;
    }
}

定义路由
app/Http/routes.php

<?php
Route::get('/out', ['uses' => 'OutController@out']);

定义控制器
app/Http/Controllers/OutController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class OutController extends Controller
{
    public function out(Request $request)
    {
        $a = $request->input('a', 0);

        $handle = popen("/usr/local/php/bin/php /laravel/artisan laravel_out {$a}", 'r');
        dump($handle);
        pclose($handle);

        exit('url over');
    }
}

现在在服务器执行命令

php artisan laravel_out

storage/logs/laravel.log

[2017-12-19 11:36:24] laravel.INFO: out ["0"] 
[2017-12-19 11:36:24] laravel.INFO: out ["1"] 
[2017-12-19 11:36:24] laravel.INFO: out ["2"] 
[2017-12-19 11:36:24] laravel.INFO: out ["3"] 
[2017-12-19 11:36:24] laravel.INFO: out ["4"] 
[2017-12-19 11:36:24] laravel.INFO: out ["5"] 
[2017-12-19 11:36:24] laravel.INFO: out ["6"] 
[2017-12-19 11:36:24] laravel.INFO: out ["7"] 
[2017-12-19 11:36:24] laravel.INFO: out ["8"] 
[2017-12-19 11:36:24] laravel.INFO: out ["9"] 
[2017-12-19 11:36:24] laravel.INFO: out ["Out from 0 to 9."] 

对比浏览器访问

xxx.app/out

storage/logs/laravel.log

[2017-12-19 11:46:43] ticket_five.INFO: out ["1"]

仅轮询了一次

现在有两个解决方案:
1.通过使用system()函数替用popen()和pclose(),但是达不到异步的效果。

2.通过直接打印输出
修改控制器
app/Http/Controllers/OutController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class OutController extends Controller
{
    public function out(Request $request)
    {
        $a = $request->input('a', 0);

        $handle = popen("/usr/local/php/bin/php /laravel/artisan laravel_out {$a}", 'r');
        dump($handle);
        
        // 添加
        // 直接打印输出
        while (!feof($handle)) {
            $out = fgets($handle, 4096);
            echo  $out;
        }  
        
        pclose($handle);

        exit('url over');
    }
}

通过浏览器访问,日志记录如下

[2017-12-19 13:44:36] laravel.INFO: out ["1"] 
[2017-12-19 13:44:36] laravel.INFO: out ["2"] 
[2017-12-19 13:44:36] laravel.INFO: out ["3"] 
[2017-12-19 13:44:36] laravel.INFO: out ["4"] 
[2017-12-19 13:44:36] laravel.INFO: out ["5"] 
[2017-12-19 13:44:36] laravel.INFO: out ["6"] 
[2017-12-19 13:44:36] laravel.INFO: out ["7"] 
[2017-12-19 13:44:36] laravel.INFO: out ["8"] 
[2017-12-19 13:44:36] laravel.INFO: out ["9"] 
[2017-12-19 13:44:36] laravel.INFO: out ["Out from 1 to 9."] 

3.重新定义PHP的标准输出,代码如下
修改脚本
app/Console/Commands/Out.php

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Log;

class Out extends Command
{
    protected $signature = 'laravel_out {a=0}';

    protected $description = 'Out number from a.';

    public function handle()
    {
        // 添加
        // 重新定义PHP的标准输出
        $outPath = '/laravel/storage/logs/out.log';
        fclose(STDOUT);
        $GLOBALS['stdout'] = fopen($outPath, 'ab');
    
        $a = $this->argument('a');
        if (!is_numeric($a) || $a >= 10) {
            Log::info('out', ["{$a} format error."]);
            echo "{$a} format error.";
        }

        for ($i = $a; $i < 10; $i++) {
            Log::info('out', ["{$i}"]);
            echo $i.PHP_EOL;
        }

        Log::info('out', ["Out from {$a} to 9."]);
        echo "Out from {$a} to 9.".PHP_EOL;
        return;
    }
}

通过浏览器访问,日志记录如下

[2017-12-19 13:39:16] laravel.INFO: out ["1"] 
1
[2017-12-19 13:39:16] laravel.INFO: out ["2"] 
2
[2017-12-19 13:39:16] laravel.INFO: out ["3"] 
3
[2017-12-19 13:39:16] laravel.INFO: out ["4"] 
4
[2017-12-19 13:39:16] laravel.INFO: out ["5"] 
5
[2017-12-19 13:39:16] laravel.INFO: out ["6"] 
6
[2017-12-19 13:39:16] laravel.INFO: out ["7"] 
7
[2017-12-19 13:39:16] laravel.INFO: out ["8"] 
8
[2017-12-19 13:39:16] laravel.INFO: out ["9"] 
9
[2017-12-19 13:39:16] laravel.INFO: out ["Out from 1 to 9."] 
Out from 1 to 9.

问题来了,第二种,第三种方案确实可以满足我的需求,但是报错的原因和解决的原理是什么呢?

阅读 2.2k
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题