守護進程(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 信號說明列表
三、實例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
|
<?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 < 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守護進程的相關介紹,希望對大家的學習有所幫助。