PHP高级编程实例:编写守护进程
1.什么是守护进程
守护进程是脱离于终端并且在后台运行的进程。守护进程脱离于终端是为了避免进程在执行过程中的信息在任何终端上显示并且进程也不会被任何终端所产生的终端信息所打断。
例如 apache, nginx, mysql 都是守护进程
2.为什么开发守护进程
很多程序以服务形式存在,他没有终端或UI交互,它可能采用其他方式与其他程序交互,如TCP/UDP Socket, UNIX Socket, fifo。程序一旦启动便进入后台,直到满足条件他便开始处理任务。
3.何时采用守护进程开发应用程序
以我当前的需求为例,我需要运行一个程序,然后监听某端口,持续接受服务端发起的数据,然后对数据分析处理,再将结果写入到数据库中; 我采用ZeroMQ实现数据收发。
如果我不采用守护进程方式开发该程序,程序一旦运行就会占用当前终端窗框,还有受到当前终端键盘输入影响,有可能程序误退出。
4.守护进程的安全问题
我们希望程序在非超级用户运行,这样一旦由于程序出现漏洞被骇客控制,攻击者只能继承运行权限,而无法获得超级用户权限。
我们希望程序只能运行一个实例,不运行同事开启两个以上的程序,因为会出现端口冲突等等问题。
5.怎样开发守护进程
例 1. 守护进程例示
1 <?php 2 class ExampleWorker extends Worker { 3 public function __construct() { 4 5 } 6 public function run(){ 7 } 8 } 9 10 11 class Example { 12 /* config */ 13 const LISTEN = "tcp://192.168.2.15:5555"; 14 const MAXCONN = 100; 15 const pidfile = __CLASS__; 16 const uid = 80; 17 const gid = 80; 18 19 protected $pool = NULL; 20 protected $zmq = NULL; 21 public function __construct() { 22 $this->pidfile = '/var/run/'.self::pidfile.'.pid'; 23 } 24 25 private function daemon(){ 26 if (file_exists($this->pidfile)) { 27 echo "The file $this->pidfile exists.\n"; 28 exit(); 29 } 30 31 $pid = pcntl_fork(); 32 if ($pid == -1) { 33 die('could not fork'); 34 } else if ($pid) { 35 // we are the parent 36 //pcntl_wait($status); //Protect against Zombie children 37 exit($pid); 38 } else { 39 // we are the child 40 file_put_contents($this->pidfile, getmypid()); 41 posix_setuid(self::uid); 42 posix_setgid(self::gid); 43 return(getmypid()); 44 } 45 } 46 private function start(){ 47 $pid = $this->daemon(); 48 } 49 private function stop(){ 50 if (file_exists($this->pidfile)) { 51 $pid = file_get_contents($this->pidfile); 52 posix_kill($pid, 9); 53 unlink($this->pidfile); 54 } 55 } 56 57 private function help($proc){ 58 printf("%s start | stop | help \n", $proc); 59 } 60 public function main($argv){ 61 if(count($argv) < 2){ 62 printf("please input help parameter\n"); 63 exit(); 64 } 65 if($argv[1] === 'stop'){ 66 $this->stop(); 67 }else if($argv[1] === 'start'){ 68 $this->start(); 69 }else{ 70 $this->help($argv[0]); 71 } 72 } 73 } 74 75 $cgse = new Example(); 76 $cgse->main($argv);
5.1. 程序启动
下面是程序启动后进入后台的代码
通过进程ID文件来判断,当前进程状态,如果进程ID文件存在表示程序在运行中,通过代码file_exists($this->pidfile)实现,但而后进程被kill需要手工删除该文件才能运行
1 private function daemon(){ 2 if (file_exists($this->pidfile)) { 3 echo "The file $this->pidfile exists.\n"; 4 exit(); 5 } 6 7 $pid = pcntl_fork(); 8 if ($pid == -1) { 9 die('could not fork'); 10 } else if ($pid) { 11 // we are the parent 12 //pcntl_wait($status); //Protect against Zombie children 13 exit($pid); 14 } else { 15 // we are the child 16 file_put_contents($this->pidfile, getmypid()); 17 posix_setuid(self::uid); 18 posix_setgid(self::gid); 19 return(getmypid()); 20 } 21 }
程序启动后,父进程会推出,子进程会在后台运行,子进程权限从root切换到指定用户,同时将pid写入进程ID文件。
5.2. 程序停止
程序停止,只需读取pid文件,然后调用posix_kill($pid, 9); 最后将该文件删除。
private function stop(){ if (file_exists($this->pidfile)) { $pid = file_get_contents($this->pidfile); posix_kill($pid, 9); unlink($this->pidfile); } }
原文出处: http://www.jb51.net/article/54617.htm
博主注:
1、关于本文,博主已把无用的业务代码删除,只留下守互进程代码主干。如果需要查看全部代码,请查看原文出处。
2、关于对进程进行降权,文中直接指定了用户uid和用户组gid,然后用posix_setuid和posix_setgif设置用户。对于不知道这两个uid时,可以用posix_getpwnam获得指定用户名的用户uid和用户组gid。
3、对于等待进程的退出,如果用pcntl_wait或pcntl_waitpid,最大的弊端就是只能等待进程是否在操作系统级别退出,如果是业务级别陷入死循环或者不可预知原因进程未退出,但也没工作,这时主进程就无法侦测。我用的方法是子进程与主进程用管道通信+心跳测试。
4、对于主进程,为了防止ssh窗口关掉退回,可以调用函数posix_setsid将进程session的归属进行更改。当然还有其他方法,博主将以另一篇文章进行说明。
5、别以为posix_setsid就高枕无忧了,标准输入/标准错误一定要进行重定向(无论在代码中实现,或者用命令行实现),否则SSH窗口一退出来,进程都直接跟着退了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具