PHP 守护进程类

WBOY
WBOYasal
2016-07-25 09:06:45886semak imbas
用 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. }
复制代码


Kenyataan:
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn