如何寫php守護(hù)進(jìn)程(Daemon)_php技巧
May 16, 2016 pm 08:01 PM守護(hù)進(jìn)程(Daemon)是運(yùn)行在后臺的一種特殊進(jìn)程。它獨(dú)立于控制終端并且周期性地執(zhí)行某種任務(wù)或等待處理某些發(fā)生的事件。守護(hù)進(jìn)程是一種很有用的進(jìn)程。php也可以實(shí)現(xiàn)守護(hù)進(jìn)程的功能。
一、基本概念
進(jìn)程:?每個(gè)進(jìn)程都有一個(gè)父進(jìn)程,子進(jìn)程退出,父進(jìn)程能得到子進(jìn)程退出的狀態(tài)。
進(jìn)程組:每個(gè)進(jìn)程都屬于一個(gè)進(jìn)程組,每個(gè)進(jìn)程組都有一個(gè)進(jìn)程組號,該號等于該進(jìn)程組組長的PID
二、守護(hù)編程要點(diǎn)
1. 在后臺運(yùn)行?????
???????? 為避免掛起控制終端將Daemon放入后臺執(zhí)行。方法是在進(jìn)程中調(diào)用fork使父進(jìn)程終止,讓Daemon在子進(jìn)程中后臺執(zhí)行。 if($pid=pcntl_fork()) exit(0);//是父進(jìn)程,結(jié)束父進(jìn)程,子進(jìn)程繼續(xù)
2. 脫離控制終端,登錄會話和進(jìn)程組?
?????? 有必要先介紹一下Linux中的進(jìn)程與控制終端,登錄會話和進(jìn)程組之間的關(guān)系:進(jìn)程屬于一個(gè)進(jìn)程組,進(jìn)程組號(GID)就是進(jìn)程組長的進(jìn)程號(PID)。登錄會話可以包含多個(gè)進(jìn)程組。這些進(jìn)程組共享一個(gè)控制終端。這個(gè)控制終端通常是創(chuàng)建進(jìn)程的登錄終? 端。 控制終端,登錄會話和進(jìn)程組通常是從父進(jìn)程繼承下來的。我們的目的就是要擺脫它們,使之不受它們的影響。方法是在第1點(diǎn)的基礎(chǔ)上,調(diào)用setsid()使進(jìn)程成為會話組長: posix_setsid();
??????? 說明:當(dāng)進(jìn)程是會話組長時(shí)setsid()調(diào)用失敗。但第一點(diǎn)已經(jīng)保證進(jìn)程不是會話組長。setsid()調(diào)用成功后,進(jìn)程成為新的會話組長和新的進(jìn)程組長,并與原來的登錄會話和進(jìn)程組脫離。由于會話過程對控制終端的獨(dú)占性,進(jìn)程同時(shí)與控制終端脫離。
3. 禁止進(jìn)程重新打開控制終端
??????? 現(xiàn)在,進(jìn)程已經(jīng)成為無終端的會話組長。但它可以重新申請打開一個(gè)控制終端??梢酝ㄟ^使進(jìn)程不再成為會話組長來禁止進(jìn)程重新打開控制終端: if($pid=pcntl_fork()) exit(0);//結(jié)束第一子進(jìn)程,第二子進(jìn)程繼續(xù)(第二子進(jìn)程不再是會話組長)
4. 關(guān)閉打開的文件描述符
??????? 進(jìn)程從創(chuàng)建它的父進(jìn)程那里繼承了打開的文件描述符。如不關(guān)閉,將會浪費(fèi)系統(tǒng)資源,造成進(jìn)程所在的文件系統(tǒng)無法卸下以及引起無法預(yù)料的錯(cuò)誤。按如下方法關(guān)閉它們:
??????? fclose(STDIN),fclose(STDOUT),fclose(STDERR)關(guān)閉標(biāo)準(zhǔn)輸入輸出與錯(cuò)誤顯示。
5. 改變當(dāng)前工作目錄
??????? 進(jìn)程活動時(shí),其工作目錄所在的文件系統(tǒng)不能卸下。一般需要將工作目錄改變到根目錄。對于需要轉(zhuǎn)儲核心,寫運(yùn)行日志的進(jìn)程將工作目錄改變到特定目錄如chdir("/")
6. 重設(shè)文件創(chuàng)建掩模
??????? 進(jìn)程從創(chuàng)建它的父進(jìn)程那里繼承了文件創(chuàng)建掩模。它可能修改守護(hù)進(jìn)程所創(chuàng)建的文件的存取位。為防止這一點(diǎn),將文件創(chuàng)建掩模清除:umask(0);
7. 處理SIGCHLD信號
??????? 處理SIGCHLD信號并不是必須的。但對于某些進(jìn)程,特別是服務(wù)器進(jìn)程往往在請求到來時(shí)生成子進(jìn)程處理請求。如果父進(jìn)程不等待子進(jìn)程結(jié)束,子進(jìn)程將成為僵尸進(jìn)程(zombie)從而占用系統(tǒng)資源。如果父進(jìn)程等待子進(jìn)程結(jié)束,將增加父進(jìn)程的負(fù)擔(dān),影? 響服務(wù)器進(jìn)程的并發(fā)性能。在Linux下可以簡單地將SIGCHLD信號的操作設(shè)為SIG_IGN。 signal(SIGCHLD,SIG_IGN);
??????? 這樣,內(nèi)核在子進(jìn)程結(jié)束時(shí)不會產(chǎn)生僵尸進(jìn)程。這一點(diǎn)與BSD4不同,BSD4下必須顯式等待子進(jìn)程結(jié)束才能釋放僵尸進(jìn)程。關(guān)于信號的問題請參考Linux 信號說明列表
三、實(shí)例
<?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; //最多運(yùn)行8個(gè)進(jìn)程 public function __construct($is_sington=false,$user='nobody',$output="/dev/null"){ $this->is_sington=$is_sington; //是否單例運(yùn)行,單例運(yùn)行會在tmp目錄下建立一個(gè)唯一的PID $this->user=$user;//設(shè)置運(yùn)行的用戶 默認(rèn)情況下nobody $this->output=$output; //設(shè)置輸出的地方 $this->checkPcntl(); } //檢查環(huán)境是否支持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下面運(yùn)行 if (php_sapi_name() != "cli"){ die("only run in command line mode\n"); } // 只能單例運(yù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){ //是父進(jìn)程,父進(jìn)程退出 exit(); } posix_setsid();//設(shè)置新會話組長,脫離終端 if (pcntl_fork() != 0){ //是第一子進(jìn)程,結(jié)束第一子進(jìn)程 exit(); } chdir("/"); //改變工作目錄 $this->setUser($this->user) or die("cannot change owner"); //關(guān)閉打開的文件描述符 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是否已經(jīng)存在 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); } //----創(chuàng)建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); } //設(shè)置運(yùn)行的用戶 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; } //信號處理函數(shù) 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; //子進(jìn)程結(jié)束信號 case SIGCHLD: while(($pid=pcntl_waitpid(-1, $status, WNOHANG)) > 0){ $this->workers_count --; } break; //中斷進(jìn)程 case SIGTERM: case SIGHUP: case SIGQUIT: $this->terminate = true; break; default: return false; } } /** *開始開啟進(jìn)程 *$count 準(zhǔn)備開啟的進(jìn)程數(shù) */ 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){ // 這個(gè)符號表示恢復(fù)系統(tǒng)對信號的默認(rèn)處理 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); } //整個(gè)進(jìn)程退出 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); } // 添加工作實(shí)例,目前只支持單個(gè)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("你必須添加運(yùn)行的函數(shù)!"); } $this->jobs=$jobs; } //日志處理 private function _log($message){ printf("%s\t%d\t%d\t%s\n", date("c"), posix_getpid(), posix_getppid(), $message); } } //調(diào)用方法1 $daemon=new DaemonCommand(true); $daemon->daemonize(); $daemon->start(2);//開啟2個(gè)子進(jìn)程工作 work(); //調(diào)用方法2 $daemon=new DaemonCommand(true); $daemon->daemonize(); $daemon->addJobs(array('function'=>'work','argv'=>'','runtime'=>1000));//function 要運(yùn)行的函數(shù),argv運(yùn)行函數(shù)的參數(shù),runtime運(yùn)行的次數(shù) $daemon->start(2);//開啟2個(gè)子進(jìn)程工作 //具體功能的實(shí)現(xiàn) function work(){ echo "測試1"; } ?>
以上就是關(guān)于php守護(hù)進(jìn)程的相關(guān)介紹,希望對大家的學(xué)習(xí)有所幫助。

Hot AI Tools

Undress AI Tool
Undress images for free

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics

AgeneratorinPHPisamemory-efficientwaytoiterateoverlargedatasetsbyyieldingvaluesoneatatimeinsteadofreturningthemallatonce.1.Generatorsusetheyieldkeywordtoproducevaluesondemand,reducingmemoryusage.2.Theyareusefulforhandlingbigloops,readinglargefiles,or

In PHP, you can use square brackets or curly braces to obtain string specific index characters, but square brackets are recommended; the index starts from 0, and the access outside the range returns a null value and cannot be assigned a value; mb_substr is required to handle multi-byte characters. For example: $str="hello";echo$str[0]; output h; and Chinese characters such as mb_substr($str,1,1) need to obtain the correct result; in actual applications, the length of the string should be checked before looping, dynamic strings need to be verified for validity, and multilingual projects recommend using multi-byte security functions uniformly.

To prevent session hijacking in PHP, the following measures need to be taken: 1. Use HTTPS to encrypt the transmission and set session.cookie_secure=1 in php.ini; 2. Set the security cookie attributes, including httponly, secure and samesite; 3. Call session_regenerate_id(true) when the user logs in or permissions change to change to change the SessionID; 4. Limit the Session life cycle, reasonably configure gc_maxlifetime and record the user's activity time; 5. Prohibit exposing the SessionID to the URL, and set session.use_only

You can use substr() or mb_substr() to get the first N characters in PHP. The specific steps are as follows: 1. Use substr($string,0,N) to intercept the first N characters, which is suitable for ASCII characters and is simple and efficient; 2. When processing multi-byte characters (such as Chinese), mb_substr($string,0,N,'UTF-8'), and ensure that mbstring extension is enabled; 3. If the string contains HTML or whitespace characters, you should first use strip_tags() to remove the tags and trim() to clean the spaces, and then intercept them to ensure the results are clean.

The urlencode() function is used to encode strings into URL-safe formats, where non-alphanumeric characters (except -, _, and .) are replaced with a percent sign followed by a two-digit hexadecimal number. For example, spaces are converted to signs, exclamation marks are converted to!, and Chinese characters are converted to their UTF-8 encoding form. When using, only the parameter values ??should be encoded, not the entire URL, to avoid damaging the URL structure. For other parts of the URL, such as path segments, the rawurlencode() function should be used, which converts the space to . When processing array parameters, you can use http_build_query() to automatically encode, or manually call urlencode() on each value to ensure safe transfer of data. just

There are two main ways to get the last N characters of a string in PHP: 1. Use the substr() function to intercept through the negative starting position, which is suitable for single-byte characters; 2. Use the mb_substr() function to support multilingual and UTF-8 encoding to avoid truncating non-English characters; 3. Optionally determine whether the string length is sufficient to handle boundary situations; 4. It is not recommended to use strrev() substr() combination method because it is not safe and inefficient for multi-byte characters.

To set and get session variables in PHP, you must first always call session_start() at the top of the script to start the session. 1. When setting session variables, use $_SESSION hyperglobal array to assign values ??to specific keys, such as $_SESSION['username']='john_doe'; it can store strings, numbers, arrays and even objects, but avoid storing too much data to avoid affecting performance. 2. When obtaining session variables, you need to call session_start() first, and then access the $_SESSION array through the key, such as echo$_SESSION['username']; it is recommended to use isset() to check whether the variable exists to avoid errors

Key methods to prevent SQL injection in PHP include: 1. Use preprocessing statements (such as PDO or MySQLi) to separate SQL code and data; 2. Turn off simulated preprocessing mode to ensure true preprocessing; 3. Filter and verify user input, such as using is_numeric() and filter_var(); 4. Avoid directly splicing SQL strings and use parameter binding instead; 5. Turn off error display in the production environment and record error logs. These measures comprehensively prevent the risk of SQL injection from mechanisms and details.
