PHP實(shí)現(xiàn)守護(hù)進(jìn)程的示例代碼
前言
寫 PHP CLI 程序的老司機(jī)們可能經(jīng)常會(huì)寫一些常駐進(jìn)程,比如消息隊(duì)列消費(fèi)者進(jìn)程,這些進(jìn)程會(huì)一直運(yùn)行,除非要發(fā)版,不然一般不會(huì)重啟的,所以程序程序是不可能由我們通過(guò) ssh 登錄到服務(wù)器上通過(guò)終端來(lái)直接啟動(dòng)的(因?yàn)橐坏嚅_(kāi) ssh 進(jìn)程就退出了),常見(jiàn)的做法就是用 systemd 或者 supervisor 來(lái)使其成為 守護(hù)進(jìn)程,這樣進(jìn)程就可以一直運(yùn)行,遇到錯(cuò)誤意外退出也能被自動(dòng)重啟。
好學(xué)的你可能會(huì)思考守護(hù)進(jìn)程到底是怎么實(shí)現(xiàn)的?為什么有的程序既可以自己就成為守護(hù)進(jìn)程,又可以通過(guò) systemd 來(lái)后臺(tái)運(yùn)行?如果不依賴外部,我們的 PHP 程序該怎樣變成守護(hù)進(jìn)程呢?
成為守護(hù)進(jìn)程的步驟
其實(shí)只需要?jiǎng)?chuàng)建子進(jìn)程并退出父進(jìn)程,將要處理的工作在子進(jìn)程中進(jìn)行就可以實(shí)現(xiàn)一個(gè)守護(hù)進(jìn)程了。但是僅僅是這么做的話,如果后續(xù)任務(wù)很復(fù)雜,或者引入了一些第三方包,那么可能就會(huì)出現(xiàn)奇奇怪怪的問(wèn)題了。
而在《UNIX環(huán)境高級(jí)編程》(英語(yǔ):Advanced Programming in the UNIX Environment,簡(jiǎn)稱APUE)一書中有介紹關(guān)于守護(hù)進(jìn)程的編碼規(guī)范,我們按照規(guī)范來(lái)實(shí)現(xiàn)我們的守護(hù)進(jìn)程就可以避免出現(xiàn)那些奇怪的問(wèn)題了。而且規(guī)范也不復(fù)雜,只需要幾步就可以了:
- 創(chuàng)建子進(jìn)程,退出父進(jìn)程
- 子進(jìn)程創(chuàng)建一個(gè)新的會(huì)話并成為
session leader - 重設(shè)文件掩碼
- 改變工作目錄
- 關(guān)閉標(biāo)準(zhǔn)輸入輸出
實(shí)現(xiàn)
<?php
function daemon()
{
// [1] 創(chuàng)建子進(jìn)程
$pid = pcntl_fork();
if ($pid == -1) {
die('fork failed');
}
// [2] 如果是父進(jìn)程,則退出
if ($pid > 0) {
exit(0);
}
///////////////// 以下是子進(jìn)程 /////////////////
// [3] 創(chuàng)建一個(gè)新的會(huì)話并成為 session leader
if ( ($sid = posix_setsid()) <= 0 ) {
die("Set sid failed.\n");
}
// [4] 重設(shè)文件掩碼
umask(0);
// [5] 改變工作目錄
if (chdir('/') === false) {
die("chdir failed.\n");
}
// [6] 關(guān)閉標(biāo)準(zhǔn)輸入輸出
fclose(STDIN);
fclose(STDOUT);
fclose(STDERR);
}
daemon();
// ... 真正的處理邏輯說(shuō)明
上面短短的十幾二十行代碼就實(shí)現(xiàn)了一個(gè)守護(hù)進(jìn)程,接下來(lái)解釋一下有些步驟為什么要這么做。
創(chuàng)建子進(jìn)程并退出父進(jìn)程
pcntl_fork() 的返回值有三種情況,上面的代碼([1]和[2])已經(jīng)處理了對(duì)應(yīng)的情況。
創(chuàng)建新的會(huì)話
調(diào)用 posix_setsid() 創(chuàng)建新會(huì)話會(huì)使得當(dāng)前進(jìn)程成為新會(huì)話中的“會(huì)話首進(jìn)程”,同時(shí)也會(huì)使當(dāng)前進(jìn)程成為“進(jìn)程組組長(zhǎng)”,并且使得當(dāng)前進(jìn)程脫離控制終端。
重設(shè)文件掩碼
調(diào)用 umask() 重設(shè)文件掩碼,這里通常是 0。為什么是 0 而不是其他呢,因?yàn)樽舆M(jìn)程從父進(jìn)程繼承來(lái)的文件掩碼可能會(huì)屏蔽某些特定的文件操作權(quán)限。比如說(shuō)引入的第三方庫(kù)可能需要用特定的權(quán)限來(lái)創(chuàng)建文件,并且它沒(méi)有將文件權(quán)限作為一個(gè)選項(xiàng)參數(shù)由你指定,那么就可能會(huì)出現(xiàn)失敗的情況;而我們傳入 0,會(huì)使得從調(diào)用了 umask() 之后,守護(hù)進(jìn)程創(chuàng)建的文件權(quán)限為 0666,目錄權(quán)限為 0777,均為最高權(quán)限。
關(guān)于 umask() 后面會(huì)展開(kāi)新的篇幅來(lái)說(shuō)明,感興趣的可以先自行搜索資料學(xué)習(xí)。
改變工作目錄
通過(guò) chdir() 我們將工作目錄設(shè)置為根目錄 /,主要是因?yàn)槭刈o(hù)進(jìn)程是長(zhǎng)時(shí)間運(yùn)行的,通常只有系統(tǒng)關(guān)閉/重啟才會(huì)退出。假如從父進(jìn)程繼承來(lái)的工作目錄是個(gè)掛載的文件系統(tǒng),如果不改變工作目錄,那么將會(huì)導(dǎo)致這個(gè)掛載的文件系統(tǒng)一直沒(méi)法卸載。
當(dāng)然也不一定要將工作目錄切換到根目錄,你也可以根據(jù)實(shí)際情況切換到特定的目錄。
關(guān)閉標(biāo)準(zhǔn)輸入輸出
因?yàn)槭刈o(hù)進(jìn)程是脫離終端控制的,所以是沒(méi)有標(biāo)準(zhǔn)輸入輸出交互的,我們將其關(guān)閉即可。
其他
二次 fork
你可能在一些資料中看到有人推薦你在 [3] 創(chuàng)建一個(gè)新的會(huì)話并成為 session leader 之后再次進(jìn)行 fork。這一步驟是在基于 System V 的系統(tǒng)中,可以保證你的守護(hù)進(jìn)程不是“會(huì)話首進(jìn)程”,可以阻止其重新申請(qǐng)獲取一個(gè)控制終端。
關(guān)閉不必要的文件描述符
按照編碼規(guī)范,實(shí)際還有一步是關(guān)閉不必要的文件描述符。但我們?yōu)榱撕?jiǎn)單起見(jiàn),上面的代碼在進(jìn)程啟動(dòng)之后先創(chuàng)建守護(hù)進(jìn)程再執(zhí)行其他操作,因此這里只打開(kāi)了三個(gè)文件描述符: 0、1 和 2(即標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出、標(biāo)準(zhǔn)錯(cuò)誤)。
注意事項(xiàng)
因?yàn)樯厦娴拇a將標(biāo)準(zhǔn)輸入輸出關(guān)閉了,也就是說(shuō)如果你在 daemon() 之后有諸如 echo "Hello world"; 之類的輸出,那么你的程序?qū)?huì)出錯(cuò)然后退出,并且你將看不到任何錯(cuò)誤信息(因?yàn)闃?biāo)準(zhǔn)錯(cuò)誤也被關(guān)閉了)。
解決方案有兩種,一種是用 file_put_contents 代替 echo,但是這樣并不優(yōu)雅,而且萬(wàn)一引入的第三方包中寫了 echo 或者是 file_put_contents(STDOUT, ...),那你的程序也會(huì)“莫名其妙”就掛了,會(huì)讓你排查半天到底是哪里出了問(wèn)題。
因此我們還可以在第[6]之后加入:
// [7] 重定向輸入輸出
global $stdin, $stdout, $stderr;
$stdin = fopen('/dev/null', 'r');
$stdout = fopen('/dev/null', 'wb'); // 你也可以將標(biāo)準(zhǔn)輸出重定向到指定的文件,相當(dāng)于是日志
$stderr = fopen('/dev/null', 'wb'); // 同上到此這篇關(guān)于PHP實(shí)現(xiàn)守護(hù)進(jìn)程的示例代碼的文章就介紹到這了,更多相關(guān)PHP守護(hù)進(jìn)程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- PHP程序守護(hù)進(jìn)程化實(shí)現(xiàn)方法詳解
- php實(shí)現(xiàn)簡(jiǎn)單的守護(hù)進(jìn)程創(chuàng)建、開(kāi)啟與關(guān)閉操作
- PHP守護(hù)進(jìn)程的兩種常見(jiàn)實(shí)現(xiàn)方式詳解
- php腳本守護(hù)進(jìn)程原理與實(shí)現(xiàn)方法詳解
- 分享PHP守護(hù)進(jìn)程類
- 如何寫php守護(hù)進(jìn)程(Daemon)
- PHP擴(kuò)展程序?qū)崿F(xiàn)守護(hù)進(jìn)程
- PHP守護(hù)進(jìn)程實(shí)例
- php萬(wàn)字碼出完美守護(hù)進(jìn)程詳解
相關(guān)文章
使用php實(shí)現(xiàn)截取指定長(zhǎng)度
以下是對(duì)使用php實(shí)現(xiàn)截取指定長(zhǎng)度的方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友可以過(guò)來(lái)參考下2013-08-08
PHP7原生MySQL數(shù)據(jù)庫(kù)操作實(shí)現(xiàn)代碼
php7已經(jīng)沒(méi)有mysql這個(gè)擴(kuò)展了,好多貼還是按照以前的方法講,讓我這個(gè)新手來(lái)回找了好多遍都沒(méi)找到mysql,php7用后面兩個(gè)鏈接數(shù)據(jù)庫(kù),千萬(wàn)不要傻傻去找也不要去來(lái)回拷貝什么dll之類的2020-07-07
PHP使用GETDATE獲取當(dāng)前日期時(shí)間作為一個(gè)關(guān)聯(lián)數(shù)組的方法
這篇文章主要介紹了PHP使用GETDATE獲取當(dāng)前日期時(shí)間作為一個(gè)關(guān)聯(lián)數(shù)組的方法,實(shí)例分析了php中GETDATE函數(shù)使用技巧,需要的朋友可以參考下2015-03-03
php數(shù)組函數(shù)序列之krsort()- 對(duì)數(shù)組的元素鍵名進(jìn)行降序排序,保持索引關(guān)系
krsort() 函數(shù)將數(shù)組按照鍵逆向排序,為數(shù)組值保留原來(lái)的鍵??蛇x的第二個(gè)參數(shù)包含附加的排序標(biāo)志2011-11-11
PHP preg_match實(shí)現(xiàn)正則表達(dá)式匹配功能【輸出是否匹配及匹配值】
這篇文章主要介紹了PHP preg_match實(shí)現(xiàn)正則表達(dá)式匹配功能,較為詳細(xì)的介紹了preg_match函數(shù)的功能、參數(shù)含義、返回值及使用方法,并結(jié)合實(shí)例給出了preg_match輸出是否匹配及匹配值的相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-07-07
用php定義一個(gè)數(shù)組最簡(jiǎn)單的方法
這篇文章主要介紹了用php定義一個(gè)數(shù)組最簡(jiǎn)單的方法,以及相關(guān)實(shí)例代碼,需要的朋友們學(xué)習(xí)下。2019-10-10

