奥门新浦京官方网站PHP7 网络编程(二)daemon守护进程

PHP也是可以直接进行守护进程的启动与终止的,相对于shell来说会简单很多,理解更方便,当然了PHP守护进程要实现自动重启还是要依赖于shell的crontab日程表,每隔一段时间去执行一次脚本看脚本是否需要重启,如果需要则杀掉进程删除RunFile文件,重新启动并在RunFile文件中写入pid。

前言

Python守护进程和脚本单例运行详解,python守护进程

本篇文章主要介绍了Python守护进程和脚本单例运行,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

一、简介

守护进程最重要的特性是后台运行;它必须与其运行前的环境隔离开来,这些环境包括未关闭的文件描述符、控制终端、会话和进程组、工作目录以及文件创建掩码等;它可以在系统启动时从启动脚本/etc/rc.d中启动,可以由inetd守护进程启动,也可以有作业规划进程crond启动,还可以由用户终端(通常是shell)执行。

Python有时需要保证只运行一个脚本实例,以避免数据的冲突。

二、Python守护进程

1、函数实现

#!/usr/bin/env python 
#coding: utf-8 
import sys, os 

'''将当前进程fork为一个守护进程 
  注意:如果你的守护进程是由inetd启动的,不要这样做!inetd完成了 
  所有需要做的事情,包括重定向标准文件描述符,需要做的事情只有chdir()和umask()了 
''' 

def daemonize (stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'): 
   #重定向标准文件描述符(默认情况下定向到/dev/null) 
  try:  
    pid = os.fork()  
     #父进程(会话组头领进程)退出,这意味着一个非会话组头领进程永远不能重新获得控制终端。 
    if pid > 0: 
      sys.exit(0)  #父进程退出 
  except OSError, e:  
    sys.stderr.write ("fork #1 failed: (%d) %sn" % (e.errno, e.strerror) ) 
    sys.exit(1) 

   #从母体环境脱离 
  os.chdir("/") #chdir确认进程不保持任何目录于使用状态,否则不能umount一个文件系统。也可以改变到对于守护程序运行重要的文件所在目录 
  os.umask(0)  #调用umask(0)以便拥有对于写的任何东西的完全控制,因为有时不知道继承了什么样的umask。 
  os.setsid()  #setsid调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登录会话和进程组脱离。 

   #执行第二次fork 
  try:  
    pid = os.fork()  
    if pid > 0: 
      sys.exit(0)  #第二个父进程退出 
  except OSError, e:  
    sys.stderr.write ("fork #2 failed: (%d) %sn" % (e.errno, e.strerror) ) 
    sys.exit(1) 

   #进程已经是守护进程了,重定向标准文件描述符 

  for f in sys.stdout, sys.stderr: f.flush() 
  si = open(stdin, 'r') 
  so = open(stdout, 'a+') 
  se = open(stderr, 'a+', 0) 
  os.dup2(si.fileno(), sys.stdin.fileno())  #dup2函数原子化关闭和复制文件描述符 
  os.dup2(so.fileno(), sys.stdout.fileno()) 
  os.dup2(se.fileno(), sys.stderr.fileno()) 

#示例函数:每秒打印一个数字和时间戳 
def main(): 
  import time 
  sys.stdout.write('Daemon started with pid %dn' % os.getpid()) 
  sys.stdout.write('Daemon stdout outputn') 
  sys.stderr.write('Daemon stderr outputn') 
  c = 0 
  while True: 
    sys.stdout.write('%d: %sn' %(c, time.ctime())) 
    sys.stdout.flush() 
    c = c+1 
    time.sleep(1) 

if __name__ == "__main__": 
   daemonize('/dev/null','/tmp/daemon_stdout.log','/tmp/daemon_error.log') 
   main() 

可以通过命令ps -ef | grep
daemon.py查看后台运行的继承,在/tmp/daemon_奥门新浦京官方网站,error.log会记录错误运行日志,在/tmp/daemon_stdout.log会记录标准输出日志。

奥门新浦京官方网站 1

2、类实现

#!/usr/bin/env python 
#coding: utf-8 

#python模拟linux的守护进程 

import sys, os, time, atexit, string 
from signal import SIGTERM 

class Daemon: 
 def __init__(self, pidfile, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'): 
   #需要获取调试信息,改为stdin='/dev/stdin', stdout='/dev/stdout', stderr='/dev/stderr',以root身份运行。 
  self.stdin = stdin 
  self.stdout = stdout 
  self.stderr = stderr 
  self.pidfile = pidfile 

 def _daemonize(self): 
  try: 
   pid = os.fork()  #第一次fork,生成子进程,脱离父进程 
   if pid > 0: 
    sys.exit(0)   #退出主进程 
  except OSError, e: 
   sys.stderr.write('fork #1 failed: %d (%s)n' % (e.errno, e.strerror)) 
   sys.exit(1) 

  os.chdir("/")   #修改工作目录 
  os.setsid()    #设置新的会话连接 
  os.umask(0)    #重新设置文件创建权限 

  try: 
   pid = os.fork() #第二次fork,禁止进程打开终端 
   if pid > 0: 
    sys.exit(0) 
  except OSError, e: 
   sys.stderr.write('fork #2 failed: %d (%s)n' % (e.errno, e.strerror)) 
   sys.exit(1) 

   #重定向文件描述符 
  sys.stdout.flush() 
  sys.stderr.flush() 
  si = file(self.stdin, 'r') 
  so = file(self.stdout, 'a+') 
  se = file(self.stderr, 'a+', 0) 
  os.dup2(si.fileno(), sys.stdin.fileno()) 
  os.dup2(so.fileno(), sys.stdout.fileno()) 
  os.dup2(se.fileno(), sys.stderr.fileno()) 

   #注册退出函数,根据文件pid判断是否存在进程 
  atexit.register(self.delpid) 
  pid = str(os.getpid()) 
  file(self.pidfile,'w+').write('%sn' % pid) 

 def delpid(self): 
  os.remove(self.pidfile) 

 def start(self): 
   #检查pid文件是否存在以探测是否存在进程 
  try: 
   pf = file(self.pidfile,'r') 
   pid = int(pf.read().strip()) 
   pf.close() 
  except IOError: 
   pid = None 

  if pid: 
   message = 'pidfile %s already exist. Daemon already running!n' 
   sys.stderr.write(message % self.pidfile) 
   sys.exit(1) 

  #启动监控 
  self._daemonize() 
  self._run() 

 def stop(self): 
  #从pid文件中获取pid 
  try: 
   pf = file(self.pidfile,'r') 
   pid = int(pf.read().strip()) 
   pf.close() 
  except IOError: 
   pid = None 

  if not pid:  #重启不报错 
   message = 'pidfile %s does not exist. Daemon not running!n' 
   sys.stderr.write(message % self.pidfile) 
   return 

   #杀进程 
  try: 
   while 1: 
    os.kill(pid, SIGTERM) 
    time.sleep(0.1) 
    #os.system('hadoop-daemon.sh stop datanode') 
    #os.system('hadoop-daemon.sh stop tasktracker') 
    #os.remove(self.pidfile) 
  except OSError, err: 
   err = str(err) 
   if err.find('No such process') > 0: 
    if os.path.exists(self.pidfile): 
     os.remove(self.pidfile) 
   else: 
    print str(err) 
    sys.exit(1) 

 def restart(self): 
  self.stop() 
  self.start() 

 def _run(self): 
  """ run your fun""" 
  while True: 
   #fp=open('/tmp/result','a+') 
   #fp.write('Hello Worldn') 
   sys.stdout.write('%s:hello worldn' % (time.ctime(),)) 
   sys.stdout.flush()  
   time.sleep(2) 


if __name__ == '__main__': 
  daemon = Daemon('/tmp/watch_process.pid', stdout = '/tmp/watch_stdout.log') 
  if len(sys.argv) == 2: 
    if 'start' == sys.argv[1]: 
      daemon.start() 
    elif 'stop' == sys.argv[1]: 
      daemon.stop() 
    elif 'restart' == sys.argv[1]: 
      daemon.restart() 
    else: 
      print 'unknown command' 
      sys.exit(2) 
    sys.exit(0) 
  else: 
    print 'usage: %s start|stop|restart' % sys.argv[0] 
    sys.exit(2) 

运行结果:

奥门新浦京官方网站 2

 它是当Daemon设计成一个模板,在其他文件中from daemon import
Daemon,然后定义子类,重写run()方法实现自己的功能。

class MyDaemon(Daemon): 
  def run(self): 
    while True: 
      fp=open('/tmp/run.log','a+') 
      fp.write('Hello Worldn') 
      time.sleep(1) 

不足:信号处理signal.signal(signal.SIGTERM,
cleanup_handler)暂时没有安装,注册程序退出时的回调函数delpid()没有被调用。

然后,再写个shell命令,加入开机启动服务,每隔2秒检测守护进程是否启动,若没有启动则启动,自动监控恢复程序。     

#/bin/sh 
while true 
do 
 count=`ps -ef | grep "daemonclass.py" | grep -v "grep"` 
 if [ "$?" != "0" ]; then 
   daemonclass.py start 
 fi 
 sleep 2 
done 

三、python保证只能运行一个脚本实例

1、打开文件本身加锁

#!/usr/bin/env python 
#coding: utf-8 
import fcntl, sys, time, os 
pidfile = 0 

def ApplicationInstance(): 
  global pidfile 
  pidfile = open(os.path.realpath(__file__), "r") 
  try: 
    fcntl.flock(pidfile, fcntl.LOCK_EX | fcntl.LOCK_NB) #创建一个排他锁,并且所被锁住其他进程不会阻塞 
  except: 
    print "another instance is running..." 
    sys.exit(1) 

if __name__ == "__main__": 
  ApplicationInstance() 
  while True: 
    print 'running...' 
    time.sleep(1) 

注意:open()参数不能使用w,否则会覆盖本身文件;pidfile必须声明为全局变量,否则局部变量生命周期结束,文件描述符会因引用计数为0被系统回收(若整个函数写在主函数中,则不需要定义成global)。

奥门新浦京官方网站 3              

2、打开自定义文件并加锁

#!/usr/bin/env python 
#coding: utf-8 
import fcntl, sys, time 
pidfile = 0 

def ApplicationInstance(): 
  global pidfile 
  pidfile = open("instance.pid", "w") 
  try: 
    fcntl.lockf(pidfile, fcntl.LOCK_EX | fcntl.LOCK_NB) #创建一个排他锁,并且所被锁住其他进程不会阻塞 
  except IOError: 
    print "another instance is running..." 
    sys.exit(0) 

if __name__ == "__main__": 
  ApplicationInstance() 
  while True: 
    print 'running...' 
    time.sleep(1) 

3、检测文件中PID

#!/usr/bin/env python 
#coding: utf-8 
import time, os, sys 
import signal 

pidfile = '/tmp/process.pid' 

def sig_handler(sig, frame): 
  if os.path.exists(pidfile): 
    os.remove(pidfile) 
  sys.exit(0) 

def ApplicationInstance(): 
  signal.signal(signal.SIGTERM, sig_handler) 
  signal.signal(signal.SIGINT, sig_handler) 
  signal.signal(signal.SIGQUIT, sig_handler) 

  try: 
   pf = file(pidfile, 'r') 
   pid = int(pf.read().strip()) 
   pf.close() 
  except IOError: 
   pid = None 

  if pid: 
   sys.stdout.write('instance is running...n') 
   sys.exit(0) 

  file(pidfile, 'w+').write('%sn' % os.getpid()) 

if __name__ == "__main__": 
  ApplicationInstance() 
  while True: 
    print 'running...' 
    time.sleep(1) 

奥门新浦京官方网站 4

奥门新浦京官方网站 5

4、检测特定文件夹或文件

#!/usr/bin/env python 
#coding: utf-8 
import time, commands, signal, sys 

def sig_handler(sig, frame): 
  if os.path.exists("/tmp/test"): 
    os.rmdir("/tmp/test") 
  sys.exit(0) 

def ApplicationInstance(): 
  signal.signal(signal.SIGTERM, sig_handler) 
  signal.signal(signal.SIGINT, sig_handler) 
  signal.signal(signal.SIGQUIT, sig_handler) 
  if commands.getstatusoutput("mkdir /tmp/test")[0]: 
    print "instance is running..." 
    sys.exit(0) 

if __name__ == "__main__": 
  ApplicationInstance() 
  while True: 
    print 'running...' 
    time.sleep(1) 

也可以检测某一个特定的文件,判断文件是否存在:

import os 
import os.path 
import time 


#class used to handle one application instance mechanism 
class ApplicationInstance: 

  #specify the file used to save the application instance pid 
  def __init__( self, pid_file ): 
    self.pid_file = pid_file 
    self.check() 
    self.startApplication() 

  #check if the current application is already running 
  def check( self ): 
    #check if the pidfile exists 
    if not os.path.isfile( self.pid_file ): 
      return 
    #read the pid from the file 
    pid = 0 
    try: 
      file = open( self.pid_file, 'rt' ) 
      data = file.read() 
      file.close() 
      pid = int( data ) 
    except: 
      pass 
    #check if the process with specified by pid exists 
    if 0 == pid: 
      return 

    try: 
      os.kill( pid, 0 )  #this will raise an exception if the pid is not valid 
    except: 
      return 

    #exit the application 
    print "The application is already running..." 
    exit(0) #exit raise an exception so don't put it in a try/except block 

  #called when the single instance starts to save it's pid 
  def startApplication( self ): 
    file = open( self.pid_file, 'wt' ) 
    file.write( str( os.getpid() ) ) 
    file.close() 

  #called when the single instance exit ( remove pid file ) 
  def exitApplication( self ): 
    try: 
      os.remove( self.pid_file ) 
    except: 
      pass 


if __name__ == '__main__': 
  #create application instance 
  appInstance = ApplicationInstance( '/tmp/myapp.pid' ) 

  #do something here 
  print "Start MyApp" 
  time.sleep(5)  #sleep 5 seconds 
  print "End MyApp" 

  #remove pid file 
  appInstance.exitApplication() 

上述os.kill( pid, 0
)用于检测一个为pid的进程是否还活着,若该pid的进程已经停止则抛出异常,若正在运行则不发送kill信号。

5、socket监听一个特定端口

#!/usr/bin/env python 
#coding: utf-8 
import socket, time, sys 


def ApplicationInstance(): 
  try:   
    global s 
    s = socket.socket() 
    host = socket.gethostname() 
    s.bind((host, 60123)) 
  except: 
    print "instance is running..." 
    sys.exit(0) 

if __name__ == "__main__": 
  ApplicationInstance() 
  while True: 
    print 'running...' 
    time.sleep(1) 

可以将该函数使用装饰器实现,便于重用(效果与上述相同):

#!/usr/bin/env python 
#coding: utf-8 
import socket, time, sys 
import functools 

#使用装饰器实现 
def ApplicationInstance(func): 
  @functools.wraps(func) 
  def fun(*args,**kwargs): 
    import socket 
    try: 
      global s 
      s = socket.socket() 
      host = socket.gethostname() 
      s.bind((host, 60123)) 
    except: 
      print('already has an instance...') 
      return None 
    return func(*args,**kwargs) 
  return fun 

@ApplicationInstance 
def main(): 
  while True: 
    print 'running...' 
    time.sleep(1) 

if __name__ == "__main__": 
  main() 

四、总结

(1)守护进程和单脚本运行在实际应用中比较重要,方法也比较多,可选择合适的来进行修改,可以将它们做成一个单独的类或模板,然后子类化实现自定义。

(2)daemon监控进程自动恢复避免了nohup和&的使用,并配合shell脚本可以省去很多不定时启动挂掉服务器的麻烦。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持帮客之家。

本篇文章主要介绍了Python守护进程和脚本单例运行,小编觉得挺不错的,现在分享给大家…

<?php       
function start($file){
    $path = dirname(__FILE__).'/';
    $runfile = $path.$file.'.run';
    $diefile = $path.$file.'.die';
    $file = $path."data/{$file}.php";
    clearstatcache();
    if(file_exists($runfile)){
        $oldpid = file_get_contents($runfile);
        $nowpid = shell_exec("ps aux | grep 'php -f process.php' | grep ${oldpid} | awk '{print $2}'");
        //如果runfile中的pid号可以匹配到正在运行的,并且上次访问runfile的时间和现在相差小于5min则返回
        if(($oldpid == $nowpid) && (time() - fileatime($runfile) < 300)){
            echo "$file is circle runing no";
            return;
        }else{
            //pid号不匹配或者已经有300秒没有运行循环语句,直接杀掉进程,重新启动
            $pid = file_get_contents($runfile);
            shell_exec("ps aux | grep 'php -f process.php' | grep {$pid} | xargs --if-no-run-empty kill");
        }
    }else{
        //将文件pid写入run文件
        if(!($newpid = getmypid()) || !file_put_contents($runfile,$newpid)){
            return;
        }
        while(true){
            //收到结束进程新号,结束进程,并删除相关文件
            if(file_exists($diefile) && unlink($runfile) && unlink($diefile)){
                return;
            }
            /*这里是守护进程要做的事*/
            file_put_contents($file,"I'm Runing Now".PHP_EOL,FILE_APPEND);
            /***********************/
            touch($runfile);
            sleep(5);
        }
    }
}
start("test");

在一个多任务的计算机操作系统中,守护进程(英语:daemon,/ˈdiːmən/或/ˈdeɪmən/)是一种在后台执行的计算机程序。此类程序会被以进程的形式初始化。守护进程程序的名称通常以字母“d”结尾:例如,syslogd就是指管理系统日志的守护进程。

PHP写守护进程时要注意几点:

daemon
程序是一直运行的服务端程序,又称为守护进程。通常在系统后台运行,没有控制终端不与前台交互,daemon
程序一般作为系统服务使用。daemon是长时间运行的进程,通常在系统启动后就运行,在系统关闭时才结束。一般说Daemon程序在后台运行,是因为它没有控制终端,无法和前台的用户交互。daemon程序一般都作为服务程序使用,等待客户端程序与它通信。我们也把运行的daemon程序称作守护进程。

1.首先就是函数clearstatcache()函数那里,查官方手册可以知道该函数是清除文件状态缓存的,当在一个脚本中多次检查同一个文件的缓存状态时如果不用该函数就会出错,受该函数影响的有:stat(),
lstat(), file_exists(), is_writable(),is_readable(),
is_executable(), is_file(), is_dir(), is_link(),filectime(),
fileatime(), filemtime(), fileinode(), filegroup(),fileowner(),
filesize(), filetype(), fileperms().

通常,守护进程没有任何存在的父进程,且在UNIX系统进程层级中直接位于init之下。守护进程程序通常通过如下方法使自己成为守护进程:对一个子进程运行fork,然后使其父进程立即终止,使得这个子进程能在init下运行。这种方法通常被称为“脱壳”。

2.在多次运行该脚本时,会在运行前进行检测,上次执行循环的时间距离现在大于300s或者pid号不匹配都会重启该进程(时间在每次执行循环式都要更新touch)。

系统通常在启动时一同起动守护进程。守护进程为对网络请求,硬件活动等进行响应,或其他通过某些任务对其他应用程序的请求进行回应提供支持。守护进程也能够对硬件进行配置(如在某些Linux系统上的devfsd),运行计划任务,以及运行其他任务。每个进程都有一个父进程,子进程退出,父进程能得到子进程退出的状态。

3.自动重启也用到了crontab的日程表,将该文件添加入日程表:

守护进程简单地说就是可以脱离终端而在后台运行的进程
. 这在Linux中是非常常见的一种进程 , 比如apache或者mysql等服务启动后 ,
就会以守护进程的方式进驻在内存中
。守护程序是在后台运行的应用程序,而不是由用户直接操作。守护进程的例子是Cron和MySQL。
使用PHP守护进程非常简单,并且需要使用PHP 4.1或更高版本编译参数:–enable-pcntl

crontab -e
#打开日程表,inset模式
*/3 * * * * /usr/bin/php -f process.php
#每3分钟执行一次,放置进程挂掉

假如有个耗时间的任务需要跑在后台 :
将所有mysql中user表中的2000万用户全部导入到redis中做预热缓存 ,
那么这个任务估计一时半会是不会结束的 ,
这个时候就需要编写一个php脚本以daemon形式运行在系统中 ,
结束后自动推出。

这样就基本ok了,要是有具体功能的话还需改动代码。

在Linux中 , 有三种方式实现脚本后台化 :

1 . 在命令后添加一个&符号

比如php task.php &. 这个方法的缺点在于
如果terminal终端关闭 , 无论是正常关闭还是非正常关闭 ,
这个php进程都会随着终端关闭而关闭 ,
其次是代码中如果有echo或者print_r之类的输出文本 ,
会被输出到当前的终端窗口中 。

2 . 使用nohup命令

比如 nohup php task.php & .
默认情况下 ,
代码中echo或者print_r之类输出的文本会被输出到php代码同级目录的nohup.out文件中
. 如果你用exit命令或者关闭按钮等正常手段关闭终端 , 该进程不会被关闭 ,
依然会在后台持续运行 . 但是如果终端遇到异常退出或者终止 ,
该php进程也会随即退出 . 本质上 , 也并非稳定可靠的daemon方案 。

3 .通过pcntlposix扩展实现

编程中需要注意的地方有:

  • 通过二次pcntl_fork()以及posix_setsid让主进程脱离终端
  • 通过pcntl_signal()忽略或者处理SIGHUP信号
  • 多进程程序需要通过二次pcntl_fork()或者pcntl_signal()忽略SIGCHLD信号防止子进程变成
    Zombie 进程
  • 通过umask()设定文件权限掩码,防止继承文件权限而来的权限影响功能
  • 将运行进程的STDIN/STDOUT/STDERR重定向到/dev/null或者其他流上

daemon有如下特征:

  • 没有终端
  • 后台运行
  • 父进程 pid 为1

想要查看运行中的守护进程可以通过ps -ax或者ps -ef查看,其中-x表示会列出没有控制终端的进程。

fork 系统调用

fork系统调用用于复制一个与父进程几乎完全相同的进程,新生成的子进程不同的地方在于与父进程有着不同的
pid
以及有不同的内存空间,根据代码逻辑实现,父子进程可以完成一样的工作,也可以不同。子进程会从父进程中继承比如文件描述符一类的资源。

PHP
中的pcntl扩展中实现了pcntl_fork()函数,用于在 PHP 中 fork
新的进程。

setsid 系统调用

setsid系统调用则用于创建一个新的会话并设定进程组
id。这里有几个概念:会话进程组

  在 Linux
中,用户登录产生一个会话,一个会话中包含一个或者多个进程组,一个进程组又包含多个进程。每个进程组有一个组长(Session Leader),它的 pid
就是进程组的组
id。进程组长一旦打开一个终端,这一个终端就被称为控制终端。一旦控制终端发生异常,会发出信号到进程组组长。

  后台运行程序(如 shell
中以&结尾执行指令)在终端关闭之后也会被杀死,就是没有处理好控制终端断开时发出的SIGHUP信号,而SIGHUP信号对于进程的默认行为则是退出进程。

调用setsid系统调用之后,会让当前的进程新建一个进程组,如果在当前进程中不打开终端的话,那么这一个进程组就不会存在控制终端,也就不会出现因为关闭终端而杀死进程的问题。

PHP
中的posix扩展中实现了posix_setsid()函数,用于在 PHP
中设定新的进程组。

二次 fork 的作用

首先,setsid系统调用不能由进程组组长调用,会返回-1。

二次 fork 操作的样例代码如下:

$pid1 = pcntl_fork();if ($pid1 > 0) {
    // 父进程会得到子进程号,所以这里是父进程执行的逻辑    exit('parent process. 1'."n");} else if ($pid1 < 0) {    exit("Failed to fork 1n");}if (-1 == posix_setsid {    exit("Failed to setsidn");}$pid2 = pcntl_fork();if ($pid2 > 0) {    exit('parent process. 2'."n");} else if ($pid2 < 0) {    exit("Failed to fork 2n");}

pcntl_fork()函数创建一个子进程,这个子进程仅PID
和PPID与其父进程不同。

返回值

  成功时,在父进程执行线程内返回产生的子进程的PID,在子进程执行线程内返回
0,失败时,在 父进程上下文返回
-1,不会创建子进程,并且会引发一个PHP错误。

假定我们在终端中执行应用程序,进程为
a,第一次 fork 会生成子进程 b,如果 fork 成功,父进程 a 退出。b
作为孤儿进程,被 init 进程托管。

此时,进程 b 处于进程组 a 中,进程 b
调用posix_setsid要求生成新的进程组,调用成功后当前进程组变为
b。

php fork2.php parent process. 1parent process. 2

此时进程 b
事实上已经脱离任何的控制终端,例程:

cli_set_process_title('process_a');$pidA = pcntl_fork();if ($pidA > 0) {    exit;} else if ($pidA < 0) {    exit;}cli_set_process_title('process_b');if (-1 === posix_setsid {    exit;}while {    sleep;}

执行程序之后:  

$ php cli-title.php $ ps ax | grep -v grep | grep -E 'process_|PID'  PID TTY      STAT   TIME COMMAND15725 ?        Ss     0:00 process_b

重新打开一个shell窗口,效果一样,都在呢

从 ps 的结果来看,process_b 的 TTY
已经变成了,即没有对应的控制终端。

代码走到这里,似乎已经完成了功能,关闭终端之后
process_b 也没有被杀死,但是为什么还要进行第二次 fork 操作呢?

StackOverflow
上的一个回答写的很好:

The second fork is there to ensure that
the new process is not a session leader, so it won’t be able to
(accidentally) allocate a controlling terminal, since daemons are not
supposed to ever have a controlling terminal.

这是为了防止实际的工作的进程主动关联或者意外关联控制终端,再次
fork
之后生成的新进程由于不是进程组组长,是不能申请关联控制终端的。

综上,二次 fork 与
setsid 的作用是生成新的进程组,防止工作进程关联控制终端。
 

写一个demo测试下

<?php// 第一次fork系统调用$pid_A = pcntl_fork();// 父进程 和 子进程 都会执行下面代码if ($pid_A < 0) {    // 错误处理: 创建子进程失败时返回-1.    exit('A fork error ');} else if ($pid_A > 0) {     // 父进程会得到子进程号,所以这里是父进程执行的逻辑    exit("A parent process exit n");}// B 作为孤儿进程,被 init 进程托管,此时,进程 B 处于进程组 A 中// 子进程得到的$pid为0, 所以以下是子进程执行的逻辑,受控制终端的影响,控制终端关闭则这里也会退出// [子进程] 控制终端未关闭前,将当前子进程提升会会话组组长,及进程组的leader// 进程 B 调用 posix_setsid 要求生成新的进程组,调用成功后当前进程组变为 Bif (-1 == posix_setsid {    exit("Failed to setsidn");}// 此时进程 B 已经脱离任何的控制终端// [子进程]  这时候在中,重新fork系统调用$pid_B = pcntl_fork();if ($pid_B < 0) {    exit('B fork error ');} else if ($pid_B > 0) {    exit("B parent process exit n");}// [新子进程] 这里是新生成的进程组,不受控制终端的影响,写写自己的业务逻辑代码for ($i = 1; $i <= 100; $i++) {    sleep;    file_put_contents('daemon.log',$i . "--" . date("Y-m-d H:i:s", time . "n",FILE_APPEND);} 

Window 下跑回直接抛出异常

php runtimedaemon.phpPHP Fatal error:  Uncaught Error: Call to undefined function pcntl_fork() in D:phpStudyPHPTutorialWWWnotesruntimedaemon.php:13Stack trace:#0 {main}  thrown in D:phpStudyPHPTutorialWWWnotesruntimedaemon.php on line 13

Linux 下执行,输出结果

php daemon.php
...97--2018-09-07 03:50:0998--2018-09-07 03:50:1099--2018-09-07 03:50:11100--2018-09-07 03:50:12

所以,现在即使关闭了终端,改脚本任然在后台守护进程运行

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。

参考:

1、

2、

发表评论

电子邮件地址不会被公开。 必填项已用*标注