[zz] 用 python 写 deamon程序

A simple unix/linux daemon in Python

by Sander Marechal

I've written a simple Python class for creating daemons on unix/linux systems. It was pieced together for various other examples, mostly corrections to various Python Cookbook articles and a couple of examples posted to the Python mailing lists. It has support for a pidfile to keep track of the process. I hope it's useful to someone.

Below is the Daemon class. To use it, simply subclass it and implement the run() method. Download this file.

Update 2009-05-31: An anonymous contributor has written a version of the Daemon class suitable for Python 3.x. Download the Python 3.x version here. The code below is for Python 2.x

  1. #!/usr/bin/env python
  2.  
  3. import sys, os, time, atexit
  4. from signal import SIGTERM
  5.  
  6. class Daemon:
  7.         """
  8.         A generic daemon class.
  9.        
  10.         Usage: subclass the Daemon class and override the run() method
  11.         """
  12.         def __init__(self, pidfile, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
  13.                 self.stdin = stdin
  14.                 self.stdout = stdout
  15.                 self.stderr = stderr
  16.                 self.pidfile = pidfile
  17.        
  18.         def daemonize(self):
  19.                 """
  20.                 do the UNIX double-fork magic, see Stevens' "Advanced
  21.                 Programming in the UNIX Environment" for details (ISBN 0201563177)
  22.                 http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16
  23.                 """
  24.                 try:
  25.                         pid = os.fork()
  26.                         if pid > 0:
  27.                                 # exit first parent
  28.                                 sys.exit(0)
  29.                 except OSError, e:
  30.                         sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror))
  31.                         sys.exit(1)
  32.        
  33.                 # decouple from parent environment
  34.                 os.chdir("/")
  35.                 os.setsid()
  36.                 os.umask(0)
  37.        
  38.                 # do second fork
  39.                 try:
  40.                         pid = os.fork()
  41.                         if pid > 0:
  42.                                 # exit from second parent
  43.                                 sys.exit(0)
  44.                 except OSError, e:
  45.                         sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror))
  46.                         sys.exit(1)
  47.        
  48.                 # redirect standard file descriptors
  49.                 sys.stdout.flush()
  50.                 sys.stderr.flush()
  51.                 si = file(self.stdin, 'r')
  52.                 so = file(self.stdout, 'a+')
  53.                 se = file(self.stderr, 'a+', 0)
  54.                 os.dup2(si.fileno(), sys.stdin.fileno())
  55.                 os.dup2(so.fileno(), sys.stdout.fileno())
  56.                 os.dup2(se.fileno(), sys.stderr.fileno())
  57.        
  58.                 # write pidfile
  59.                 atexit.register(self.delpid)
  60.                 pid = str(os.getpid())
  61.                 file(self.pidfile,'w+').write("%s\n" % pid)
  62.        
  63.         def delpid(self):
  64.                 os.remove(self.pidfile)
  65.  
  66.         def start(self):
  67.                 """
  68.                 Start the daemon
  69.                 """
  70.                 # Check for a pidfile to see if the daemon already runs
  71.                 try:
  72.                         pf = file(self.pidfile,'r')
  73.                         pid = int(pf.read().strip())
  74.                         pf.close()
  75.                 except IOError:
  76.                         pid = None
  77.        
  78.                 if pid:
  79.                         message = "pidfile %s already exist. Daemon already running?\n"
  80.                         sys.stderr.write(message % self.pidfile)
  81.                         sys.exit(1)
  82.                
  83.                 # Start the daemon
  84.                 self.daemonize()
  85.                 self.run()
  86.  
  87.         def stop(self):
  88.                 """
  89.                 Stop the daemon
  90.                 """
  91.                 # Get the pid from the pidfile
  92.                 try:
  93.                         pf = file(self.pidfile,'r')
  94.                         pid = int(pf.read().strip())
  95.                         pf.close()
  96.                 except IOError:
  97.                         pid = None
  98.        
  99.                 if not pid:
  100.                         message = "pidfile %s does not exist. Daemon not running?\n"
  101.                         sys.stderr.write(message % self.pidfile)
  102.                         return # not an error in a restart
  103.  
  104.                 # Try killing the daemon process       
  105.                 try:
  106.                         while 1:
  107.                                 os.kill(pid, SIGTERM)
  108.                                 time.sleep(0.1)
  109.                 except OSError, err:
  110.                         err = str(err)
  111.                         if err.find("No such process") > 0:
  112.                                 if os.path.exists(self.pidfile):
  113.                                         os.remove(self.pidfile)
  114.                         else:
  115.                                 print str(err)
  116.                                 sys.exit(1)
  117.  
  118.         def restart(self):
  119.                 """
  120.                 Restart the daemon
  121.                 """
  122.                 self.stop()
  123.                 self.start()
  124.  
  125.         def run(self):
  126.                 """
  127.                 You should override this method when you subclass Daemon. It will be called after the process has been
  128.                 daemonized by start() or restart().
  129.                 """

And here is an example implementation. It implements the daemon as well as it's controlling client. Simply invoke this script with start, stop or restart as it's first argument. Download this file.

  1. #!/usr/bin/env python
  2.  
  3. import sys, time
  4. from daemon import Daemon
  5.  
  6. class MyDaemon(Daemon):
  7.         def run(self):
  8.                 while True:
  9.                         time.sleep(1)
  10.  
  11. if __name__ == "__main__":
  12.         daemon = MyDaemon('/tmp/daemon-example.pid')
  13.         if len(sys.argv) == 2:
  14.                 if 'start' == sys.argv[1]:
  15.                         daemon.start()
  16.                 elif 'stop' == sys.argv[1]:
  17.                         daemon.stop()
  18.                 elif 'restart' == sys.argv[1]:
  19.                         daemon.restart()
  20.                 else:
  21.                         print "Unknown command"
  22.                         sys.exit(2)
  23.                 sys.exit(0)
  24.         else:
  25.                 print "usage: %s start|stop|restart" % sys.argv[0]
  26.                 sys.exit(2)

That's it! I hope this is of some use to someone. Happy coding!

 

记得刚入职的时候,那时候什么都不懂,组长让我跑个迁移程序,还没跑完就关终端走人了,结果可想而知,那是第一次知道守护进程的概念。
当时后来是加了nohup参数解决的,
nohup ./program &
但是总是强迫别人用nohup来启动自己的程序毕竟不是办法,所以还是要把自己的进程变成守护进程才行。

C/C++的版本就不说了,这里有篇文章写的很清楚。
http://colding.bokee.com/5277082.html

这里主要介绍一下在网上无意发现的一个国外哥们的写的python版本:
http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/
顺便吐个槽,这哥们用的Vim配色明显是Wombat~~
代码如下(对私有函数名加了_前缀,便于理解,并加了一定的注释):

#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys, os, time, atexit
from signal import SIGTERM
class Daemon:
    """
    A generic daemon class.

    Usage: subclass the Daemon class and override the _run() method
    """
    def __init__(self, pidfile, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
        self.stdin = stdin
        self.stdout = stdout
        self.stderr = stderr
        self.pidfile = pidfile

    def _daemonize(self):
        """
        do the UNIX double-fork magic, see Stevens' "Advanced 
        Programming in the UNIX Environment" for details (ISBN 0201563177)
http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16
        """

        #脱离父进程
        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.setsid()
        #修改当前工作目录  
        os.chdir("/")
        #重设文件创建权限
        os.umask(0)

        #第二次fork,禁止进程重新打开控制终端
        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+')
        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("%s\n" % pid)

    def delpid(self):
        os.remove(self.pidfile)
    def start(self):
        """
        Start the daemon
        """
        # Check for a pidfile to see if the daemon already runs
        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)

        # Start the daemon
        self._daemonize()
        self._run()
    def stop(self):
        """
        Stop the daemon
        """
        # Get the pid from the pidfile
        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 # not an error in a restart
        # Try killing the daemon process    
        try:
            while 1:
                os.kill(pid, SIGTERM)
                time.sleep(0.1)
        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):
        """
        Restart the daemon
        """
        self.stop()
        self.start()
    def _run(self):
        """
        You should override this method when you subclass Daemon. It will be called after the process has been
        daemonized by start() or restart().
        """

class MyDaemon(Daemon):
    def _run(self):
        while True:
            time.sleep(1)

if __name__ == "__main__":
    daemon = MyDaemon('/tmp/daemon-example.pid')
    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)


简单解释一下,整个类实现的功能:

1.进程脱离父进程及终端绑定

2.进程唯一性保证

3.标准输入/输出/错误重定向

附:

源代码下载

OK,就这样~

 

一个别人写的模块,可以直接用来把一个普通进程改为守护进程
并且自动把标准输出重定向到日志文件,很实用啊。

   1. '''
   2.     This module is used to fork the current process into a daemon.
   3.     Almost none of this is necessary (or advisable) if your daemon
   4.     is being started by inetd. In that case, stdin, stdout and stderr are
   5.     all set up for you to refer to the network connection, and the fork()s
   6.     and session manipulation should not be done (to avoid confusing inetd).
   7.     Only the chdir() and umask() steps remain as useful.
   8.     References:
   9.         UNIX Programming FAQ
  10.             1.7 How do I get my program to act like a daemon?
  11.                 [url]http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16[/url]
  12.         Advanced Programming in the Unix Environment
  13.             W. Richard Stevens, 1992, Addison-Wesley, ISBN 0-201-56317-7.
  14.

  15.     History:
  16.       2001/07/10 by Juergen Hermann
  17.       2002/08/28 by Noah Spurrier
  18.       2003/02/24 by Clark Evans
  19.       2003/11/01 by Irmen de Jong --- adapted a bit for Snakelets
  20.                                     (raises exception at certain points)
  21.      
  22.       [url]http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66012[/url]
  23. '''
  24. import sys, os, time
  25. from signal import SIGINT,SIGTERM,SIGKILL
  26.

  27. def daemonize(stdout='/dev/null', stderr=None, stdin='/dev/null',
  28.               pidfile=None, startmsg = 'started with pid %s' ):
  29.     '''
  30.         This forks the current process into a daemon.
  31.         The stdin, stdout, and stderr arguments are file names that
  32.         will be opened and be used to replace the standard file descriptors
  33.         in sys.stdin, sys.stdout, and sys.stderr.
  34.         These arguments are optional and default to /dev/null.
  35.         Note that stderr is opened unbuffered, so
  36.         if it shares a file with stdout then interleaved output
  37.         may not appear in the order that you expect.
  38.     '''
  39.

  40.     # flush io
  41.     sys.stdout.flush()
  42.     sys.stderr.flush()
  43.

  44.     # Do first fork.
  45.     try:
  46.         pid = os.fork()
  47.         if pid > 0: sys.exit(0) # Exit first parent.
  48.     except OSError, e:
  49.         sys.stderr.write("fork #1 failed: (%d) %s\n" % (e.errno, e.strerror))
  50.         sys.exit(1)
  51.        
  52.     # Decouple from parent environment.
  53.     os.chdir("/")
  54.     os.umask(0)
  55.     os.setsid()
  56.   
  57.     # Do second fork.
  58.     try:
  59.         pid = os.fork()
  60.         if pid > 0: sys.exit(0) # Exit second parent.
  61.     except OSError, e:
  62.         sys.stderr.write("fork #2 failed: (%d) %s\n" % (e.errno, e.strerror))
  63.         sys.exit(1)
  64.   
  65.     # Open file descriptors and print start message
  66.     if not stderr: stderr = stdout
  67.     si = file(stdin, 'r')
  68.     so = file(stdout, 'a+')
  69.     se = file(stderr, 'a+', 0)  #unbuffered
  70.     pid = str(os.getpid())
  71.     sys.stderr.write("\n%s\n" % startmsg % pid)
  72.     sys.stderr.flush()
  73.     if pidfile: file(pidfile,'w+').write("%s\n" % pid)
  74.   
  75.     # Redirect standard file descriptors.
  76.     os.dup2(si.fileno(), sys.stdin.fileno())
  77.     os.dup2(so.fileno(), sys.stdout.fileno())
  78.     os.dup2(se.fileno(), sys.stderr.fileno())
  79.

  80.


  81. class DaemonizeError(Exception): pass
  82.

  83.


  84. def startstop(stdout='/dev/null', stderr=None, stdin='/dev/null',
  85.               pidfile='pid.txt', startmsg = 'started with pid %s', action=None ):
  86.              
  87.     if not action and len(sys.argv) > 1:
  88.         action = sys.argv[1]
  89.

  90.     if action:
  91.         try:
  92.             pf  = file(pidfile,'r')
  93.             pid = int(pf.read().strip())
  94.             pf.close()
  95.         except IOError:
  96.             pid = None
  97.         if 'stop' == action or 'restart' == action:
  98.             if not pid:
  99.                 mess = "Could not stop, pid file '%s' missing.\n"
 100.                 raise DaemonizeError(mess % pidfile)
 101.             try:
 102.                while 1:
 103.                    print "sending SIGINT to",pid
 104.                    os.kill(pid,SIGINT)
 105.                    time.sleep(2)
 106.                    print "sending SIGTERM to",pid
 107.                    os.kill(pid,SIGTERM)
 108.                    time.sleep(2)
 109.                    print "sending SIGKILL to",pid
 110.                    os.kill(pid,SIGKILL)
 111.                    time.sleep(1)
 112.             except OSError, err:
 113.                print "process has been terminated."
 114.                os.remove(pidfile)
 115.                if 'stop' == action:
 116.                    return    ## sys.exit(0)
 117.                action = 'start'
 118.                pid = None
 119.         if 'start' == action:
 120.             if pid:
 121.                 mess = "Start aborted since pid file '%s' exists. Server still running?\n"
 122.                 raise DaemonizeError(mess % pidfile)
 123.             daemonize(stdout,stderr,stdin,pidfile,startmsg)
 124.             return
 125.     print "usage: %s start|stop|restart" % sys.argv[0]
 126.     raise DaemonizeError("invalid command")
 127.

 128. def test():
 129.     '''
 130.         This is an example main function run by the daemon.
 131.         This prints a count and timestamp once per second.
 132.     '''
 133.     sys.stdout.write ('Message to stdout...')
 134.     sys.stderr.write ('Message to stderr...')
 135.     c = 0
 136.     while 1:
 137.         sys.stdout.write ('%d: %s\n' % (c, time.ctime(time.time())) )
 138.         sys.stdout.flush()
 139.         c = c + 1
 140.         time.sleep(1)
 141.

 142. if __name__ == "__main__":
 143.     startstop(stdout='/tmp/daemonize.log',
 144.               pidfile='/tmp/daemonize.pid')
 145.     if sys.argv[1]in ('start', 'restart'):
 146.         test()

 

 

# server.py
while True:
   continue

好了现在我们已经有了一个服务器了, 接下来要做的事情就是让他跑到后台去。
第一章, 原理
本章仅作了解就可以了, 因为这些细节性的东西实在是无聊,
所以我强烈推荐你直接跳到第二章。

   当然, 如果你有足够的耐心和能力, 还是看下去吧 ……

通常我们把后台进程叫做 "守护进程"、"精灵进程", 或者 daemon。
在 Unix 下, 我们使用 fork 实现。

   import os
   def daemonize():
      # 首先 fork 出两个进程
      pid = os.fork()

      # pid 非零的是父进程,
      # 父进程就是你用命令启动的那个程序。
      #
      if pid != 0:
         # 接着我们退出父进程
         # 这样你的命令行程序就退出了。
         #
         # 父进程退出后, 子进程就被系统托管了
         # 这时子进程就转入后台执行
         os._exit(0)

      # 子进程开始
      # 我们在这里启动后台程序 (服务器)
      os.system('python server.py')
      # 当然, 你永远不会使用 os.system 来启动一个程序
      # os.system 会启动 python、server.py 还有你不希望看到的 sh 进程

当然, 真正能用的 daemonize 程序还要作更多的事情,
这些都被记载在典籍《Unix 高级环境编程》中。

我归纳了一下, 分别是一下几个方面。

   1. 切换程序的身份。
      请看 Apache 的设计, 在使用 root 得到 80 端口后旋即转换成 nobody (www-data) 用户。
      很明显, 使用 root 身份执行一个程序是危险的, 后台程序的身份不应该是 root。

         最实用的选择是: 当配置文件里没有说明以何身份执行时,
         就以当前用户的身份启动后台进程。

   2. 自定义部分信号处理程序。

         如果不是必须, 依我看还是不要做太多信号处理,
         否则你的服务器很可能将只能吃到 kill -9 才可退出了, 这显然很蠢。
      如果你不想看到太多的 undead, 处理 SIGCHLD 是必须的。

   3. 重定向 stdin、stdout 和 stderr。
      标准输入输出对 daemon 进程而言没有意义, 因为后台进程是没有终端可供使用的。

         注意, 这里是重定向而不是关闭, 这样可以避免程序在 print 的时候出错。

         我们通常会将 stdout、stderr 重定向到日志文件。
         /dev/nul 是选择之一。
         stdin 是没有意义的。

   4. setsid(), umask(), chdir() 逐一 google。这里不赘述了。

   5. 把 pid 写到 xxx.pid 文件中去。
      当我们需要结束后台进程的时候, 我们就可以从 pid 文件中得到子进程的 pid,
      然后 kill PID。

目前, Python 最好的 Daemon 库依然是 Zope 的 zdaemon,
欲了解健壮的 Daemon 实现, 可以阅读 zdaemon 的源代码。
当然, 阅读 zdaemon 源码是比较痛苦的, 我自己也比较痛恨 zdaemon 的编码风格。

   我痛恨一切没有必要的, 过度的 OO 设计。

zdaemon 的贡献是它引入了 "进程控制器" 这个概念, 它的 daemonize 使用了两次 fork。

   # zdaemon.py
   import os
   def zdaemonize():
      # 首次 fork, 进入后台
      pid = os.fork()

      if pid != 0:
         os._exit(0)

      # 第二次 fork
      pid = os.fork()

      # 父进程, Daemon 管理器
      if pid != 0:
         # 启动进程管理器服务器
         # 这里, manager_server 是假想的
         import manager_server
         manger_server.serve_forever()

      # 子进程, 启动后台服务
      os.system('python server.py')

进程管理器 manger_server 通常是一个 TCP 服务, 他使用 Unix Socket 与外界交互。
这样, pid 文件就变成了 Unix Socket 文件。
你通过 Unix Socket (TCP Socket) 接口对进程管理器发送控制指令,
比如 'start'、'status' 和 'stop',
进程管理器再对他的子进程 (后台守护进程) 发送控制信号, 启动或者关闭守护进程。

通过进程管理器, 后台进程的控制就和后台进程的运行逻辑分开了。

zdaemon 也有一个致命缺点, 其过度对象化的设计, 使封装层次失控,
加上采用了底层的 Unix Socket, 使其代码量过度膨胀。

   人类能够理解的代码行数是有限制的。
      --《Unix 编程艺术》

   所以 Python 的语法决定了 Python 能够编写出目前最复杂的程序。
   代码量失控是 Python 的最大敌人, 这说明我们可能正在用 Java 写 Python。

为此我修改了 zdaemon, 将它实现在 270 行代码中。
用 Local XMLRPC Server (Unix Socket) 取代 zdaemon 的 Socket 底层,
为我赢得了不少行数。

同样, 在编写 Local XMLRPC Server 的时候, 再次遇到 Python 标准库 SimpleXMLRPCServer.py
的过度封装问题, 有兴趣的话大家可以阅读第三章 Local XMLRPC Server 的实现部分,

   过度向对象实际上就是完全无法重用, 这是一个典型的例子。

使用 XMLRPC Server 的额外好处,
就是让我们可以更方便地增加除了 start、stop、status 这种控制命令。

   daemon = Daemon(...)
   daemon.register_function(...)

   不过, 额外的控制命令, 通常是不需要的。

我的 daemon 程序 (daemon.py) 源码放在第三章中,
同样, 阅读源码是一件枯燥的事情, 你可以忽略掉第三章。
但是第二章是必须理解的。

第二章、使用 daemon.py 的通用范例,你可以不用修改就用于你的项目

#!/usr/bin/env python2.5
# -*- coding: utf-8 -*-

import os, sys
from os import fork
from time import sleep
from sys import stderr, stdout
from socket import error as SocketError
from traceback import print_exc

import schema # 载入配置信息, 还记得《配置文件》一招鲜吗?
import pyetc  # ... 要不要复习一下

from daemon import Daemon, ServerProxy, Fault, \
   error as DaemonError
## #

def usage():
   print '使用方法: %s start|stop|status' %sys.argv[0]

def start():
   # 读取配置文件
   # demo.conf
   pyetc.load(schema.ETC('demo.conf'), env=schema.env)
   conf = schema.config.daemon
   # ... 请见《配置文件一招鲜》最后一个范例
   try:
      # 创建 Daemon 对象
      daemon = Daemon(
         address = conf.address, # 进程控制器地址/pid 文件位置
         program = conf.program, # 后台进程程序位置
         verbose = conf.verbose  # 调试
         )
      print '进程管理器已经启动'
      # 启动后台进程
      #    daemon(arg1, arg2, ...)
      #    参数 arg1, arg2 ... 将被用于启动后台进程,
      #    这里相当于命令行:
      #       program.py arg1 arg2 ...
      daemon()
   except DaemonError, msg:
      print '进程管理器未启动, 原因是: ', msg
   except:
      print_exc(file=stderr)

def stop():
   pyetc.load(schema.ETC('demo.conf'), env=schema.env)
   conf = schema.config.daemon
   # 取得进程控制器
   try:
      daemon = ServerProxy(conf.address)
      daemon.stop()
      print '进程已经退出'
   except SocketError:
      print '进程管理器未启动'
   except Fault:
      print '进程管理器已被强制关闭'
   except:
      print_exc(file=stderr)

def status():
   pyetc.load(schema.ETC('demo.conf'), env=schema.env)
   conf = schema.config.daemon
   try:
      daemon = ServerProxy(conf.address)
      status = daemon.status()
      if status == 'running':
         print '进程 "%s" 正在运行' %conf.program
      elif status == 'stopped':

stdout.write( ('进程管理器正在运行, 但是进程 "%s" 已经停止, '
            '正在停止进程管理器 ... '
            ) %conf.program )
         stop()
      else:
         print '进程管理器正在运行, 进程状态未知'
   except SocketError:
      print '进程管理器未启动'
   except:
      print_exc(file=stderr)
## #
# 解析参数
if len(sys.argv) != 2:
   usage()
elif sys.argv[1] == 'start':
   start()
elif sys.argv[1] == 'stop':
   stop()
elif sys.argv[1] == 'status':
   status()
else: usage()

第三章、附件: daemon.py
import os, sys
from httplib import HTTP, HTTPConnection
from pwd import getpwnam, getpwuid
from signal import signal as setsignal, SIGCHLD, SIGTERM
from sys import stderr, stdout
from urllib import urlopen
from SocketServer import UnixStreamServer
from socket import socket, error as SocketError, \
   AF_UNIX, SOCK_STREAM
from SimpleXMLRPCServer import SimpleXMLRPCDispatcher, \
   SimpleXMLRPCRequestHandler, SimpleXMLRPCServer
from xmlrpclib import Fault, Transport, dumps as xmlrpc_dumps, \
   _Method as _XMLRPCMethod, ServerProxy as ServerProxy_N___G
from os import execv, chdir, chmod, fork, geteuid, getpid, \
   kill, setgid, setuid, umask, unlink, waitpid, \
   error as OSError, WNOHANG

class error(Exception): pass

class nul:
   write = staticmethod(lambda s: None)
   flush = staticmethod(lambda  : None)
   read  = staticmethod(lambda n: ''  )

class UnixStreamXMLRPCServer(UnixStreamServer, SimpleXMLRPCDispatcher):
   def __init__(self, address, requestHandler=SimpleXMLRPCRequestHandler,
      allow_none=False, encoding=None):

      self.logRequests = False

      SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)
      UnixStreamServer.__init__(self, address, requestHandler)

class Daemon:
   def __init__(self, **args):
      address      =           args[ 'address'             ]
      allow_none   =       args.get( 'allow_none', True    )
      encoding     =       args.get( 'encoding'  , 'utf-8' )

      self.verbose =       args.get( 'verbose'   , False   )
      self.stdout  =       args.get( 'stdout'    , nul     )
      self.stderr  =       args.get( 'stderr'    , nul     )

      try:
         ServerProxy(address).ping()
      except SocketError:
         pass
      else:
         raise error, 'Another daemon is already up using socket %s' %repr(address)

      if isinstance(address, str):
         try:
            unlink(address)
         except OSError:
            pass

         self.manager = UnixStreamXMLRPCServer(address,
            allow_none=allow_none, encoding=encoding)

         self.pidfile = address
      else:
         self.manager = SimpleXMLRPCServer(address,
            allow_none=allow_none, encoding=encoding)

self.pid     = None
      self.running = True

      self.program       =     args[ 'program'             ]

      DaemonizeTools.setuid(
         user       = args.get( 'user'      , None    )  )

      SignalTools.setsignals(self)

      DaemonizeTools.daemonize(
         directory  = args.get( 'directory' , None    ),
         umask      = args.get( 'umask'     , 022     )  )

      self.register_function = lambda *args: (
         self.manager.register_function(*args) )

      self.register_function(lambda: self.stop()  , 'stop'  )
      self.register_function(lambda: self.status(), 'status')
      self.register_function(lambda: True         , 'ping'  )

   def __call__(self, *args):
      if not self.verbose:
         DaemonizeTools.close_files(self.stdout, self.stderr)
         del self.stdout, self.stderr

      pid = fork()

      if pid != 0:
         self.pid = pid
         while self.running:
            self.manager.handle_request()

         return pid

      else:
         try:
            for i in xrange(3, 100):
               try:
                  os.close(i)
               except OSError:
                  pass

            try:
               execv(sys.executable, tuple(
                  [sys.executable, self.program] + list(args) ) )
            except OSError, err:
               print >> stderr, ( 'can\'t exec %r: %s\n'
                  % (self.program, err) )

         finally:
            os._exit(127)

   def status(self):
      if not self.pid:
         return 'stopped'
      else:
         return 'running'

   def stop(self):
      if not self.pid:
         self.running = False
         if hasattr(self, 'pidfile'):
            try:
               unlink(self.pidfile)
            except OSError:
               pass

         raise error, 'no subprocess running'

      kill(self.pid, SIGTERM)
      self.running = False
      if hasattr(self, 'pidfile'):
         try:

unlink(self.pidfile)
         except OSError:
            pass

class UnixStreamHTTPConnection(HTTPConnection):
   def connect(self):
      self.sock = socket(AF_UNIX, SOCK_STREAM)
      self.sock.connect(self.host)

class UnixStreamHTTP(HTTP):
   _connection_class = UnixStreamHTTPConnection

class UnixStreamTransport(Transport):
   def make_connection(self, host):
      return UnixStreamHTTP(host)

class UnixStreamServerProxy_NG:
   def __init__(self, uri, transport=None, encoding=None, verbose=0,
      allow_none=0, use_datetime=0):

      self.__host = uri
      self.__handler = '/RPC2'

      if not transport:
         self.__transport = UnixStreamTransport(use_datetime=use_datetime)

      self.__encoding = encoding
      self.__verbose = verbose
      self.__allow_none = allow_none

   def __request(self, methodname, params):
      request = xmlrpc_dumps(params, methodname, encoding=self.__encoding,
         allow_none=self.__allow_none)

      response = self.__transport.request(
         self.__host, self.__handler, request,
         verbose=self.__verbose )

      if len(response) == 1:
         response = response[0]

      return response

   def __getattr__(self, name):
      return _XMLRPCMethod(self.__request, name)

def ServerProxy(address, **args):
   if isinstance(address, str):
      return UnixStreamServerProxy_NG(address, **args)

   else:
      host, port = address
      host = (host, '127.0.0.1')[host == '0.0.0.0']
      return ServerProxy_N___G('http://%s:%d' %(host, port), **args)

class DaemonizeTools:
   @staticmethod
   def setuid(**args):
      user = args['user']
      if user is None:
         return

      try:
         uid = int(user)

      except ValueError:
         try:
            pwrec = pwd.getpwnam(user)
         except KeyError:
            raise error, 'username %r not found' % user

         uid = pwrec[2]

      else:
         try:
            pwrec = pwd.getpwuid(uid)
         except KeyError:
            raise error, 'uid %r not found' % user

      euid = geteuid()
      if euid != 0 and euid != uid:
         raise error, 'only root can change users'

      setgid(pwrec[3])
      setuid(uid)

   @staticmethod
   def daemonize(**args):
      pid = fork()
      if pid != 0:
         os._exit(0)

      if args['directory']:
         try:
            chdir(args['directory'])
         except OSError, err:
            print >> stderr, ( 'can\'t chdir into %r: %s'

% (args['directory'], err) )
         else:
            print >> stderr, ( 'set current directory: %r'
               % args['directory'] )

      os.setsid()
      umask(args['umask'])


   @staticmethod
   def close_files(stdout, stderr):
      os.close(0)
      sys.stdin = sys.__stdin__ = nul
      os.close(1)
      sys.stdout = sys.__stdout__ = stdout
      os.close(2)
      sys.stderr = sys.__stderr__ = stderr

class SignalTools:
   daemon = None

   @staticmethod
   def setsignals(daemon):
      SignalTools.daemon = daemon
      setsignal(SIGCHLD, SignalTools.sigchild)

   @staticmethod
   def sigchild(sig, frame):
      try:
         pid, sts = waitpid(-1, WNOHANG)
         if pid == SignalTools.daemon.pid:
            SignalTools.daemon.pid = None

      except OSError:
         return

某些时候是需要python程序作为一个守护进程daemon来运行的。懒人的做法是:
nohup python ./aaa.py > /dev/null &
比较好的办法是写一个真的可以自己fork的python程序,下面就提供一个daemon的python类:
daemon.py下载
使用方法:
导入from daemon import Daemon
然后派生出新类并重新定义run方法即可:



#!/usr/bin/env python



import sys, time

from daemon import Daemon



class MyDaemon(Daemon):

     def run(self):

             while True:

                     time.sleep(1)



if __name__ == "__main__":

     daemon = MyDaemon('/tmp/daemon-example.pid')

     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]

posted @ 2012-03-03 23:24  zaleilynn  阅读(2226)  评论(0编辑  收藏  举报