【问题标题】:How to run 2 PHP script simultaniously (non-blocking) for monitoring a popen command?如何同时运行 2 个 PHP 脚本(非阻塞)来监控 popen 命令?
【发布时间】:2012-12-26 16:58:18
【问题描述】:

如何同时(同步)运行 2 个 PHP 脚本来监控 popen 命令?

我有一个脚本启动这样的命令:

7za a -t7z -mx9 backup.7z "H:\Informatique\*"

我想在使用 jQuery 和 PHP 的页面上显示压缩进度。

运行此命令的 php 脚本如下所示:

    if( ($fp = popen("7za a -t7z ".$GLOBALS["backup_compression"]." \"".$backuplocation.$backupname."\" \"".$pathtobackup."\"", "r")) ) {
        while( !feof($fp) ){

            $fread = fread($fp, 256);
            $line_array = preg_split('/\n/',$fread);
            $num_lines = count($line_array);
            $_SESSION['job'][$jobid]['currentfile'] = $_SESSION['job'][$jobid]['currentfile']+$num_lines;
            $num_lines = 0;
            flush();
        }
        pclose($fp);
    }

jQuery 调用 7za 脚本,然后 jquery 每 1000 毫秒调用一次侦听器 (listener.php)。 listener.php 页面包含以下代码:

session_start();
$jobid = $_GET['jobid'];

if(!isset($_SESSION['job'][$jobid])) { $arr = array("error"=>"Job not found"); echo json_encode($arr); exit(); };
$arr = array(
    "curfile" => $_SESSION['job'][$jobid]['currentfile'],
    "totalfiles" => $_SESSION['job'][$jobid]['totalfiles'],
);
echo json_encode($arr);
$jobid = null;
$arr = null;
exit();

在 jquery 调用完成后(使用监听器并且我们从服务器得到响应),我们以正常的形式显示信息,例如:$("currentfile").text(data['curfile']);

问题是监听器处于无限循环中等待第一个脚本完成......这不是监听器的工作......它在一切结束时监听......当你聆听时,它是知道发生了什么。 :P

您知道发生了什么吗?我该如何解决这个问题? 或者你可以帮我解决这个问题?

一如既往,欢迎提出任何建议。 谢谢。

编辑

jQuery 脚本:

function backup_launch(jobid) {
    x('jobid: '+jobid+' on state '+state);

    x('Listener launched');
    listen(jobid);

    timeout = setTimeout("listen('"+jobid+"')", 500);
    $.ajax({
        url:'backup.manager.php?json&jobid='+jobid+'&state='+state, 
        dataType:'json', 
        success:function(data)
        {
            state = 3;
        }
    });
}
function listen(jobid) {

    $.ajax({
        url:'backup.listener.php?json&jobid='+jobid, 
        dataType:'json', 
        success:function(data)
        {
            var curfile = data['curfile'];
            var totalfiles = data['totalfiles'];
            var p = curfile * 100 / totalfiles;
            x('File '+curfile+' out of '+totalfiles+' progress%: '+p);
            timeout = setTimeout("listen('"+jobid+"')", 500);

        }
    });
}

编辑 2 我找到了 Gearman (http://gearman.org/),但我根本不知道如何实现它,它需要是可移植的/独立的......我会尝试调查一下。

编辑 3

backup.manager.php 页面的完整代码。脚本会立即发送响应,但在后台执行此工作。

listen.php 页面在返回任何结果之前仍然等待命令完成。

$jobid = isset($_GET['jobid']) ? $_GET['jobid'] : 0;

//Make sure jobid is specified
if($jobid == 0) { return; } 

header("Connection: close");
@ob_end_clean();
ignore_user_abort();
ob_start();

echo 'Launched in backgroud';

$size = ob_get_length();
header("Content-Length: ".$size);
ob_end_flush();
flush();




$_SESSION['job'][$jobid]['currentfile'] = 0;


// 3. When app appove backup, 
//  - Write infos to DB
//  - Zip all files into 1 backup file
$datebackup = time();
$bckpstatus = 1; //In progress

$pathtobackup = $_SESSION['job'][$jobid]['path'];


/*
$query = "INSERT INTO backups (watchID, path, datebackup, checksum, sizeori, sizebackup, bckpcomplete)
        VALUES ($watchID, '{$path}', '{$datebackup}', '', '{$files_totalsize}', '', '{$bckpstatus}')"; 


$sth = $db->prepare($query);

$db->beginTransaction();
$sth->execute();
$db->commit();
$sth->closeCursor();
*/



$backupname = $jobid.".".$GLOBALS["backup_ext"];
$backuplocation = "D:\\";

if( ($fp = popen("7za a -t7z ".$GLOBALS["backup_compression"]." \"".$backuplocation.$backupname."\" \"".$pathtobackup."\"", "r")) ) {
    while( !feof($fp) ){

        $fread = fread($fp, 256);
        $line_array = preg_split('/\n/',$fread);
        $num_lines = count($line_array);
        $_SESSION['job'][$jobid]['currentfile'] = $_SESSION['job'][$jobid]['currentfile']+$num_lines;
        $num_lines = 0;
        sleep(1);
        flush();
    }
    pclose($fp);
}

【问题讨论】:

    标签: php jquery listener popen progress


    【解决方案1】:

    我决定为每个单独的文件调用该命令。

    它会变慢,但管理文件原因错误以及哪些文件已正确插入存档会更安全。

    我会尝试更快地找到一些东西,例如存档目录有 10 000 个图标文件。一次可能是 5 个文件,而不是一次一个文件。

    此代码用于测试目的,该代码根本没有针对生产进行优化

    index.html jquery 脚本:

    var state = 0;
    var jobid = '';
    var timeout;
    var jobfiles;
    var jobpaths = [];
    $(document).ready(function() {
        $("button").click(function(e) {
            e.preventDefault();
    
            console.log('Sending');
            var path = $("input").val();
            x('state 1 getting info - infpb');
            state = 1;
            $.ajax({
                url:'backup.manager.php?json&path='+encodeURI(path)+'&type=0&state='+state, 
                dataType:'json', 
                success:function(data)
                {
    
                    jobid = data['stats']['jobid'];
                    jobfiles = data['stats']['totalfiles'];
                    var jobsize = data['stats']['totalsize'];
                    for(var i = 0, len = jobfiles; i < len; i++) {
                        jobpaths.push(data[i]['File']);
                    }
                    state = 2;
                    x('state 2 - infpb stop (Information retrieved, launch backup) '+jobfiles+' files with '+jobsize+' bytes');
                    backup_launch(jobid);
    
                }
            });
        });
    });
    function x(x) {
        $("#l").append(x+"<br>");   
    }
    var curfileid = 0;
    function backup_launch(jobid) {
        x('jobid: '+jobid+' on state '+state);
        $.ajax({
            url:'backup.manager.php',
            data: {
                'json': 1,
                'jobid': jobid,
                'state': state,
                'path': jobpaths[curfileid]
            },
            dataType:'json', 
            success:function(data)
            {
    
                if(curfileid < jobfiles) {
                    x((curfileid+1)+' / '+jobfiles);
                        curfileid++;
                        backup_launch(jobid);
                }
            },
            error: function(jqXHR, textStatus, errorThrown) {
                x(["backup.manager error", textStatus, errorThrown, jobid].join(": "));
            }
        });
    }
    function listen(jobid) {
    
        $.ajax({
            url:'backup.listener.php?json&jobid='+jobid, 
            dataType:'json', 
            success:function(data)
            {
                var curfile = data['curfile'];
                var totalfiles = data['totalfiles'];
                var p = curfile * 100 / totalfiles;
                x('File '+curfile+' out of '+totalfiles+' progress%: '+p);
                timeout = setTimeout("listen('"+jobid+"')", 500);
    
            }
        });
    }
    

    backup.manager.php

    set_time_limit(0);
    require('../functions.php');
    session_start();
    
    
    
    $keepCPUlow_lastlookup = time();
    $keepCPUlow_mindelay = 60;
    function keepCPUlow() {
        global $keepCPUlow_lastlookup, $keepCPUlow_mindelay;
        if((time() - $keepCPUlow_lastlookup) > $keepCPUlow_mindelay) {
            $keepCPUlow_lastlookup = time();
            getSysload(75, 1000);   // Max %, wait time in ms
        }
    }
    
    
    
    $state = isset($_GET['state']) ? $_GET['state'] : 0;
    if($state == '1') {
        //
        $json = isset($_GET['json']) ? true : false;// Result should be in json format
        $path = isset($_GET['path']) ? $_GET['path'] : ''; // Path of the file or folder to backup
        $type = isset($_GET['type']) ? $_GET['type'] : ''; // Type - not very useful, for now
    
        //0. Assign a jobid for this job, it will help retrieve realtime information about this task
        $jobid = hash('md4', (time().uniqid().session_id()));
        //Store the current status (0) job not started
        $_SESSION['job'][$jobid]['status'] = 0; //Not started... yet
    
        // 1. Retrive list of files and stats
        $fileslist = array(); //Will contain the list of files
        $files_totalsize = 0; // Total size of files
    
        // Check if file or folder
        if(is_dir($path)) {
            //Path is a folder, get the list of files
            $files = getFilelist($path);
    
    
            foreach($files as $file) { //For each files
                if(!is_dir($file['File'])) { //That is not a directory
                    $files_totalsize = $files_totalsize+$file['Filesize']; //Increment toal size
                    $cpumon = keepCPUlow(); //if($cpumon[1]) echo ">CPU BURN".$cpumon[0]."<";
    
                }
            }
    
            $files_total = count($files); // Number of files
        } else {
            $files_totalsize = $files_totalsize+getFilesize($path);
    
            $files_total = 1;
        }
    
    
        $files['stats'] = array("totalfiles"=>$files_total, "jobid"=>$jobid, "totalsize"=>$files_totalsize);
    
        //Store infos in session
        $_SESSION['job'][$jobid]['totalfiles'] = $files_total;
        $_SESSION['job'][$jobid]['totalsize'] = $files_totalsize;
        $_SESSION['job'][$jobid]['path'] = is_dir($path) ? $path.'\\*' : $path;
        $_SESSION['job'][$jobid]['currentfile'] = 0;
        $_SESSION['job'][$jobid]['currentfile_path'] = '';
        $_SESSION['job'][$jobid]['bname'] = "SafeGuard_".$jobid.".".$GLOBALS["backup_ext"];
        $_SESSION['job'][$jobid]['blocation'] = "D:\\";
    
    
        // 2. return to app and wait for ready confirmation
        if(isset($_GET['json'])) {
            echo json_encode($files);
        }
        exit();
    }
    else if($state == '2') {
    
        $jobid = isset($_GET['jobid']) ? $_GET['jobid'] : 0;
        $_SESSION['job'][$jobid]['currentfile'] = 0;
    
        // 3. When app appove backup, 
        //      - Write infos to DB
        //      - Zip all files into 1 backup file
        $datebackup = time();
        $bckpstatus = 1; //In progress
    
        //$pathtobackup = $_SESSION['job'][$jobid]['path'];
        $pathtobackup = isset($_GET['path']) ? $_GET['path'] : "--";
    
        $backupname = $_SESSION['job'][$jobid]['bname'];
        $backuplocation = $_SESSION['job'][$jobid]['blocation'];
        /*
        $query = "INSERT INTO backups (watchID, path, datebackup, checksum, sizeori, sizebackup, bckpcomplete)
                VALUES ($watchID, '{$path}', '{$datebackup}', '', '{$files_totalsize}', '', '{$bckpstatus}')"; 
    
    
        $sth = $db->prepare($query);
    
        $db->beginTransaction();
        $sth->execute();
        $db->commit();
        $sth->closeCursor();
        */
    
    
    
        if( ($fp = popen("7za a -t7z ".$GLOBALS["backup_compression"]." \"".$backuplocation.$backupname."\" \"".$pathtobackup."\"", "r")) ) {
            while( !feof($fp) ){
    
                fread($fp, 256);
                $num_lines = 1;
                $_SESSION['job'][$jobid]['currentfile'] = $_SESSION['job'][$jobid]['currentfile']+$num_lines;
                $num_lines = 0;
    
                flush();
                echo '1';
            }
            pclose($fp);
        }
    
    
    
    
    }
    

    【讨论】:

      【解决方案2】:

      杰里米,

      几个月前,我回答了一个关于在 *NIX/PHP 环境中运行服务器端批处理作业的类似问题。如果我理解正确,您的要求会有所不同,但答案中可能会有一些帮助。

      Run a batch file from my website

      编辑

      这是您的客户端代码的修改版本。您会看到我更改的主要内容是:

      • listen(jobid); 移动到backup_launch 的成功处理程序中。
      • 添加错误处理程序,以便您观察错误。

      其他一切都只是编程风格的问题。

      function backup_launch(jobid) {
          x(['jobid: ' + jobid, 'on state', state].join(' '));
          $.ajax({
              url: 'backup.manager.php',
              data: {
                  'json': 1,
                  'jobid': jobid,
                  'state': state
              }
              dataType: 'json',
              success:function(data) {
                  state = 3;
                  x("Job started: " + jobid);
                  listen(jobid);
                  x("Listener launched: " + jobid);
              },
              error: function(jqXHR, textStatus, errorThrown) {
                  x(["backup.manager error", textStatus, errorThrown, jobid].join(": "));
              }
          });
      }
      function listen(jobid) {
          $.ajax({
              url: 'backup.listener.php',
              data: {
                  'json': 1
                  'jobid': jobid
              },
              dataType: 'json', 
              success: function(data) {
                  var curfile = data.curfile;
                  var totalfiles = data.totalfiles;
                  var p = curfile * 100 / totalfiles;
                  x(['File', curfile, 'out of', totalfiles, 'progress%:', p].join(' '));
                  timeout = setTimeout(function() {
                      listen(jobid);
                  }, 500);
              },
              error: function(jqXHR, textStatus, errorThrown) {
                  x(["Listener error", textStatus, errorThrown, jobid].join(": "));
              }
          });
      }
      

      【讨论】:

      • 我看了你的回答 Beetroot-Beetroot,它很相似,但我的问题更像是一个异步请求......我认为它在 PHP 本身,我正在尝试找到一个解决方法.
      • 是的,我的应用程序确实不同,主要是我只需要启动一个服务器端工作并获得它已经启动(或未启动)的反馈。如果我理解正确,您想执行类似操作,然后轮询服务器以获取进度?
      • 是的,一个命令正在一个while循环中的一个脚本上运行,并增加一个存储在会话中的值,另一个脚本“监视器”。它读取存储在会话中的信息。不幸的是,监听器等到第一个脚本完成后才返回任何结果。
      • 对不起,我想我颠倒了术语,同步/异步......我需要脚本是“非阻塞”的,很抱歉造成混乱。
      • 大概,您允许 script_1 在尝试开始投票之前完成并返回?即客户端,您在调用 7za 脚本的 jQuery 代码的 success(或 .done())处理程序中启动 listener.php 的轮询。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-10-03
      • 1970-01-01
      • 1970-01-01
      • 2015-03-18
      • 2019-11-19
      • 2015-11-11
      相关资源
      最近更新 更多