PHP 編寫守護程序
PHP 建立守護程序
- 程序根據狀態可以分為三種程序,守護程序,殭屍程序,孤兒程序。今天我們著重來分析下守護程序。
守護程序
簡介
守護程序 (daemon) 是一類在後臺執行的特殊程序,用於執行特定的系統任務。很多守護程序在系統引導的時候啟動,並且一直執行直到系統關閉。另一些只在需要的時候才啟動,完成任務後就自動結束。
建立步驟
- 建立子程序,終止父程序
由於守護程序是脫離控制終端的,因此首先建立子程序,終止父程序,使得程式在 shell 終端裡造成一個已經執行完畢的假象。之後所有的工作都在子程序中完成,而使用者在 shell 終端裡則可以執行其他的命令,從而使得程式以殭屍程序形式執行,在形式 I 上做到了與控制終端的脫離。 - 在子程序中建立新會話
這個步驟是建立守護程序中最重要的一步,在這裡使用的是系統函式 setsid。setsid 函式用於建立一個新的會話,並擔任該會話組的組長。呼叫 setsid 的三個作用:讓程序擺脫原會話的控制、讓程序擺脫原程序組的控制和讓程序擺脫原控制終端的控制。
在呼叫 fork 函式時,子程序全盤拷貝父程序的會話期 (session,是一個或多個程序組的集合)、程序組、控制終端等,雖然父程序退出了,但原先的會話期、程序組、控制終端等並沒有改變,因此,那還不是真正意義上使兩者獨立開來。setsid 函式能夠使程序完全獨立出來,從而脫離所有其他程序的控制。 - 改變工作目錄
使用 fork 建立的子程序也繼承了父程序的當前工作目錄。由於在程序執行過程中,當前目錄所在的檔案系統不能解除安裝,因此,把當前工作目錄換成其他的路徑,如 “/” 或 “/tmp” 等。改變工作目錄的常見函式是 chdir。 - 重設檔案建立掩碼
檔案建立掩碼是指遮蔽掉檔案建立時的對應位。由於使用 fork 函式新建的子程序繼承了父程序的檔案建立掩碼,這就給該子程序使用檔案帶來了諸多的麻煩。因此,把檔案建立掩碼設定為 0,可以大大增強該守護程序的靈活性。設定檔案建立掩碼的函式是 umask,通常的使用方法為 umask (0)。 - 關閉檔案描述符
用 fork 新建的子程序會從父程序那裡繼承一些已經打開了的檔案。這些被開啟的檔案可能永遠不會被守護程序讀或寫,但它們一樣消耗系統資源,可能導致所在的檔案系統無法解除安裝。
直接上程式碼
注:執行環境是 linux 系統,並且要在 cli 模式下執行。
檔名:deamon.php
<?php
/**
* User: streetlamp
* Date: 2019/1/9
* Time: 15:14
*/
class Deamon{
protected $_pidFile;
public function __construct(){
$this->_pidFile = '/var/www/html/queue/public/pid.log';
$this->_checkPcntl();
}
/**
* 建立守護程序核心函式
* @return string|void
*/
private function _demonize(){
if (php_sapi_name() != 'cli') {
die('Should run in CLI');
}
//建立子程序
$pid = pcntl_fork();
if ($pid == -1) {
return 'fork faile';
} elseif ($pid) {
//終止父程序
exit('parent process');
}
//在子程序中建立新的會話
if (posix_setsid() === -1) {
die('Could not detach');
}
//改變工作目錄
chdir('/');
//重設檔案建立的掩碼
umask(0);
$fp = fopen($this->_pidFile, 'w') or die("Can't create pid file");
//把當前程序的id寫入到檔案中
fwrite($fp, posix_getpid());
fclose($fp);
//關閉檔案描述符
fclose(STDIN);
fclose(STDOUT);
fclose(STDERR);
//執行守護程序的邏輯
$this->job();
return;
}
/**
* 守護程序的任務
*/
private function job(){
//TODO 你的守護經常需要執行的任務
while (true) {
file_put_contents('/var/www/html/queue/public/job.log', 'job' . PHP_EOL, FILE_APPEND);
sleep(5);
}
}
/**
* 獲取守護程序的id
* @return int
*/
private function _getPid(){
//判斷存放守護程序id的檔案是否存在
if (!file_exists($this->_pidFile)) {
return 0;
}
$pid = intval(file_get_contents($this->_pidFile));
if (posix_kill($pid, SIG_DFL)) {//判斷該程序是否正常執行中
return $pid;
} else {
unlink($this->_pidFile);
return 0;
}
}
/**
* 判斷pcntl拓展
*/
private function _checkPcntl(){
!function_exists('pcntl_signal') && die('Error:Need PHP Pcntl extension!');
}
private function _message($message){
printf("%s %d %d %s" . PHP_EOL, date("Y-m-d H:i:s"), posix_getpid(), posix_getppid(), $message);
}
/**
* 開啟守護程序
*/
private function start(){
if ($this->_getPid() > 0) {
$this->_message('Running');
} else {
$this->_demonize();
$this->_message('Start');
}
}
/**
* 停止守護程序
*/
private function stop(){
$pid = $this->_getPid();
if ($pid > 0) {
//通過向程序id傳送終止訊號來停止程序
posix_kill($pid, SIGTERM);
unlink($this->_pidFile);
echo 'Stoped' . PHP_EOL;
} else {
echo "Not Running" . PHP_EOL;
}
}
private function status(){
if ($this->_getPid() > 0) {
$this->_message('Is Running');
} else {
echo 'Not Running' . PHP_EOL;
}
}
public function run($argv){
$param = is_array($argv) && count($argv) == 2 ? $argv[1] : null;
switch ($param) {
case 'start':
$this->start();
break;
case 'stop':
$this->stop();
break;
case 'status':
$this->status();
break;
default:
echo "Argv start|stop|status " . PHP_EOL;
break;
}
}
}
$deamon = new \Deamon();
$deamon->run($argv);
執行方式
- 開啟守護程序:php demon.php start
- 停止守護程序:php demon.php stop
- 檢視守護程序的狀態:php demon.php status
以上內容希望幫助到大家,更多PHP大廠PDF面試文件,PHP進階架構視訊資料,PHP精彩好文免費獲取可以關注公眾號:PHP開源社群,或者訪問:
「其他文章」
- 基於Nginx的負載均衡原理與實戰
- PHP控制反轉(IOC)和依賴注入(DI)
- 深入理解PHP7核心之Reference
- php中類的不定引數使用示例
- php單例模式的常見應用場景
- laravel 配置MySQL讀寫分離
- PHP的垃圾回收機制(建議收藏)
- 【shell指令碼】字串和陣列的使用
- PHP-FPM是什麼東東?
- PHP 編寫守護程序
- PHP命令列指令碼接收傳入引數的三種方式
- php專案中類的自動載入
- 複習下Linux去除重複項命令uniq
- 深入理解PHP核心:變數及資料型別
- Swoole協程與傳統fpm同步模式比較
- PHP中Session ID的實現原理
- 寫一手好SQL,該從哪裡入手最好?
- PHP命令列指令碼接收傳入引數的三種方式
- 使用 Shell 在多伺服器上批量操作
- PHP實現使用者異地登入提醒功能的方法