欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  后端开发

PHP 守护进程类

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