当前位置:Gxlcms > PHP教程 > 分享PHP守护进程类_php技巧

分享PHP守护进程类_php技巧

时间:2021-07-01 10:21:17 帮助过:47人阅读

用PHP实现的Daemon类。可以在服务器上实现队列或者脱离 crontab 的计划任务。
使用的时候,继承于这个类,并重写 _doTask 方法,通过 main 初始化执行。

  1. <?php
  2. class Daemon {
  3. const DLOG_TO_CONSOLE = 1;
  4. const DLOG_NOTICE = 2;
  5. const DLOG_WARNING = 4;
  6. const DLOG_ERROR = 8;
  7. const DLOG_CRITICAL = 16;
  8. const DAPC_PATH = '/tmp/daemon_apc_keys';
  9. /**
  10. * User ID
  11. *
  12. * @var int
  13. */
  14. public $userID = 65534; // nobody
  15. /**
  16. * Group ID
  17. *
  18. * @var integer
  19. */
  20. public $groupID = 65533; // nobody
  21. /**
  22. * Terminate daemon when set identity failure ?
  23. *
  24. * @var bool
  25. * @since 1.0.3
  26. */
  27. public $requireSetIdentity = false;
  28. /**
  29. * Path to PID file
  30. *
  31. * @var string
  32. * @since 1.0.1
  33. */
  34. public $pidFileLocation = '/tmp/daemon.pid';
  35. /**
  36. * processLocation
  37. * 进程信息记录目录
  38. *
  39. * @var string
  40. */
  41. public $processLocation = '';
  42. /**
  43. * processHeartLocation
  44. * 进程心跳包文件
  45. *
  46. * @var string
  47. */
  48. public $processHeartLocation = '';
  49. /**
  50. * Home path
  51. *
  52. * @var string
  53. * @since 1.0
  54. */
  55. public $homePath = '/';
  56. /**
  57. * Current process ID
  58. *
  59. * @var int
  60. * @since 1.0
  61. */
  62. protected $_pid = 0;
  63. /**
  64. * Is this process a children
  65. *
  66. * @var boolean
  67. * @since 1.0
  68. */
  69. protected $_isChildren = false;
  70. /**
  71. * Is daemon running
  72. *
  73. * @var boolean
  74. * @since 1.0
  75. */
  76. protected $_isRunning = false;
  77. /**
  78. * Constructor
  79. *
  80. * @return void
  81. */
  82. public function __construct() {
  83. error_reporting(0);
  84. set_time_limit(0);
  85. ob_implicit_flush();
  86. register_shutdown_function(array(&$this, 'releaseDaemon'));
  87. }
  88. /**
  89. * 启动进程
  90. *
  91. * @return bool
  92. */
  93. public function main() {
  94. $this->_logMessage('Starting daemon');
  95. if (!$this->_daemonize()) {
  96. $this->_logMessage('Could not start daemon', self::DLOG_ERROR);
  97. return false;
  98. }
  99. $this->_logMessage('Running...');
  100. $this->_isRunning = true;
  101. while ($this->_isRunning) {
  102. $this->_doTask();
  103. }
  104. return true;
  105. }
  106. /**
  107. * 停止进程
  108. *
  109. * @return void
  110. */
  111. public function stop() {
  112. $this->_logMessage('Stoping daemon');
  113. $this->_isRunning = false;
  114. }
  115. /**
  116. * Do task
  117. *
  118. * @return void
  119. */
  120. protected function _doTask() {
  121. // override this method
  122. }
  123. /**
  124. * _logMessage
  125. * 记录日志
  126. *
  127. * @param string 消息
  128. * @param integer 级别
  129. * @return void
  130. */
  131. protected function _logMessage($msg, $level = self::DLOG_NOTICE) {
  132. // override this method
  133. }
  134. /**
  135. * Daemonize
  136. *
  137. * Several rules or characteristics that most daemons possess:
  138. * 1) Check is daemon already running
  139. * 2) Fork child process
  140. * 3) Sets identity
  141. * 4) Make current process a session laeder
  142. * 5) Write process ID to file
  143. * 6) Change home path
  144. * 7) umask(0)
  145. *
  146. * @access private
  147. * @since 1.0
  148. * @return void
  149. */
  150. private function _daemonize() {
  151. ob_end_flush();
  152. if ($this->_isDaemonRunning()) {
  153. // Deamon is already running. Exiting
  154. return false;
  155. }
  156. if (!$this->_fork()) {
  157. // Coudn't fork. Exiting.
  158. return false;
  159. }
  160. if (!$this->_setIdentity() && $this->requireSetIdentity) {
  161. // Required identity set failed. Exiting
  162. return false;
  163. }
  164. if (!posix_setsid()) {
  165. $this->_logMessage('Could not make the current process a session leader', self::DLOG_ERROR);
  166. return false;
  167. }
  168. if (!$fp = fopen($this->pidFileLocation, 'w')) {
  169. $this->_logMessage('Could not write to PID file', self::DLOG_ERROR);
  170. return false;
  171. } else {
  172. fputs($fp, $this->_pid);
  173. fclose($fp);
  174. }
  175. // 写入监控日志
  176. $this->writeProcess();
  177. chdir($this->homePath);
  178. umask(0);
  179. declare(ticks = 1);
  180. pcntl_signal(SIGCHLD, array(&$this, 'sigHandler'));
  181. pcntl_signal(SIGTERM, array(&$this, 'sigHandler'));
  182. pcntl_signal(SIGUSR1, array(&$this, 'sigHandler'));
  183. pcntl_signal(SIGUSR2, array(&$this, 'sigHandler'));
  184. return true;
  185. }
  186. /**
  187. * Cheks is daemon already running
  188. *
  189. * @return bool
  190. */
  191. private function _isDaemonRunning() {
  192. $oldPid = file_get_contents($this->pidFileLocation);
  193. if ($oldPid !== false && posix_kill(trim($oldPid),0))
  194. {
  195. $this->_logMessage('Daemon already running with PID: '.$oldPid, (self::DLOG_TO_CONSOLE | self::DLOG_ERROR));
  196. return true;
  197. }
  198. else
  199. {
  200. return false;
  201. }
  202. }
  203. /**
  204. * Forks process
  205. *
  206. * @return bool
  207. */
  208. private function _fork() {
  209. $this->_logMessage('Forking...');
  210. $pid = pcntl_fork();
  211. if ($pid == -1) {
  212. // 出错
  213. $this->_logMessage('Could not fork', self::DLOG_ERROR);
  214. return false;
  215. } elseif ($pid) {
  216. // 父进程
  217. $this->_logMessage('Killing parent');
  218. exit();
  219. } else {
  220. // fork的子进程
  221. $this->_isChildren = true;
  222. $this->_pid = posix_getpid();
  223. return true;
  224. }
  225. }
  226. /**
  227. * Sets identity of a daemon and returns result
  228. *
  229. * @return bool
  230. */
  231. private function _setIdentity() {
  232. if (!posix_setgid($this->groupID) || !posix_setuid($this->userID))
  233. {
  234. $this->_logMessage('Could not set identity', self::DLOG_WARNING);
  235. return false;
  236. }
  237. else
  238. {
  239. return true;
  240. }
  241. }
  242. /**
  243. * Signals handler
  244. *
  245. * @access public
  246. * @since 1.0
  247. * @return void
  248. */
  249. public function sigHandler($sigNo) {
  250. switch ($sigNo)
  251. {
  252. case SIGTERM: // Shutdown
  253. $this->_logMessage('Shutdown signal');
  254. exit();
  255. break;
  256. case SIGCHLD: // Halt
  257. $this->_logMessage('Halt signal');
  258. while (pcntl_waitpid(-1, $status, WNOHANG) > 0);
  259. break;
  260. case SIGUSR1: // User-defined
  261. $this->_logMessage('User-defined signal 1');
  262. $this->_sigHandlerUser1();
  263. break;
  264. case SIGUSR2: // User-defined
  265. $this->_logMessage('User-defined signal 2');
  266. $this->_sigHandlerUser2();
  267. break;
  268. }
  269. }
  270. /**
  271. * Signals handler: USR1
  272. * 主要用于定时清理每个进程里被缓存的域名dns解析记录
  273. *
  274. * @return void
  275. */
  276. protected function _sigHandlerUser1() {
  277. apc_clear_cache('user');
  278. }
  279. /**
  280. * Signals handler: USR2
  281. * 用于写入心跳包文件
  282. *
  283. * @return void
  284. */
  285. protected function _sigHandlerUser2() {
  286. $this->_initProcessLocation();
  287. file_put_contents($this->processHeartLocation, time());
  288. return true;
  289. }
  290. /**
  291. * Releases daemon pid file
  292. * This method is called on exit (destructor like)
  293. *
  294. * @return void
  295. */
  296. public function releaseDaemon() {
  297. if ($this->_isChildren && is_file($this->pidFileLocation)) {
  298. $this->_logMessage('Releasing daemon');
  299. unlink($this->pidFileLocation);
  300. }
  301. }
  302. /**
  303. * writeProcess
  304. * 将当前进程信息写入监控日志,另外的脚本会扫描监控日志的数据发送信号,如果没有响应则重启进程
  305. *
  306. * @return void
  307. */
  308. public function writeProcess() {
  309. // 初始化 proc
  310. $this->_initProcessLocation();
  311. $command = trim(implode(' ', $_SERVER['argv']));
  312. // 指定进程的目录
  313. $processDir = $this->processLocation . '/' . $this->_pid;
  314. $processCmdFile = $processDir . '/cmd';
  315. $processPwdFile = $processDir . '/pwd';
  316. // 所有进程所在的目录
  317. if (!is_dir($this->processLocation)) {
  318. mkdir($this->processLocation, 0777);
  319. chmod($processDir, 0777);
  320. }
  321. // 查询重复的进程记录
  322. $pDirObject = dir($this->processLocation);
  323. while ($pDirObject && (($pid = $pDirObject->read()) !== false)) {
  324. if ($pid == '.' || $pid == '..' || intval($pid) != $pid) {
  325. continue;
  326. }
  327. $pDir = $this->processLocation . '/' . $pid;
  328. $pCmdFile = $pDir . '/cmd';
  329. $pPwdFile = $pDir . '/pwd';
  330. $pHeartFile = $pDir . '/heart';
  331. // 根据cmd检查启动相同参数的进程
  332. if (is_file($pCmdFile) && trim(file_get_contents($pCmdFile)) == $command) {
  333. unlink($pCmdFile);
  334. unlink($pPwdFile);
  335. unlink($pHeartFile);
  336. // 删目录有缓存
  337. usleep(1000);
  338. rmdir($pDir);
  339. }
  340. }
  341. // 新进程目录
  342. if (!is_dir($processDir)) {
  343. mkdir($processDir, 0777);
  344. chmod($processDir, 0777);
  345. }
  346. // 写入命令参数
  347. file_put_contents($processCmdFile, $command);
  348. file_put_contents($processPwdFile, $_SERVER['PWD']);
  349. // 写文件有缓存
  350. usleep(1000);
  351. return true;
  352. }
  353. /**
  354. * _initProcessLocation
  355. * 初始化
  356. *
  357. * @return void
  358. */
  359. protected function _initProcessLocation() {
  360. $this->processLocation = ROOT_PATH . '/app/data/proc';
  361. $this->processHeartLocation = $this->processLocation . '/' . $this->_pid . '/heart';
  362. }
  363. }

人气教程排行