PHP 如何创建守护(daemon)进程
先讲几个概念
守护进程:
Linux中的后台服务进程。它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程常常在系统引导装入时启动,在系统关闭时终止。
进程组:
是一个或多个进程的集合。进程组有进程组ID来唯一标识。除了进程号(PID)之外,进程组ID也是一个进程的必备属性。每个进程组都有一个组长进程,其组长进程的进程号等于进程组ID。且该进程组ID不会因组长进程的退出而受到影响。
会话周期:
会话期是一个或多个进程组的集合。通常,一个会话开始于用户登录,终止于用户退出,在此期间该用户运行的所有进程都属于这个会话期。
创建一个守护进程大体这样:
1、fork子进程,父进程退出
为避免挂起,控制终端将 Daemon 放入后台执行,方法是在进程中调用 fork(),然后使父进程终止,我们所有后续工作都在子进程中完成。
<?php
$pid = pcntl_fork(); // 父进程 和 子进程 都会执行下面代码
if ($pid == -1) {
// 当 pid 为 -1 的时候表示创建子进程失败,这时返回-1。
return false;
} else if ($pid) {
// 父进程会得到子进程号,所以这里是父进程执行的逻辑
pcntl_wait($status); // 等待子进程中断,防止子进程成为僵尸进程。
} else {
// 子进程得到的 $pid 为 0,所以这里是 子进程 执行的逻辑。
}
2、在子进程中创建新会话
先介绍一下 Linux 中的 进程 与 控制终端,登录会话 和 进程组 之间的关系:
进程属于一个进程组,进程组号(GID)就是进程组长的进程号(PID)。登录会话可以包含多个进程组。这些进程组共享一个控制终端。这个控制终端通常是创建进程的登录终端。 控制终端,登录会话和进程组通常是从父进程继承下来的。我们的目的就是要摆脱它们,使之不受它们的影响。方法是在第1点的基础上,调用 posix_setsid(); 使进程成为会话组长。setsid有几个作用:让进程摆脱原会话的控制;让进程摆脱原进程组的控制;
3、改变当前目录为根目录
进程活动时,其工作目录所在的文件系统不能卸下。一般需要将工作目录改变到根目录。对于需要转储核心,写运行日志的进程将工作目录改变到特定目录如 chdir("/"),如果有特殊的需要,我们也可以把当前工作目录换成其他的路径,比如 /tmp。
4、重设文件权限掩码
进程从父进程那里继承了文件创建掩模。它可能修改守护进程所创建的文件的存取位。为防止这一点,将文件创建掩模清除:umask(0),如果你的应用程序根本就不涉及创建新文件或是文件访问权限的设定,这一步不是必须的。
5、关闭文件描述符
同文件权限掩码一样,新进程会从父进程那里继承一些已经打开了的文件。这些被打开的文件可能永远不被我们的daemon进程读或写,但它们一样消耗系统资源,而且可能导致所在的文件系统无法卸下。文件描述符为 0、1 和 2 的三个文件,输入、输出 和 报错 这三个文件也需要被关闭。在 PHP 中只需要 fclose() 就可以了。
fclose(STDIN);
fclose(STDOUT);
fclose(STDERR);
6、守护进程退出,处理 SIGCHLD 信号
当用户需要外部停止守护进程运行时,往往会使用 kill 命令停止该守护进程。所以,守护进程中需要编码来实现 kill 发出的 signal 信号处理,达到进程的正常退出。处理 SIGCHLD 信号并不是必须的。但对于某些进程,特别是服务器进程往往在请求到来时生成子进程处理请求。如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源。如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。
<?php
// 使用 ticks 需要 PHP 4.3.0 以上版本
declare(ticks = 1); // 信号处理函数
function sig_handler($signo) {
switch ($signo) {
case SIGTERM:
// 处理 SIGTERM 信号 - 进程终止
exit;
break;
case SIGHUP:
// 处理 SIGHUP 信号 - 终止控制终端或进程
break;
case SIGUSR1:
// 用户信号
echo "Caught SIGUSR1...\n";
break;
default:
// 处理所有其他信号
}
} echo "Installing signal handler...\n"; // 安装信号处理器
pcntl_signal(SIGTERM, "sig_handler");
pcntl_signal(SIGHUP, "sig_handler");
pcntl_signal(SIGUSR1, "sig_handler"); // 或者在PHP 4.3.0以上版本可以使用对象方法
// pcntl_signal(SIGUSR1, array($obj, "do_something");
echo "Generating signal SIGTERM to self...\n"; // 向当前进程发送 SIGUSR1 信号
posix_kill(posix_getpid(), SIGUSR1);
echo "Done\n";
下面实现一个守护进程的示例代码
<?php
class Deamon {
private $_pidFile;
private $_jobs = array();
private $_infoDir; public function __construct($dir = '/tmp') {
$this->_setInfoDir($dir);
$this->_pidFile = rtrim($this->_infoDir, '/') . '/' . __CLASS__ . '_pid.log';
$this->_checkPcntl();
} private function _demonize() {
if (php_sapi_name() != 'cli') {
die('Should run in CLI');
} $pid = pcntl_fork(); if ($pid < 0) {
die("Can't Fork!");
} else if ($pid > 0) {
exit();
} if (posix_setsid() === -1) {
die('Could not detach');
} chdir('/');
umask(0);
$fp = fopen($this->_pidFile, 'w') or die("Can't create pid file");
fwrite($fp, posix_getpid());
fclose($fp); if (!empty($this->_jobs)) {
foreach ($this->_jobs as $job) {
if (!empty($job['argv'])) {
call_user_func($job['function'], $job['argv']);
} else {
call_user_func($job['function']);
}
}
}
return;
} private function _setInfoDir($dir = null) {
if (is_dir($dir)) {
$this->_infoDir = $dir;
} else {
$this->_infoDir = __DIR__;
}
} private function _checkPcntl() {
!function_exists('pcntl_signal') && die('Error:Need PHP Pcntl extension!');
} private function _getPid() {
if (!file_exists($this->_pidFile)) {
return 0;
} $pid = intval(file_get_contents($this->_pidFile)); if (posix_kill($pid, SIG_DFL)) {
return $pid;
} else {
unlink($this->_pidFile);
return 0;
}
} private function _message($message) {
printf("%s %d %d %s" . PHP_EOL, date("Y-m-d H:i:s"), posix_getpid(), posix_getppid(), $message);
} public function start() {
if ($this->_getPid() > 0) {
$this->_message('Running');
} else {
$this->_demonize();
$this->_message('Start');
}
} public function stop() {
$pid = $this->_getPid();
if ($pid > 0) {
posix_kill($pid, SIGTERM);
unlink($this->_pidFile);
echo 'Stoped' . PHP_EOL;
} else {
echo "Not Running" . PHP_EOL;
}
} public function status() {
if ($this->_getPid() > 0) {
$this->_message('Is Running');
} else {
echo 'Not Running' . PHP_EOL;
}
} public function addJobs($jobs = array()) {
if (!isset($jobs['function']) || empty($jobs['function'])) {
$this->_message('Need function param');
} if (!isset($jobs['argv']) || empty($jobs['argv'])) {
$jobs['argv'] = "";
} $this->_jobs[] = $jobs;
} public function run($argv) {
$param = is_array($argv) && count($argv) == 2 ? $argv[1] : null;
switch ($param) {
case 'start':
$this->start();
break;
case 'stop':
$this->stop();
break;
case 'status':
$this->status();
break;
default:
echo "Argv start|stop|status " . PHP_EOL;
break;
}
}
} $deamon = new Deamon('');
$deamon->addJobs(array(
'function' => 'test',
'argv' => 'Go'
));
$deamon->run($argv); function test($param) {
$i = 0;
while (true) {
echo 'Now is ', $param . PHP_EOL;
$i++;
sleep(5);
}
}
延伸阅读:
【荐】记录 php daemon 守护进程 遇到的问题 -- posix_setsid() 函数
PHP 如何创建守护(daemon)进程的更多相关文章
- linux系统编程之进程(八):守护进程详解及创建,daemon()使用
一,守护进程概述 Linux Daemon(守护进程)是运行在后台的一种特殊进程.它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件.它不需要用户输入就能运行而且提供某种服务,不是对整个 ...
- linux系统编程:守护进程详解及创建,daemon()使用
一,守护进程概述 Linux Daemon(守护进程)是运行在后台的一种特殊进程.它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件.它不需要用户输入就能运行而且提供某种服务,不是对整个 ...
- 守护进程详解及创建,daemon()使用
一,守护进程概述 Linux Daemon(守护进程)是运行在后台的一种特殊进程.它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件.它不需要用户输入就能运行而 且提供某种服务,不是对整 ...
- ASP.NET Core Linux下为 dotnet 创建守护进程(必备知识)
前言 在上篇文章中介绍了如何在 Docker 容器中部署我们的 asp.net core 应用程序,本篇主要是怎么样为我们在 Linux 或者 macOs 中部署的 dotnet 程序创建一个守护进程 ...
- 创建守护进程步骤与setsid() -- linux deamon进程
原创:http://www.cnblogs.com/mickole/p/3188321.html 一,守护进程概述 Linux Daemon(守护进程)是运行在后台的一种特殊进程.它独立于控制终端并且 ...
- ASP.ENT Core Linux 下 为 donet创建守护进程(转载)
原文地址:http://www.cnblogs.com/savorboard/p/dotnetcore-supervisor.html 前言 在上篇文章中介绍了如何在 Docker 容器中部署我们的 ...
- linux创建守护进程
守护进程deamon 是一个后台进程,无需用户输入就能运行,用来在系统后台提供某种服务. 常见的守护进程有Wbe服务器.邮件服务器以及数据库服务器等等.守护进程不能控制终端,所以任何的输入和输出都需要 ...
- Supervisor 为服务创建守护进程
今天需要再服务上部署一个.net 方面的项目:当时开启服务的命令只能在前台执行:使用nohub CMD &等放在后台开启服务都会宕机:所以搜寻了Supervisor 这个解决办法,为服务创建守 ...
- Python中创建守护进程
python 创建守护进程 python 的os.setdid()提供了类似linux c api的 setsid 也可以通过unix双fork创建守护进程. 几个相关的函数 os.umask(0) ...
随机推荐
- 关于mysql-connector-java(JDBC驱动)的一些坑
最近在写一个项目的时候,用了maven仓库里面较新的mysql的JDBC驱动,版本是6.0.6,Mybatis的全局配置是这么写的: <?xml version='1.0' encoding=' ...
- 洛谷 P4910 帕秋莉的手环 矩阵乘法+快速幂详解
矩阵快速幂解法: 这是一个类似斐波那契数列的矩乘快速幂,所以推荐大家先做一下下列题目:(会了,差不多就是多倍经验题了) 注:如果你不会矩阵乘法,可以了解一下P3390的题解 P1939 [模板]矩阵加 ...
- Linux如何解决动态库的版本控制
引用自:http://www.linuxidc.com/Linux/2012-04/59071.htm (换句话说,soname不是真实存在的文件,只是在此库中和将来调用此库的文件中保存的一个名字,在 ...
- React-Native 之 ListView使用
前言 学习本系列内容需要具备一定 HTML 开发基础,没有基础的朋友可以先转至 HTML快速入门(一) 学习 本人接触 React Native 时间并不是特别长,所以对其中的内容和性质了解可能会有所 ...
- jenkins的svn路径中文问题
今天弄Jenkins,我们的SVN代码路径是中文的,他娘的坑死我了,很没面子弄了俩点,网上方案试了好多,说装插件,修改Tomcat server.xml,基本没用,后来看到一个帖子写的方案蛮实用的,分 ...
- axios笔记
参考:http://www.cnblogs.com/Upton/p/6180512.html https://cloud.tencent.com/developer/article/1098141 ...
- mysql索引 B+tree
一.B+tree示意图 二.为什么要用索引 1.索引能极大减少存储引擎需要扫描的数据量:因为索引有序所以可以快速查找并且不用全表查找: 2.索引可以把随机IO变为顺序IO:因为B+tree在数据中保存 ...
- SpringBoot-定制banner
我们在启动Spring Boot项目的时候,在控制台会默认输出一个启动图案 这个图案如果你需要的话是可以自己修改的,修改方式很简单 1.在src/main/resources下新建一个banner.t ...
- 【LOJ】#2536. 「CQOI2018」解锁屏幕
题解 什么破题,看一眼就能想出来\(n^2 2^n\)看了一眼数据范围有点虚,结果跑得飞快= = 处理出\(a[i][j]\)表示从\(i\)到\(j\)经过的点的点集 然后\(f[i][S]\)表示 ...
- 【LOJ】#2512. 「BJOI2018」链上二次求和
题面 题解 转化一下可以变成所有小于等于r的减去小于等于l - 1的 然后我们求小于等于x的 显然是 \(\sum_{i = 1}^{n} \sum_{j = 1}^{min(i,x)} sum[i] ...