写PHPCLI程序的老司机可能经常会写一些常驻进程,比如消息队列消费者进程。这些进程会一直运行,除非释放,否则不会重启,所以我们不可能通过ssh登录服务器直接通过终端启动程序(因为ssh进程一旦断开,就会退出),通常的做法是用systemd或者supervisor让它做一个daemon进程,这样进程就可以一直运行,遇到错误意外退出可以自动重启。如果你好学,你可能会想守护进程是如何实现的?为什么有的程序不仅可以成为守护进程,还可以通过systemd在后台运行呢?如果不依赖于外界,我们的PHP程序如何成为守护进程呢?成为守护进程的步骤其实只需要创建子进程并退出父进程,将要处理的工作放在子进程中即可实现守护进程。但是如果只是这样做的话,如果后续的任务比较复杂,或者引入了一些第三方的包,那么就可能会出现奇怪的问题。在《UNIX环境高级编程》(英文:AdvancedProgrammingintheUNIXEnvironment,简称APUE)一书中,书中介绍了守护进程的编码规范。我们可以通过按照规范实现我们的守护进程来避免那些奇怪的问题。并且规范并不复杂,只需要几个步骤:创建子进程,退出父进程,子进程创建一个新会话并成为会话领导者,重置文件掩码,更改工作目录,关闭标准输入输出实现0){exit(0);}////////////////下面是子进程//////////////////[3]创建一个新会话并成为会话领导者if(($sid=posix_setsid())<=0){die("Setsidfailed.\n");}//[4]重置文件Maskumask(0);//[5]更改工作目录if(chdir('/')===false){die("chdirfailed.\n");}//[6]关闭标准输入输出fclose(STDIN);fclose(标准输出);fclose(STDERR);}daemon();//...真正的处理逻辑说明上面短短十几二十行代码就实现了一个daemon进程,下面解释为什么有些步骤需要做.创建子进程和退出父进程pcntl_fork()的返回值分为三种情况,上面的代码([1]和[2])已经处理了相应的情况。创建新会话调用posix_setsid()创建新会话会使当前进程成为新会话中的“会话头进程”,同时也使当前进程成为“进程组组长”,并使当前进程与当前进程分离控制终端。重置文件掩码调用umask()重置文件掩码,这里通常为0。为什么是0而不是其他,因为子进程从父进程继承的文件掩码可能会屏蔽某些特定的文件操作权限。比如导入的第三方库可能需要创建特定权限的文件,它没有把文件权限指定为option参数让你指定,那么可能会失败;而如果我们传入0,就会调用from设置umask()后,守护进程创建的文件权限为0666,目录权限为0777,都是最高权限。关于um??ask()后面会在新的章节进行讲解,有兴趣的可以先搜索资料,自己学习。通过chdir()更改工作目录我们将工作目录设置为根目录/,主要是因为守护进程是长时间运行的,通常只在系统关闭/重启时退出。如果从父进程继承的工作目录是挂载的文件系统,如果不改变工作目录,挂载的文件系统将永远无法卸载。当然,并不是一定要把工作目录切换到根目录,也可以根据实际情况切换到具体的目录。关闭标准输入输出因为守护进程不受终端控制,所以没有标准输入输出交互,我们直接关闭即可。其他二次分叉你可能会在一些资料中看到有人建议你在[3]创建新会话并成为会话负责人后再次分叉。这一步是基于SystemV系统的,可以保证你的daemon进程不是“sessionfirstprocess”,可以防止它重新申请一个控制终端。关闭不需要的文件描述符根据编码规范,实际上还有一个步骤可以关闭不需要的文件描述符。但是为了简单起见,上面的代码在进程启动后执行其他操作之前创建了一个守护进程,所以这里只打开了三个文件描述符:0、1和2(即标准输入、标准输出、标准错误)。注意因为上面的代码关闭了标准输入和输出,也就是说如果你有输出比如echo"Helloworld";在daemon()之后,您的程序将出错并退出,并且您将看到没有错误消息(因为标准错误也已关闭)。解决办法有两种,一种是用file_put_contents代替echo,但是这样不优雅,如果把echo或者file_put_contents(STDOUT,...)写在导入的第三方包里,那你的程序也会“莫名其妙”挂了,它会让你查半天是哪里出了问题。所以我们也可以在[6]之后加上://[7]redirectinputandoutputglobal$stdin,$stdout,$stderr;$stdin=fopen('/dev/null','r');$stdout=fopen('/dev/null','wb');//也可以将标准输出重定向到指定文件,相当于记录日志$stderr=fopen('/dev/null','wb');//参考同上LinuxdaemonprocessLinux--processgroup,session,daemonprocess
