Php cli是守护进程的吗
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Php cli是守护进程的吗相关的知识,希望对你有一定的参考价值。
php_cli模式简介
php-cli是php Command Line Interface的简称,如同它名字的意思,就是php在命令行运行的接口,区别于在Web服务器上运行的php环境(php-cgi, isapi等) 也就是说,php不单可以写前台网页,它还可以用来写后台的程序。 PHP的CLI shell脚本适用于所有的PHP优势,使创建要么支持脚本或系统甚至与GUI应用程序的服务端!——注:windows和linux下都支持php_cli模式
PHP-cli应用场景:
1.多线程应用
这方面的好处,引用鸟哥的话:
优点:
1. 使用多进程, 子进程结束以后, 内核会负责回收资源
2. 使用多进程,子进程异常退出不会导致整个进程Thread退出. 父进程还有机会重建流程.
3. 一个常驻主进程, 只负责任务分发, 逻辑更清楚.
php的多线程—没错就是php多线程应用,虽然大家都普遍认为php没有多线程(curl属于模拟多线程而不是真实的),但是在php_cli模式下的php彻底的是属于多线程。这个时候php属于linux的一个守护进程。 在本人之前写过的《PHP多线程批量采集下载美女图片(续)》的时候在采集程序里虽然使用curl来模拟多线程,但是在浏览器执行的时候也是会遇到执行超时或内存abort而导致程序中断,(要尝试几次才可以彻底成功),但是如果在php-cli模式下执行,你就会发现这个程序执行的很快,php多线程执行的优势被彻底表现出来了.
备注:这种多线程方式不是很成熟,不适合大规模的生成应用,偶尔使用还是可以的
2.定时执行php程序
利用linux的cron方式,那么这个方式是如何定时执行php程序?请看下文
3.开发桌面程序
你可以做您的Windows或Linux中使用PHP的图形用户界面(GUI)应用!所有你需要的是PHP的命令行接口和一包GTK。这将允许建立真正的便携式图形用户界面应用程序(呵呵,之前只是知道php可以做桌面程序,现在才知道是使用php_cli模式),并且不需要学习别的。
4.编写PHP的shell脚本
如果你不会bash shell或者Perl等的使用,但是你又需要一些脚本去执行的时候,怎么办?这个时候你完全可以使用你熟悉的php编写shell脚本,这个时候你是不是突然感觉PHP是不是太强大了!—–真正做到一种语言,到处开发!
PHP_CLI使用方法
win下面的执行方法:
假设php.exe 在D:xamppphp在dos命令在可以这个执行:
复制代码 代码如下:D:\\xamppphpphp.exe D:\\xampphtdocstest.php
就可以执行test.php这个文件了 。这里推荐win平台下xampp集成环境,真正比wamp强大N倍,这个集成包可以直接进入dos模式。
linux下php_cli使用
首先找到你安装php的路径,以我为例:
php安装在路径/usr/local/php/bin/php下
复制代码 代码如下:/usr/local/php/bin/php /usr/local/apache/htdocs/a.php
就可以执行a。php文件
PHP_CLI编程需知
如何检测环境支持php_cli模式?
复制代码 代码如下:
<?php
//方法1
if (PHP_SAPI === 'cli')
// ...
//方法2
if (php_sapi_name() === 'cli')
// ...
PHP_ClI如何接收参数?
默认情况下/usr/local/php/bin/php接收参数是$argv,这个变量是固定的!在php文件中var_dump($argv);
得到下面结果:
可以写个简单的处理函数把这个方式转化为大家常用的GET/post的参数模式。
简单代码:
复制代码 代码如下:
<?php
function parseArgs($argv)
array_shift($argv);
$out = array();
foreach ($argv as $arg)
if (substr($arg,0,2) == '--')
$eqPos = strpos($arg,'=');
if ($eqPos === false)
$key = substr($arg,2);
$out[$key] = isset($out[$key]) ? $out[$key] : true;
else
$key = substr($arg,2,$eqPos-2);
$out[$key] = substr($arg,$eqPos+1);
else if (substr($arg,0,1) == '-')
if (substr($arg,2,1) == '=')
$key = substr($arg,1,1);
$out[$key] = substr($arg,3);
else
$chars = str_split(substr($arg,1));
foreach ($chars as $char)
$key = $char;
$out[$key] = isset($out[$key]) ? $out[$key] : true;
else
$out[] = $arg;
return $out;
var_dump($argv);
var_dump(parseArgs($argv));exit;
执行结果:
当然实现的方法不止一个,大家可以尝试其他方法实现!
例外关于php的cli还有很多参数可以加入:具体可以参考:http://php.net/manual/en/features.commandline.php
关于定时执行
cron是一个linux下的定时执行工具,可以在无需人工干预的情况下运行作业,周期性作业,比如备份数据 打开/etc/crontab,添加:
复制代码 代码如下:
/usr/bin/php -f /data/htdocs/test.php
php写守护进程(Daemon)
守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程是一种很有用的进程。php也可以实现守护进程的功能。
1、基本概念
进程
每个进程都有一个父进程,子进程退出,父进程能得到子进程退出的状态。
进程组
每个进程都属于一个进程组,每个进程组都有一个进程组号,该号等于该进程组组长的PID
2、守护编程要点
1. 在后台运行。
为避免挂起控制终端将Daemon放入后台执行。方法是在进程中调用fork使父进程终止,让Daemon在子进程中后台执行。 if($pid=pcntl_fork()) exit(0);//是父进程,结束父进程,子进程继续
2. 脱离控制终端,登录会话和进程组
有必要先介绍一下Linux中的进程与控制终端,登录会话和进程组之间的关系:进程属于一个进程组,进程组号(GID)就是进程组长的进程号(PID)。登录会话可以包含多个进程组。这些进程组共享一个控制终端。这个控制终端通常是创建进程的登录终 端。 控制终端,登录会话和进程组通常是从父进程继承下来的。我们的目的就是要摆脱它们,使之不受它们的影响。方法是在第1点的基础上,调用setsid()使进程成为会话组长: posix_setsid();
说明:当进程是会话组长时setsid()调用失败。但第一点已经保证进程不是会话组长。setsid()调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登录会话和进程组脱离。由于会话过程对控制终端的独占性,进程同时与控制终端脱离。
3. 禁止进程重新打开控制终端
现在,进程已经成为无终端的会话组长。但它可以重新申请打开一个控制终端。可以通过使进程不再成为会话组长来禁止进程重新打开控制终端: if($pid=pcntl_fork()) exit(0);//结束第一子进程,第二子进程继续(第二子进程不再是会话组长)
4. 关闭打开的文件描述符
进程从创建它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,造成进程所在的文件系统无法卸下以及引起无法预料的错误。按如下方法关闭它们:
fclose(STDIN),fclose(STDOUT),fclose(STDERR)关闭标准输入输出与错误显示。
5. 改变当前工作目录
进程活动时,其工作目录所在的文件系统不能卸下。一般需要将工作目录改变到根目录。对于需要转储核心,写运行日志的进程将工作目录改变到特定目录如chdir("/")
6. 重设文件创建掩模
进程从创建它的父进程那里继承了文件创建掩模。它可能修改守护进程所创建的文件的存取位。为防止这一点,将文件创建掩模清除:umask(0);
7. 处理SIGCHLD信号
处理SIGCHLD信号并不是必须的。但对于某些进程,特别是服务器进程往往在请求到来时生成子进程处理请求。如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源。如果父进程等待子进程结束,将增加父进程的负担,影 响服务器进程的并发性能。在Linux下可以简单地将SIGCHLD信号的操作设为SIG_IGN。 signal(SIGCHLD,SIG_IGN);
这样,内核在子进程结束时不会产生僵尸进程。这一点与BSD4不同,BSD4下必须显式等待子进程结束才能释放僵尸进程。关于信号的问题请参考Linux 信号说明列表
3、实例
php] view plain copy print? <?php /** *@author [email protected] *@date 2013-07-25 * 后台脚本控制类 */ class DaemonCommand{ private $info_dir="/tmp"; private $pid_file=""; private $terminate=false; //是否中断 private $workers_count=0; private $gc_enabled=null; private $workers_max=8; //最多运行8个进程 public function __construct($is_sington=false,$user=‘nobody‘,$output="/dev/null"){ $this->is_sington=$is_sington; //是否单例运行,单例运行会在tmp目录下建立一个唯一的PID $this->user=$user;//设置运行的用户 默认情况下nobody $this->output=$output; //设置输出的地方 $this->checkPcntl(); } //检查环境是否支持pcntl支持 public function checkPcntl(){ if ( ! function_exists(‘pcntl_signal_dispatch‘)) { // PHP < 5.3 uses ticks to handle signals instead of pcntl_signal_dispatch // call sighandler only every 10 ticks declare(ticks = 10); } // Make sure PHP has support for pcntl if ( ! function_exists(‘pcntl_signal‘)) { $message = ‘PHP does not appear to be compiled with the PCNTL extension. This is neccesary for daemonization‘; $this->_log($message); throw new Exception($message); } //信号处理 pcntl_signal(SIGTERM, array(__CLASS__, "signalHandler"),false); pcntl_signal(SIGINT, array(__CLASS__, "signalHandler"),false); pcntl_signal(SIGQUIT, array(__CLASS__, "signalHandler"),false); // Enable PHP 5.3 garbage collection if (function_exists(‘gc_enable‘)) { gc_enable(); $this->gc_enabled = gc_enabled(); } } // daemon化程序 public function daemonize(){ global $stdin, $stdout, $stderr; global $argv; set_time_limit(0); // 只允许在cli下面运行 if (php_sapi_name() != "cli"){ die("only run in command line mode\n"); } // 只能单例运行 if ($this->is_sington==true){ $this->pid_file = $this->info_dir . "/" .__CLASS__ . "_" . substr(basename($argv[0]), 0, -4) . ".pid"; $this->checkPidfile(); } umask(0); //把文件掩码清0 if (pcntl_fork() != 0){ //是父进程,父进程退出 exit(); } posix_setsid();//设置新会话组长,脱离终端 if (pcntl_fork() != 0){ //是第一子进程,结束第一子进程 exit(); } chdir("/"); //改变工作目录 $this->setUser($this->user) or die("cannot change owner"); //关闭打开的文件描述符 fclose(STDIN); fclose(STDOUT); fclose(STDERR); $stdin = fopen($this->output, ‘r‘); $stdout = fopen($this->output, ‘a‘); $stderr = fopen($this->output, ‘a‘); if ($this->is_sington==true){ $this->createPidfile(); } } //--检测pid是否已经存在 public function checkPidfile(){ if (!file_exists($this->pid_file)){ return true; } $pid = file_get_contents($this->pid_file); $pid = intval($pid); if ($pid > 0 && posix_kill($pid, 0)){ $this->_log("the daemon process is already started"); } else { $this->_log("the daemon proces end abnormally, please check pidfile " . $this->pid_file); } exit(1); } //----创建pid public function createPidfile(){ if (!is_dir($this->info_dir)){ mkdir($this->info_dir); } $fp = fopen($this->pid_file, ‘w‘) or die("cannot create pid file"); fwrite($fp, posix_getpid()); fclose($fp); $this->_log("create pid file " . $this->pid_file); } //设置运行的用户 public function setUser($name){ $result = false; if (empty($name)){ return true; } $user = posix_getpwnam($name); if ($user) { $uid = $user[‘uid‘]; $gid = $user[‘gid‘]; $result = posix_setuid($uid); posix_setgid($gid); } return $result; } //信号处理函数 public function signalHandler($signo){ switch($signo){ //用户自定义信号 case SIGUSR1: //busy if ($this->workers_count < $this->workers_max){ $pid = pcntl_fork(); if ($pid > 0){ $this->workers_count ++; } } break; //子进程结束信号 case SIGCHLD: while(($pid=pcntl_waitpid(-1, $status, WNOHANG)) > 0){ $this->workers_count --; } break; //中断进程 case SIGTERM: case SIGHUP: case SIGQUIT: $this->terminate = true; break; default: return false; } } /** *开始开启进程 *$count 准备开启的进程数 */ public function start($count=1){ $this->_log("daemon process is running now"); pcntl_signal(SIGCHLD, array(__CLASS__, "signalHandler"),false); // if worker die, minus children num while (true) { if (function_exists(‘pcntl_signal_dispatch‘)){ pcntl_signal_dispatch(); } if ($this->terminate){ break; } $pid=-1; if($this->workers_count<$count){ $pid=pcntl_fork(); } if($pid>0){ $this->workers_count++; }elseif($pid==0){ // 这个符号表示恢复系统对信号的默认处理 pcntl_signal(SIGTERM, SIG_DFL); pcntl_signal(SIGCHLD, SIG_DFL); if(!empty($this->jobs)){ while($this->jobs[‘runtime‘]){ if(empty($this->jobs[‘argv‘])){ call_user_func($this->jobs[‘function‘],$this->jobs[‘argv‘]); }else{ call_user_func($this->jobs[‘function‘]); } $this->jobs[‘runtime‘]--; sleep(2); } exit(); } return; }else{ sleep(2); } } $this->mainQuit(); exit(0); } //整个进程退出 public function mainQuit(){ if (file_exists($this->pid_file)){ unlink($this->pid_file); $this->_log("delete pid file " . $this->pid_file); } $this->_log("daemon process exit now"); posix_kill(0, SIGKILL); exit(0); } // 添加工作实例,目前只支持单个job工作 public function setJobs($jobs=array()){ if(!isset($jobs[‘argv‘])||empty($jobs[‘argv‘])){ $jobs[‘argv‘]=""; } if(!isset($jobs[‘runtime‘])||empty($jobs[‘runtime‘])){ $jobs[‘runtime‘]=1; } if(!isset($jobs[‘function‘])||empty($jobs[‘function‘])){ $this->log("你必须添加运行的函数!"); } $this->jobs=$jobs; } //日志处理 private function _log($message){ printf("%s\t%d\t%d\t%s\n", date("c"), posix_getpid(), posix_getppid(), $message); } } //调用方法1 $daemon=new DaemonCommand(true); $daemon->daemonize(); $daemon->start(2);//开启2个子进程工作 work(); //调用方法2 $daemon=new DaemonCommand(true); $daemon->daemonize(); $daemon->addJobs(array(‘function‘=>‘work‘,‘argv‘=>‘‘,‘runtime‘=>1000));//function 要运行的函数,argv运行函数的参数,runtime运行的次数 $daemon->start(2);//开启2个子进程工作 //具体功能的实现 function work(){ echo "测试1"; } ?>
以上是关于Php cli是守护进程的吗的主要内容,如果未能解决你的问题,请参考以下文章