首发于范浩博科学院。在使用PHP玩了第一篇流程——基础复习之后,我们已经掌握了流程的基础知识,现在我们可以尝试使用PHP来做一些简单的流程控制和管理,加深对流程的理解。接下来,我将实现一个简单的多进程模型的PHPServer,基于它你可以做任何事情。完整的PHPServer源代码可以在fan-haobai/php-server获取。总体流程PHPServer的Master进程和Worker进程的主要控制流程如下图所示:其中,主要涉及三个对象,分别是入口脚本、Master进程和Worker进程。它们所扮演的角色如下:入口脚本:主要实现PHPServer的启动、停止、重载功能,即触发Master进程的启动、停止、重载过程;Master进程:负责创建和监控Worker进程。在启动阶段,会注册signalhandler,然后创建Worker;在运行阶段,会持续监控Worker进程的健康状态,接收并响应入口脚本的控制信号;在停止阶段,停止所有Worker进程;进程:负责执行业务逻辑。被Master进程创建后,处于持续运行阶段,会监听Master进程的信号,实现自停;整个过程包括4个进程:进程①:以daemon状态启动PHPServer时的主进程。入口脚本会daemonize,即实现进程的daemon状态。这时候会fork出一个Master进程;Master进程会先保存PID,注册信号处理器,然后创建一个Workerfork多个Worker进程;进程②:是对Master进程的持续监控进程,进程中会捕获入口脚本发送的信号。主要监控Worker进程的健康状态。当Worker进程异常退出时,会尝试创建一个新的Worker进程来维持Worker进程的数量;进程③:对于Worker进程来说是一个持续运行的进程,进程中会捕获Master进程发送的信号。进程①中的Worker进程创建后,会继续执行业务逻辑,阻塞在这里;进程④:停止PHPServer主进程。入口脚本会先向Master进程发送一个SIGINT信号。Master进程捕获到信号后,将SIGINT信号转发给所有Worker进程(通知所有Worker进程终止),等待所有Worker进程终止退出;在流程②中,Worker进程被Master进程fork出来后,会继续运行并阻塞在这里,只有Master进程才会继续后续流程。代码实现启动流程见流程①,主要包括四个部分:守护进程、保存PID、注册信号处理器、创建多进程Worker。守护进程首先在入口脚本中fork出一个子进程,然后该进程退出,并将新的子进程设置为sessionleader。这时候子进程就会脱离当前终端的控制。如下图所示:这里使用了2个fork,所以最后一个fork的子进程就是Master进程。其实一个fork也是可以的。代码如下:protectedstaticfunctiondaemonize(){umask(0);$pid=pcntl_fork();if(-1===$pid){exit("processforkfail\n");}elseif($pid>0){退出(0);}//将当前进程提升为会话领导者if(-1===posix_setsid()){exit("processsetsidfail\n");}//再次fork以避免SVR4系统终端再次获得进程控制$pid=pcntl_fork();if(-1===$pid){exit("processforkfail\n");}elseif(0!==$pid){退出(0);}}通常在启动时加上-d参数,表示进程将以daemon模式运行。成功成为daemon进程后,Master进程已经脱离了终端控制,所以需要关闭标准输出和标准错误输出。如下:protectedstaticfunctionresetStdFd(){global$STDERR,$STDOUT;//重定向标准输出和错误输出@fclose(STDOUT);fclose(STDERR);$STDOUT=fopen(static::$stdoutFile,'a');$STDERR=fopen(static::$stdoutFile,'a');}保存PID为了实现PHPServer的过载或者停止,我们需要在PID文件中保存Master进程的PID,比如php-server.pid文件。代码如下:protectedstaticfunctionsaveMasterPid(){//保存pid用于重新加载和停止static::$_masterPid=posix_getpid();if(false===file_put_contents(static::$pidFile,static::$_masterPid)){exit("无法将pid保存到".static::$pidFile."\n");}echo"PHPServerstart\t\033[32m[OK]\033[0m\n";}注册信号处理因为守护进程一旦脱离了终端控制,就如脱缰的野马,有可能任由它狂奔,它就为所欲为,所以我们要驯服它。这里使用信号来实现进程间通信,控制进程的行为。注册信号处理程序如下:protectedstaticfunctioninstallSignal(){pcntl_signal(SIGINT,array('\PHPServer\Worker','signalHandler'),false);pcntl_signal(SIGTERM,array('\PHPServer\Worker','signalHandler'),false);pcntl_signal(SIGUSR1,array('\PHPServer\Worker','signalHandler'),false);pcntl_signal(SIGQUIT,array('\PHPServer\Worker','signalHandler'),false);//忽略信号pcntl_signal(SIGUSR2,SIG_IGN,false);pcntl_signal(SIGHUP,SIG_IGN,false);}受保护的静态函数signalHandler($signal){switch($signal){caseSIGINT:caseSIGTERM:static::stop();休息;案例SIGQUIT:案例SIGUSR1:static::reload();休息;默认值:中断;其中,SIGINT和SIGTERM信号会触发stop操作,即终止所有进程;SIGQUIT和SIGUSR1信号会触发reload操作,即重新加载所有Worker进程;这里忽略了SIGUSR2和SIGHUP信号,但是没有忽略SIGKILL信号,即可以强行杀死所有进程。创建多进程的WorkerMaster进程通过fork系统调用,可以创建多个Worker进程。实现代码如下:protectedstaticfunctionforkOneWorker(){$pid=pcntl_fork();//父进程if($pid>0){static::$_workers[]=$pid;}elseif($pid===0){//子进程static::setProcessTitle('PHPServer:worker');//子进程会阻塞在这里static::run();//子进程退出exit(0);}else{thrownew\Exception("forkoneworkerfail");}}受保护的静态函数forkWorkers(){while(count(static::$_workers)
