如何写php守护进程(Daemon)
守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程是一种很有用的进程。php也可以实现守护进程的功能。
一、基本概念
进程: 每个进程都有一个父进程,子进程退出,父进程能得到子进程退出的状态。
进程组:每个进程都属于一个进程组,每个进程组都有一个进程组号,该号等于该进程组组长的PID
二、守护编程要点
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 信号说明列表
三、实例
<?php * 后台脚本控制类 */ 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 _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 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_count0){ $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"; } ?>

종종 키워드와 추적 매개 변수로 혼란스러워하는 긴 URL은 방문자를 방해 할 수 있습니다. URL 단축 스크립트는 솔루션을 제공하여 소셜 미디어 및 기타 플랫폼에 이상적인 간결한 링크를 만듭니다. 이 스크립트는 개별 웹 사이트 a에 유용합니다

Instagram은 2012 년 Facebook에서 유명한 인수에 이어 타사 사용을 위해 두 개의 API 세트를 채택했습니다. Instagram Graph API 및 Instagram Basic Display API입니다. 개발자는

Laravel은 직관적 인 플래시 방법을 사용하여 임시 세션 데이터 처리를 단순화합니다. 응용 프로그램에 간단한 메시지, 경고 또는 알림을 표시하는 데 적합합니다. 데이터는 기본적으로 후속 요청에만 지속됩니다. $ 요청-

이것은 Laravel 백엔드가있는 React Application을 구축하는 데있어 시리즈의 두 번째이자 마지막 부분입니다. 이 시리즈의 첫 번째 부분에서는 기본 제품 목록 응용 프로그램을 위해 Laravel을 사용하여 편안한 API를 만들었습니다. 이 튜토리얼에서는 Dev가 될 것입니다

Laravel은 간결한 HTTP 응답 시뮬레이션 구문을 제공하여 HTTP 상호 작용 테스트를 단순화합니다. 이 접근법은 테스트 시뮬레이션을보다 직관적으로 만들면서 코드 중복성을 크게 줄입니다. 기본 구현은 다양한 응답 유형 단축키를 제공합니다. Illuminate \ support \ Facades \ http를 사용하십시오. http :: 가짜 ([ 'google.com'=> 'Hello World', 'github.com'=> [ 'foo'=> 'bar'], 'forge.laravel.com'=>

PHP 클라이언트 URL (CURL) 확장자는 개발자를위한 강력한 도구이며 원격 서버 및 REST API와의 원활한 상호 작용을 가능하게합니다. PHP CURL은 존경받는 다중 프로모토콜 파일 전송 라이브러리 인 Libcurl을 활용하여 효율적인 execu를 용이하게합니다.

고객의 가장 긴급한 문제에 실시간 인스턴트 솔루션을 제공하고 싶습니까? 라이브 채팅을 통해 고객과 실시간 대화를 나누고 문제를 즉시 해결할 수 있습니다. 그것은 당신이 당신의 관습에 더 빠른 서비스를 제공 할 수 있도록합니다.

2025 PHP Landscape Survey는 현재 PHP 개발 동향을 조사합니다. 개발자와 비즈니스에 대한 통찰력을 제공하는 프레임 워크 사용, 배포 방법 및 과제를 탐색합니다. 이 조사는 현대 PHP Versio의 성장을 예상합니다


핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

AI Hentai Generator
AI Hentai를 무료로 생성하십시오.

인기 기사

뜨거운 도구

SublimeText3 Linux 새 버전
SublimeText3 Linux 최신 버전

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

Atom Editor Mac 버전 다운로드
가장 인기 있는 오픈 소스 편집기

WebStorm Mac 버전
유용한 JavaScript 개발 도구

ZendStudio 13.5.1 맥
강력한 PHP 통합 개발 환경
