杰瑞科技汇

Python daemonizer如何实现守护进程?

Of course! Creating a daemon in Python is a common task for background services. A "daemon" is a long-running background process that typically runs detached from the terminal, surviving the shell that started it.

Python daemonizer如何实现守护进程?-图1
(图片来源网络,侵删)

While you can write the low-level logic for detaching a process yourself, it's highly recommended to use a well-tested library. The most popular and robust library for this is python-daemon.

Here's a complete guide, starting with the best-practice approach using the library, followed by an explanation of the manual method for educational purposes.


Method 1: The Recommended Approach using python-daemon

This library handles all the complexities of creating a proper Unix daemon, such as forking, changing the working directory, setting file descriptors, and managing the process ID (PID) file.

Step 1: Install the Library

pip install python-daemon

Step 2: Create Your Daemon Script

Let's create a simple daemon script named my_daemon.py. This script will:

Python daemonizer如何实现守护进程?-图2
(图片来源网络,侵删)
  1. Log a message every 5 seconds to a log file.
  2. Run in a loop until a specific "stop" file is created.
  3. Be properly managed by the python-daemon library.
# my_daemon.py
import daemon
import daemon.pidfile
import time
import os
import logging
import sys
# --- Configuration ---
LOG_FILE = '/tmp/my_daemon.log'
PID_FILE = '/tmp/my_daemon.pid'
STOP_FILE = '/tmp/stop_my_daemon' # File to signal the daemon to stop
# --- Logging Setup ---
# Configure logging to write to a file.
# The 'a' mode means the log file will be appended to if it already exists.
logging.basicConfig(
    filename=LOG_FILE,
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)
def main_program_logic():
    """
    This is the core logic of your daemon.
    It will be run in a loop.
    """
    logging.info("Daemon started.")
    try:
        while True:
            # Check for a stop signal
            if os.path.exists(STOP_FILE):
                logging.info("Stop signal received. Shutting down.")
                break
            # Your daemon's work goes here
            logging.info("Daemon is running...")
            time.sleep(5)
    except Exception as e:
        logging.error(f"An error occurred: {e}", exc_info=True)
    finally:
        logging.info("Daemon stopped.")
        # Clean up the stop file
        if os.path.exists(STOP_FILE):
            os.remove(STOP_FILE)
if __name__ == '__main__':
    # The 'with' block ensures that the daemon context is properly managed.
    # When the block is exited, the daemon will be detached.
    with daemon.DaemonContext(
        # The pidfile is crucial for managing the daemon's lifecycle.
        # It prevents multiple instances from running.
        pidfile=daemon.pidfile.PIDLockFile(PID_FILE),
        # Optional: Change the working directory to the root.
        # This prevents the daemon from holding onto a directory that might be unmounted.
        working_directory='/',
        # Optional: Redirect standard streams to /dev/null.
        # This is standard practice for daemons to prevent them from
        # accidentally interacting with a terminal.
        stdout=sys.stdout,
        stderr=sys.stderr,
        stdin=sys.stdin
    ):
        main_program_logic()

Step 3: Manage the Daemon

You can now use standard Python scripts to start, stop, and check the status of your daemon.

manage_daemon.py

# manage_daemon.py
import os
import time
import sys
import subprocess
PID_FILE = '/tmp/my_daemon.pid'
LOG_FILE = '/tmp/my_daemon.log'
DAEMON_SCRIPT = 'my_daemon.py'
def start_daemon():
    """Start the daemon in the background."""
    if is_running():
        print("Daemon is already running.")
        return
    # Use '&' to run the script in the background
    # The 'nohup' command ensures the process keeps running after the terminal is closed.
    # We redirect output to the log file.
    command = f"nohup python3 {DAEMON_SCRIPT} > {LOG_FILE} 2>&1 &"
    os.system(command)
    # Give it a moment to start and write the PID file
    time.sleep(1)
    if is_running():
        print(f"Daemon started. PID: {get_pid()}")
    else:
        print("Failed to start daemon.")
def stop_daemon():
    """Stop the daemon."""
    pid = get_pid()
    if not pid:
        print("Daemon is not running.")
        return
    try:
        # Send a SIGTERM signal for a graceful shutdown
        os.kill(pid, 15) # SIGTERM
        print(f"Daemon with PID {pid} has been stopped.")
    except ProcessLookupError:
        # The process was not found, maybe it crashed or already stopped.
        # Clean up the stale PID file.
        if os.path.exists(PID_FILE):
            os.remove(PID_FILE)
        print("Daemon was not running. Stale PID file removed.")
def status_daemon():
    """Check the status of the daemon."""
    if is_running():
        print(f"Daemon is running. PID: {get_pid()}")
    else:
        print("Daemon is not running.")
def is_running():
    """Check if the daemon process is currently running."""
    pid = get_pid()
    if not pid:
        return False
    # Check if the process with the given PID still exists
    try:
        os.kill(pid, 0) # Sending signal 0 doesn't kill the process, just checks existence
        return True
    except OSError:
        # Process does not exist
        if os.path.exists(PID_FILE):
            os.remove(PID_FILE) # Clean up stale PID file
        return False
def get_pid():
    """Read the PID from the PID file."""
    try:
        with open(PID_FILE, 'r') as f:
            return int(f.read().strip())
    except (IOError, ValueError):
        return None
if __name__ == '__main__':
    if len(sys.argv) != 2:
        print("Usage: python manage_daemon.py <start|stop|status>")
        sys.exit(1)
    command = sys.argv[1]
    if command == 'start':
        start_daemon()
    elif command == 'stop':
        stop_daemon()
    elif command == 'status':
        status_daemon()
    else:
        print("Invalid command. Use 'start', 'stop', or 'status'.")

How to Use It

  1. Start the Daemon:

    python3 manage_daemon.py start

    Output: Daemon started. PID: 12345

    Python daemonizer如何实现守护进程?-图3
    (图片来源网络,侵删)
  2. Check its Status:

    python3 manage_daemon.py status

    Output: Daemon is running. PID: 12345

  3. Check the Log File:

    tail -f /tmp/my_daemon.log

    You should see log messages appearing every 5 seconds.

  4. Stop the Daemon:

    python3 manage_daemon.py stop

    Output: Daemon with PID 12345 has been stopped. The log file will show the "Shutting down" message.


Method 2: The Manual Approach (For Educational Purposes)

Understanding how a daemon works is valuable. The python-daemon library essentially automates these steps. Here is the logic of what a manual daemonization process looks like.

A proper daemonization involves several steps:

  1. Fork the Parent Process: The parent process forks a child and then exits. This detaches the daemon from the controlling terminal.
  2. Become a Session Leader: The child process creates a new session and becomes its leader. This decouples it from the old terminal.
  3. Change Working Directory: Change the current working directory to to prevent the daemon from holding a mount point.
  4. Reset File Descriptors: Close standard file descriptors (stdin, stdout, stderr) and redirect them to /dev/null to prevent any unintended I/O.
  5. Create a PID File: Write the process ID to a file so the daemon can be managed.

Here is a simplified version of my_daemon.py that does this manually.

# my_daemon_manual.py
import os
import time
import sys
import logging
PID_FILE = '/tmp/my_daemon_manual.pid'
LOG_FILE = '/tmp/my_daemon_manual.log'
def daemonize():
    """
    The manual daemonization process.
    """
    # 1. Fork the first time to become a daemon parent.
    try:
        if os.fork() > 0:
            sys.exit(0) # Parent exits
    except OSError as e:
        sys.stderr.write(f"Fork #1 failed: {e}\n")
        sys.exit(1)
    # 2. Decouple from parent environment.
    os.chdir('/')
    os.setsid()
    os.umask(0)
    # 3. Fork a second time to ensure the daemon cannot re-acquire a terminal.
    try:
        if os.fork() > 0:
            sys.exit(0) # Second parent exits
    except OSError as e:
        sys.stderr.write(f"Fork #2 failed: {e}\n")
        sys.exit(1)
    # 4. Redirect standard file descriptors.
    sys.stdout.flush()
    sys.stderr.flush()
    si = open(os.devnull, 'r')
    so = open(os.devnull, 'a+')
    se = open(os.devnull, 'a+')
    os.dup2(si.fileno(), sys.stdin.fileno())
    os.dup2(so.fileno(), sys.stdout.fileno())
    os.dup2(se.fileno(), sys.stderr.fileno())
    # 5. Write the PID file.
    pid = str(os.getpid())
    with open(PID_FILE, 'w+') as f:
        f.write(pid + '\n')
def main():
    # Setup logging after daemonizing
    logging.basicConfig(
        filename=LOG_FILE,
        level=logging.INFO,
        format='%(asctime)s - %(levelname)s - %(message)s'
    )
    logging.info("Manual daemon started.")
    try:
        while True:
            logging.info("Daemon is running...")
            time.sleep(5)
    except KeyboardInterrupt:
        logging.info("Manual daemon stopped by user.")
    except Exception as e:
        logging.error(f"An error occurred: {e}", exc_info=True)
    finally:
        if os.path.exists(PID_FILE):
            os.remove(PID_FILE)
        logging.info("Manual daemon stopped.")
if __name__ == '__main__':
    # The main script first calls the daemonize function
    daemonize()
    # After daemonize() returns, we are in the daemon process
    main()

Why you should avoid this: The manual method is error-prone. You have to handle signals correctly, manage the PID file yourself, and ensure your code is robust. The python-daemon library has solved all these problems and is the industry standard for a reason.

Summary: python-daemon vs. Manual

Feature python-daemon (Recommended) Manual Method
Ease of Use High. Simple with block. Low. Requires deep OS knowledge.
Robustness High. Battle-tested and handles edge cases. Low. Prone to subtle errors.
Code Clarity High. Daemon logic is separate from setup logic. Low. Daemonization logic is mixed with main logic.
PID File Managed automatically. Must be managed manually.
Signal Handling Better handled by the library context. Must be implemented manually.
When to Use For almost all production daemons. For learning purposes or in environments where you cannot add dependencies.
分享:
扫描分享到社交APP
上一篇
下一篇