• PHP CLI shell 多进程入口


    #!/usr/bin/env php
    <?php
    /*
     * PHP CLI shell 多进程入口
     *
     * 运行 ./bat.php [--help] 查看帮助
     * 运行 ./bat.php bat-test.php 执行示例
     *
     * bat-test.php 脚本内容如下:
     * <?php
     
    #防误确认
    if(!bat::confirm()){
        bat::message("用户取消");
        exit;
    }
     
    #全局变量
    global $x;
     
    $x = 12345;
     
    #添加任务
    bat::run('a');
    bat::run('b', __LINE__);
    bat::run('c');
    bat::run('b', __LINE__);
    bat::run('a');
     
    #启动任务
    bat::start();
     
    #任务函数
    function a(){
        global $x;
        do{
            bat::notify("我是通知主进程显示的提示文字,测试变量 $x = " . $x++);
            usleep(500000);
        }while(mt_rand(100, 999) > 159);
    }
     
    function b($line){
        do{
            bat::notify("我是显示传递的参数 $line = $line");
            usleep(500000);
        }while(mt_rand(100, 999) > 359);
    }
     
    function c(){
        global $x;
        bat::notify("多个任务之间的初始变量值不受影响, $x = $x");
        bat::notify("我是暂停 9 秒时间测试");
        sleep(9);
        bat::notify("我是出错代码 5 测试");
        exit(5);
    }
     
     * ?>
     */
     
    /** 确保这个脚本只能运行在 SHELL 中 */
    if(substr(php_sapi_name(), 0, 3) !== 'cli'){
        die("This Programe can only be run in CLI mode. ");
    }
     
    if(!is_callable('pcntl_fork') || !is_callable('msg_send')){
        bat::message("本程序需要 pcntl, sysvmsg 扩展,但您的系统没有安装!", 2);
        exit(5);
    }
     
    class bat{
        static private
                $max = 3,
                $total = 0,
                $running = 0,
                $failure = 0,
                $finished = 0,
                $tasks = array(),
                $msg, $msgs = array(),
                $logfile = "/tmp/bat.php.log",
                $childs, $get, $parent,
                $start, $split;
     
        static function main(){
            $i = 1;
            $files = array();
            if($_SERVER["argc"] > 1){
                while($i < $_SERVER["argc"]){
                    switch($_SERVER["argv"][$i++]){
                        case "?":
                        case "/?":
                        case "-?":
                        case "-h":
                        case "--help":
                            self::usage();
                        case "-f":
                        case "--file":
                            $file = $_SERVER["argv"][$i++];
                            if(is_readable($file)){
                                $files[] = $file;
                                continue;
                            }
                            if(is_null($file)){
                                self::message("缺少脚本参数", 1);
                                help(1);
                            }else{
                                self::message("脚本 $file 不在在或不可访问", 2);
                                exit(4);
                            }
                        case "-m":
                        case "--max":
                            if(self::$max = $_SERVER["argv"][$i++]){
                                self::$max = intval(self::$max);
                                if(self::$max >= 1){
                                    continue;
                                }
                                self::message("进程数量应为正整数", 2);
                                exit(8);
                            }
                            self::message("未指定进程数量", 2);
                            exit(7);
                        case "-l":
                        case "--log":
                        case "--logfile":
                            if(self::$logfile = $_SERVER["argv"][$i++]){
                                if(is_dir(self::$logfile)){
                                    self::$logfile .= "/bat.php.log";
                                }
                                if(is_file(self::$logfile)){
                                    if(is_writable(self::$logfile)){
                                        continue;
                                    }
                                }else{
                                    if(is_writable(dirname(self::$logfile))){
                                        continue;
                                    }
                                }
                                self::message("日志目录不可写", 2);
                                exit(9);
                            }
                        case "-v":
                        case "--version":
                            exit(self::version());
                        default :
                            $file = $_SERVER["argv"][$i - 1];
                            if(is_readable($file)){
                                $files[] = $file;
                                continue;
                            }
                            self::message("脚本 $file 不在在或不可访问", 2);
                            exit(4);
                    }
                }
     
                set_time_limit(0);
                error_reporting(8106 & E_ALL);
                ini_set('display_errors', 'Off');
                set_error_handler(array(__CLASS__, 'error'), E_ALL);
                set_exception_handler(array(__CLASS__, 'exception'));
                register_shutdown_function(array(__CLASS__, 'shutdown'));
     
                self::$start = time();
                self::$split = str_repeat('=', 512);
                self::$parent = msg_get_queue(getmypid());
     
                foreach($files as $file){
                    self::inc($file);
                }
     
                self::end();
                exit;
            }
     
            self::usage();
        }
     
        static function run($fun, $arg = null){
            if(is_callable($fun)){
                self::$tasks[] = array($fun, $arg);
            }else{
                throw new Exception("不是函数或不可调用", 9);
            }
        }
     
        static function start(){
            self::$total = count(self::$tasks);
            foreach(self::$tasks as $fun_arg){
                if(self::$max < ++self::$running){
                    self::run_wait();
                }elseif(self::$running == 1){
                    # 清屏并设置光标到第一行
                    $x = intval(`tput lines`);
                    echo str_repeat(" ", $x -1);
                    self::flush('程序开始执行...', 1);
                }
                if($cid = pcntl_fork()){
                    if($cid < 0){
                        throw new Exception("创建进程失败", 3);
                    }
                    self::$childs[$cid] = msg_get_queue($cid);
                }else{
                    ob_start();
                    self::$tasks = array();
                    self::$get = msg_get_queue(getmypid());
                    self::$msg = sprintf("%-6d", getmypid());
                    msg_send(self::$parent, 1, getmypid(), false);
                    call_user_func($fun_arg[0], $fun_arg[1]);
                    exit;
                }
            }
            while(self::$running) self::run_wait();
            self::$tasks = array();
        }
     
        static private function run_wait(){
            $nomsg_interval = time();
    label_wait:
            if(msg_receive(self::$parent, 0, $typ, 8192, $msg, false, MSG_NOERROR | MSG_IPC_NOWAIT)){
                if($typ != 3){
                    if($typ == 1){
                        $msg = sprintf("%-6d%s %s", $msg, date("H:i:s"), '进程启动');
                    }elseif($typ == 4){
    label_child_exit:
                        unset(self::$childs[pcntl_waitpid($msg, &$status)]);
                        if(!pcntl_wifexited($status) || pcntl_wexitstatus($status)){
                            $msg = sprintf("%-6d%s %s", $msg, date("H:i:s"), '进程异常退出');
                            if(pcntl_wifexited($status)){
                                $msg .= ',错误代码:' . pcntl_wexitstatus($status);
                            }
                            self::$failure++;
                        }else{
                            $msg = sprintf("%-6d%s %s", $msg, date("H:i:s"), '进程执行完毕');
                            self::$finished++;
                        }
                        self::flush($msg, $nomsg_interval);
                        self::$running--;
                        return;
                    }else{
                        goto label_wait;
                    }
                }
                $nomsg_interval = time();
                self::flush($msg, $nomsg_interval);
            }else{
                if($nomsg_interval != time()){
                    foreach(self::$childs as $msg => $t){
                        if(!msg_queue_exists($msg)){
                            goto label_child_exit;
                        }
                    }
                    echo "33[0;0H"; $lines = intval(`tput lines`);
                    echo "33[K运行时长:", self::run_time(), ' ', date("Y-m-d H:i:s", self::$start), ' - ', date("Y-m-d H:i:s"), "33[$lines;0H";
                }
                usleep(100000);
            }
            goto label_wait;
        }
     
        static function notify($msg){
            msg_send(self::$parent, 3, self::$msg . date("H:i:s ") . $msg, false);
        }
     
        static function message($msg, $code = 0){
            switch($code){
            case 0:
                echo "33[37m提示:33[0m", $msg, " ";
                break;
            case 1:
                echo "33[33m警告:33[0m", $msg, " ";
                break;
            case 2:
                echo "33[31m错误:33[0m", $msg, " ";
                break;
            }
        }
     
        static function confirm($msg = "确定要继续执行"){
            echo $msg, "(yes/no)?: "; # 暂这样
            return "yes " == fgets(STDIN);
        }
     
        static function help($code = 0){
            echo " 请使用 $_ENV[_] --help 查看帮助! ";
            $code && exit($code);
        }
     
        static function usage(){
            $bat = __CLASS__;
     
            echo ""
            , "Usage: "
            , " $_ENV[_] [options] [-f | --file] <file> "
            , "Options: "
            , " -h | --help     显示本帮助信息 "
            , " -v | --version      查看程序版本信息 "
            , " "
            , " -m | --max <num>  同时执行进程数量,默认 ", self::$max, " 个 "
            , " -l | --log <file> 错误记录日志文件,默认 ", self::$logfile, " "
            , "Information: "
            , " 脚本中调用 $bat::run(fun[, arg]) 来添加任务 "
            , " fun 为要执行的函数名;arg 为传递给这个函数的参数,可省 "
            , " "
            , " 脚本中调用 $bat::start() 来启动子进程执行上面添加的任务 "
            , " 在子进程中,通过调用 $bat::notify(msg) 发送要显示的信息给父进程 "
            , " 在子进程中,程序执行发生错误,要让主进程统计为失败需用 exit(num) 非零返回 "
            ;
            exit;
        }
     
        static function version(){
            return "Version: 0.1 by huye ";
        }
     
        static private function inc($file){
            include $file;
        }
     
        static private function end(){
            $cols = intval(`tput cols`);
            $lines = intval(`tput lines`);
     
            if(self::$total){
                self::flush("执行完毕.", 1);
                echo "33[$lines;{$cols}H33[1C ";
            }
     
            if(is_file(self::$logfile))echo "33[K发生错误:", self::$logfile, " ";
            echo "33[K运行时长:", self::run_time(), ' ', date("Y-m-d H:i:s", self::$start), ' - ', date("Y-m-d H:i:s"), " ";
            echo "33[K执行完毕:已完成任务 ", self::$finished, " 个", self::$failure ? ",失败 " . self::$failure . " 个" : "", self::$total ? "(共 " . self::$total . " 个)" : "", "。 ";
        }
     
        static private function flush($msg, $time){
            $cols = intval(`tput cols`);
            $lines = intval(`tput lines`);
            if($msg){
                $_max = $cols;
                foreach(explode(" ", $msg) as $msg){
                    if($cols < strlen($msg)){# ascii utf8 ascii utf8 ascii ...
                        $tmp = preg_split("#((?:[xe0-xef][x80-xbf]{2})+)#", $msg, 0, PREG_SPLIT_DELIM_CAPTURE);
                        for($i = 0, $l = count($tmp); $i < $l;){
                            $x = strlen($z = $tmp[$i]);
                            if($_max > $x){
                                $_max -= $x;
                                if(++$i >= $l)break;
                                $x = strlen($z = $tmp[$i]) / 3 * 2;
                                if($_max > $x){
                                    $_max -= $x;
                                    $i++; continue;
                                }elseif($_max < $x){
                                    $_max = floor($_max / 2) * 3;
                                    $msg = array_slice($tmp, $i -1);
                                    $msg[0] = '';
                                    $msg[1] = substr($z, $_max);
                                    $tmp[$i] = substr($z, 0, $_max);
                                }else{
                                    $msg = array_slice($tmp, $i + 1);
                                }
                            }elseif($_max < $x){
                                $msg = array_slice($tmp, $i);
                                $msg[0] = substr($z, $_max);
                                $tmp[$i] = substr($z, 0, $_max);
                            }else{
                                $msg = array_slice($tmp, $i);
                                $msg[0] = '';
                            }
                            if(++$i < $l){
                                array_splice($tmp, $i);
                            }
                            if(isset($msg[1])){
                                self::$msgs[] = implode("", $tmp);
                                $msg[0] = "               " . $msg[0];
                                $i = 0; $l = count($msg); $tmp = $msg; $_max = $cols;
                            }elseif(isset($msg[0]) && strlen($msg[0])){
                                self::$msgs[] = implode("", $tmp);
                                if($cols - 15 < strlen($msg[0])){
                                    foreach(str_split($msg[0], $cols - 15) as $tmp){
                                        $tmp = "               " . $tmp;
                                        if($cols == strlen($tmp)){
                                            self::$msgs[] = $tmp;
                                        }else{
                                            $tmp = array($tmp);
                                            break;
                                        }
                                    }
                                }else{
                                    $tmp = array("               " . $msg[0]);
                                }
                                break;
                            }else{
                                break;
                            }
                        }
                        self::$msgs[] = implode("", $tmp);
                    }else{
                        self::$msgs[] = $msg;
                    }
                }
            }else{
                self::$msgs[] = $msg;
            }
     
            static $last_time = 0;
            if($last_time == $time)return true;
            $last_time = $time; # 防止在远程 ssh 的时候刷屏死掉
     
            echo "33[0;0H";
    #       echo "33[K程序信息:", self::version();
            echo "33[K运行时长:", self::run_time(), ' ', date("Y-m-d H:i:s", self::$start), ' - ', date("Y-m-d H:i:s"), " ";
     
            if($lines < 5){
                if($lines < 3) return;
            }else{
                echo $split = substr(self::$split, 0, $cols), " ";
     
                if(($_max = count(self::$msgs) + 4) > $lines){
                    array_splice(self::$msgs, 0, $_max - $lines);
                }elseif($lines > $_max){
                    $split = str_repeat(" 33[K", $lines - $_max) . $split;
                }
     
                echo "33[K", implode(" 33[K", self::$msgs), " ", $split, " ";
            }
     
            $msg = "已完成任务 " . self::$finished . " 个";
            if(self::$failure) $msg .= ",失败 " . self::$failure . " 个";
            if(self::$total) $msg .= "(共 " . self::$total . " 个)";
            echo str_repeat(' ', $cols - strlen(preg_replace("#[xe0-xef][x80-xbf]{2}#", "**", $msg))), $msg, "33[$lines;0H";
        }
     
        static function run_time(){
            $consume = time()
                     - self::$start;
     
            $str = "";
            if($consume >= 86400){
                $str = floor($consume / 86400) . "天";
                $consume = $consume % 86400;
                $zero = true;
            }
            if($consume >= 3600){
                $str .= floor($consume / 3600) . "时";
                $consume = $consume % 3600;
                $zero = true;
            }elseif($consume > 0 && isset($zero)){
                unset($zero);
                $str .= "零";
            }
            if($consume >= 60){
                $str .= floor($consume / 60) . "分";
                $consume = $consume % 60;
                $zero = true;
            }elseif($consume > 0 && isset($zero)){
                unset($zero);
                $str .= "零";
            }
            if($consume > 0){
                $str .= $consume . "秒";
            }elseif($str == ""){
                $str = "0秒";
            }
     
            return $str;
        }
     
        static function error($no, $err, $file, $line){
            if(error_reporting()){
                $log = $no & 1032 ? 'M' : ($no & 514 ? 'W' : ($no & 2048 ? 'M' : 'E'));
                $log = "[" . date("m-d H:i:s") . "] $log $line $file $err ";
                file_put_contents(self::$logfile, $log, FILE_APPEND);
            }
        }
     
        static function shutdown(){
            if($last = error_get_last() and 85 & $last['type']){
                self::error($last['type'], $last['message'], $last['file'], $last['line']);
                self::$get || self::end();
            }
            if(self::$get){
                msg_send(self::$parent, 4, getmypid(), false); # 通知父进程结束
                self::$parent = self::$get; # 同时也防止上一行通知失败
                ob_end_clean();
            }
            msg_remove_queue(self::$parent);
        }
     
        static function exception($e){
            self::error($e->getCode(), $e->getMessage(), $e->getFile(), $e->getLine());
            exit($e->getCode());
        }
    }
     
    bat::main();

  • 相关阅读:
    CH6201走廊泼水节
    P3366 (模板)最小生成树
    linux 基础语法
    django 3.1 序列化讲述
    django 的基础设计
    django 的初始项目结构
    http
    mysql(一)
    反射型xss
    html(四)
  • 原文地址:https://www.cnblogs.com/lemon66/p/4112517.html
Copyright © 2020-2023  润新知