Pythonデーモンの詳細

13161 ワード

   ,   

          ,                ,
               。

     ,             ,        ……

            "    "、"    ",    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
'                   % (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