python 一个程序中的多个守护进程不起作用

python multi daemon in one program doesn't work

我尝试构建一个 python 程序,同时 运行 两个或多个守护进程,守护进程都监听不同的端口并根据它们的代码做事。但是当我完成 运行 时,我发现只有一个守护进程可以工作,但第二个。我怎样才能做到这一点?以下是我的测试代码:

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

import sys
import time
import socket
import logging
import atexit
import os
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:
                # exit first parent
                sys.exit(0)
        except OSError, e:
            sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror))
            sys.exit(1)

        # decouple from parent environment
        os.chdir("/")
        os.setsid()
        os.umask(0)

        # do second fork
        try:
            pid = os.fork()
            if pid > 0:
                # exit from second parent
                sys.exit(0)
        except OSError, e:
            sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror))
            sys.exit(1)

        # redirect standard file descriptors
        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())

        # write pidfile
        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 controlDaemon(Daemon):
    global tcpListenPort
    global bufferSize

    def run(self):
        logger.debug("start listen:"+str(tcpListenPort))
        address = ('127.0.0.1', tcpListenPort)
        udpListenSocket2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        udpListenSocket2.bind(address)
        while True:
            data = udpListenSocket2.recv(bufferSize)
            logger.debug("received:{0}".format(data))
        udpListenSocket2.close()


class notificationPushDaemon(Daemon):
    global udpListenPort
    global bufferSize

    def run(self):
        logger.debug("start listen:"+str(udpListenPort))
        address = ('127.0.0.1', udpListenPort)
        udpListenSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        udpListenSocket.bind(address)
        while True:
            data = udpListenSocket.recv(bufferSize)
            logger.debug("received:{0}".format(data))
        udpListenSocket.close()


def InitLog():
    logger.setLevel(logging.DEBUG)

    fh = logging.FileHandler("tt.log")
    fh.setLevel(logging.DEBUG)
    ch = logging.StreamHandler()
    ch.setLevel(logging.ERROR)

    formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
    ch.setFormatter(formatter)
    fh.setFormatter(formatter)

    logger.addHandler(fh)
    logger.addHandler(ch)


if __name__ == "__main__":
    logger = logging.getLogger("logtest")
    InitLog()
    tcpListenPort = 19999
    udpListenPort = 19966
    tcpPidFile = '/tmp/test1.pid'
    udpPidFile = '/tmp/test2.pid'
    cDaemon = controlDaemon(tcpPidFile)
    npDaemon = notificationPushDaemon(udpPidFile)
    bufferSize = 65535

    if len(sys.argv) == 2:
        if 'start' == sys.argv[1]:
            cDaemon.start()
            npDaemon.start()
        elif 'stop' == sys.argv[1]:
            cDaemon.stop()
            npDaemon.stop()
        elif 'restart' == sys.argv[1]:
            cDaemon.restart()
            npDaemon.restart()
        else:
            print "Unknown command"
            sys.exit(2)
            sys.exit(0)
    else:
        print "usage: %s start|stop|restart" % sys.argv[0]
        sys.exit(2)

您可以使用标准python的多处理模块。

import multiprocessing as mp

class Daemon(mp.Process):
    def __init__(self):
        mp.Process.__init__(self)

    def run(self):
        here everything that should be a demon goes


main():
daemon = Daemon()
daemon.daemon = True  # this lets the daemon be presistent in the memory, even when the program quits
daemon.start()

一个 Unix 守护进程是一个进程,因此试图让一个进程成为两个守护进程是行不通的。要么编写两个单独的守护进程和另一个程序或脚本到 start/stop/restart 两者,要么只编写一个守护进程并让它与 threadingmultiprocessing 并行执行两项工作。这是重写为使用线程的示例:

#!/usr/bin/env python
# coding: utf8
import atexit
import logging
import os
import socket
import sys
import time
from contextlib import closing
from signal import SIGTERM
from threading import Thread

LOGGER = logging.getLogger('logtest')
PID_FILENAME = '/tmp/test.pid'
TCP_LISTEN_PORT = 19999
UDP_LISTEN_PORT = 19966
BUFFER_SIZE = 65535


class Daemon(object):
    pass
    # ... Class definition from question goes here ...


def control():
    LOGGER.debug('start listen: %s', TCP_LISTEN_PORT)
    address = ('127.0.0.1', TCP_LISTEN_PORT)
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    server_socket.bind(address)
    while True:
        sock, address = server_socket.accept()
        with closing(sock):
            while True:
                data = sock.recv(BUFFER_SIZE)
                if not data:
                    break
                LOGGER.debug('received from %s: %s', address, data)


def push_notifications():
    LOGGER.debug('start listen: %s', UDP_LISTEN_PORT)
    address = ('127.0.0.1', UDP_LISTEN_PORT)
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.bind(address)
    while True:
        data, address = sock.recvfrom(BUFFER_SIZE)
        LOGGER.debug('received from %s: %s', address, data)
    sock.close()


class TheDaemon(Daemon):

    @staticmethod
    def run():
        thread = Thread(target=control)
        thread.start()
        push_notifications()


def init_logging():
    LOGGER.setLevel(logging.DEBUG)

    file_handler = logging.FileHandler('tt.log')
    file_handler.setLevel(logging.DEBUG)
    stream_handler = logging.StreamHandler()
    stream_handler.setLevel(logging.ERROR)

    formatter = logging.Formatter(
        '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
    )
    stream_handler.setFormatter(formatter)
    file_handler.setFormatter(formatter)

    LOGGER.addHandler(file_handler)
    LOGGER.addHandler(stream_handler)


def main():
    init_logging()
    commands = ['start', 'stop', 'restart']
    daemon = TheDaemon(PID_FILENAME)

    if len(sys.argv) == 2:
        command = sys.argv[1]
        if command in commands:
            getattr(daemon, command)()
        else:
            print 'Unknown command'
            sys.exit(2)
    else:
        print 'usage: {0} {1}'.format(sys.argv[0], '|'.join(commands))
        sys.exit(2)


if __name__ == '__main__':
    main()

TCP 和 UDP 处理代码有一些错误。如果您希望同时有多个 TCP 连接,您可能希望在它们自己的线程中处理单个连接。

global 语句没有任何效果。