python中的daemon守护进程实现

发布时间:2019-07-23 09:42:56编辑:auto阅读(1235)

    守护进程是生存期长的一种进程。它们独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件。他们常常在系统引导装入时启动,在系统关闭时终止。

    守护进程的特性
    1.在后台运行
    2.与其运行前的环境隔离开来。这些环境包括未关闭的文件描述符、控制终端、会话和进程组、工作目录以及文件创建掩码等。这些环境通常是守护进程从执行它的父进程(特别是shell)中继承下来的。
    3.启动方式特殊,它可以在系统启动时从启动脚本/etc/rc.d中启动,可以由inetd守护进程启动,可以由crond启动,还可以由用户终端(通常是shell)执行。
    总之,除开这些特殊性以外,守护进程与普通进程基本上没有什么区别。因此,编写守护进程实际上是把一个普通进程按照上述的守护进程的特性改造成为守护进程。

    守护进程编程规则
    1.在后台运行,调用fork ,然后使父进程exit
    2.脱离控制终端,登录会话和进程组,调用setsid()使进程成为会话组长
    3.禁止进程重新打开控制终端
    4.关闭打开的文件描述符,调用fclose()
    5.将当前工作目录更改为根目录。
    6.重设文件创建掩码为0
    7.处理SIGCHLD 信号

    下面是一个的demo源码示例:

    #!/usr/bin/env python
    #encoding: utf-8
    #description: 一个守护进程的简单包装类, 具备常用的start|stop|restart|status功能, 使用方便
    #             需要改造为守护进程的程序只需要重写基类的run函数就可以了
    #date: 2015-10-29
    #usage: 启动: python daemon_class.py start
    #       关闭: python daemon_class.py stop
    #       状态: python daemon_class.py status
    #       重启: python daemon_class.py restart
    #       查看: ps -axj | grep daemon_class
    
    import atexit, os, sys, time, signal
    
    class CDaemon:
        '''
        a generic daemon class.
        usage: subclass the CDaemon class and override the run() method
        stderr  表示错误日志文件绝对路径, 收集启动过程中的错误日志
        verbose 表示将启动运行过程中的异常错误信息打印到终端,便于调试,建议非调试模式下关闭, 默认为1, 表示开启
        save_path 表示守护进程pid文件的绝对路径
        '''
        def __init__(self, save_path, stdin=os.devnull, stdout=os.devnull, stderr=os.devnull, home_dir='.', umask=022, verbose=1):
            self.stdin = stdin
            self.stdout = stdout
            self.stderr = stderr
            self.pidfile = save_path #pid文件绝对路径
            self.home_dir = home_dir
            self.verbose = verbose #调试开关
            self.umask = umask
            self.daemon_alive = True
    
        def daemonize(self):
            try:
                pid = os.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(self.home_dir)
            os.setsid()
            os.umask(self.umask)
    
            try:
                pid = os.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+')
            if self.stderr:
                se = file(self.stderr, 'a+', 0)
            else:
                se = so
    
            os.dup2(si.fileno(), sys.stdin.fileno())
            os.dup2(so.fileno(), sys.stdout.fileno())
            os.dup2(se.fileno(), sys.stderr.fileno())
    
            def sig_handler(signum, frame):
                self.daemon_alive = False
            signal.signal(signal.SIGTERM, sig_handler)
            signal.signal(signal.SIGINT, sig_handler)
    
            if self.verbose >= 1:
                print 'daemon process started ...'
    
            atexit.register(self.del_pid)
            pid = str(os.getpid())
            file(self.pidfile, 'w+').write('%s\n' % pid)
    
        def get_pid(self):
            try:
                pf = file(self.pidfile, 'r')
                pid = int(pf.read().strip())
                pf.close()
            except IOError:
                pid = None
            except SystemExit:
                pid = None
            return pid
    
        def del_pid(self):
            if os.path.exists(self.pidfile):
                os.remove(self.pidfile)
    
        def start(self, *args, **kwargs):
            if self.verbose >= 1:
                print 'ready to starting ......'
            #check for a pid file to see if the daemon already runs
            pid = self.get_pid()
            if pid:
                msg = 'pid file %s already exists, is it already running?\n'
                sys.stderr.write(msg % self.pidfile)
                sys.exit(1)
            #start the daemon
            self.daemonize()
            self.run(*args, **kwargs)
    
        def stop(self):
            if self.verbose >= 1:
                print 'stopping ...'
            pid = self.get_pid()
            if not pid:
                msg = 'pid file [%s] does not exist. Not running?\n' % self.pidfile
                sys.stderr.write(msg)
                if os.path.exists(self.pidfile):
                    os.remove(self.pidfile)
                return
            #try to kill the daemon process
            try:
                i = 0
                while 1:
                    os.kill(pid, signal.SIGTERM)
                    time.sleep(0.1)
                    i = i + 1
                    if i % 10 == 0:
                        os.kill(pid, signal.SIGHUP)
            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)
                if self.verbose >= 1:
                    print 'Stopped!'
    
        def restart(self, *args, **kwargs):
            self.stop()
            self.start(*args, **kwargs)
    
        def is_running(self):
            pid = self.get_pid()
            #print(pid)
            return pid and os.path.exists('/proc/%d' % pid)
    
        def run(self, *args, **kwargs):
            'NOTE: override the method in subclass'
            print 'base class run()'
    
    class ClientDaemon(CDaemon):
        def __init__(self, name, save_path, stdin=os.devnull, stdout=os.devnull, stderr=os.devnull, home_dir='.', umask=022, verbose=1):
            CDaemon.__init__(self, save_path, stdin, stdout, stderr, home_dir, umask, verbose)
            self.name = name #派生守护进程类的名称
    
        def run(self, output_fn, **kwargs):
            fd = open(output_fn, 'w')
            while True:
                line = time.ctime() + '\n'
                fd.write(line)
                fd.flush()
                time.sleep(1)
            fd.close()
    
    
    if __name__ == '__main__':
        help_msg = 'Usage: python %s <start|stop|restart|status>' % sys.argv[0]
        if len(sys.argv) != 2:
            print help_msg
            sys.exit(1)
        p_name = 'clientd' #守护进程名称
        pid_fn = '/tmp/daemon_class.pid' #守护进程pid文件的绝对路径
        log_fn = '/tmp/daemon_class.log' #守护进程日志文件的绝对路径
        err_fn = '/tmp/daemon_class.err.log' #守护进程启动过程中的错误日志,内部出错能从这里看到
        cD = ClientDaemon(p_name, pid_fn, stderr=err_fn, verbose=1)
    
        if sys.argv[1] == 'start':
            cD.start(log_fn)
        elif sys.argv[1] == 'stop':
            cD.stop()
        elif sys.argv[1] == 'restart':
            cD.restart(log_fn)
        elif sys.argv[1] == 'status':
            alive = cD.is_running()
            if alive:
                print 'process [%s] is running ......' % cD.get_pid()
            else:
                print 'daemon process [%s] stopped' %cD.name
        else:
            print 'invalid argument!'
            print help_msg
    下面是运行截图

    产生的日志文件为

    参考文档
    http://www.jb51.net/article/54199.htm 都不错,这个守护进程类包装非常完备,我已经重新整理了一遍

关键字