python:守护进程deamon

简介:

一、守护进程的基本编码规范

    详细参见:《AdvancedProgrammingin The Unix Environment》Section 13.3 Page 583

     本小节将介绍一些守护进程的基本编码规范,这些规范将阻止守护进程与当前环境产生一些不必要的交互。本节将通过一个函数daemonize实现这些规范。
    1. 首先要做的被称为 umask,这一步骤会将文件创建掩码重置为0。这一步的原因是守护进程继承(inherited)得到的文件掩码有可能会拒绝某些特定的文件操作权限。如果守护进程想要创建文件,那有可能它需要设置特定的文件操作权限。例如,如果守护进程想要创建允许组读和写(group-readand group-write)权限的文件,但继承得到的文件创建掩码屏蔽了这个权限,则创建操作不会成功。

    2.  调用 fork 并使父进程退出(exit)。这一步骤的目的在于。首先,如果守护进程是通过一个简单的shell命令建立的,那么在父进程终止的时候shell会认为命令已经结束了继而结束守护进程。其次,子进程继承得到父进程的groupID同时也获得了一个新的进程号,所以我们必须得保证子进程不能担任groupleader的角色,这是下一步setsid 操作的前提。
    注:此步骤是使得进程在后台运行。

    3.    调用 setsid 创建一个新的会话。有三个步骤将会执行:(a)进程将成为这个新会话的sessionleader角色,(b)此进程将会成为一个新的进程组的groupleader,(c)此进程将不会有控制终端。
    注:此步骤将使得进程脱离控制终端、登录会话以及进程组。

    在基于SystemV的系统中,有建议再一次调用fork 并使父进程退出。而新产生的进程将会成为真正的守护进程。这一步骤将保证守护进程不是一个sessionleader,进而阻止它获取一个控制终端。或者另一种阻止守护进程获取控制终端的方案是任意时刻打开一个终端设备的时候确保指定O_NOCTTY。
    注:此步骤将禁止进程重新打开控制终端。

    4.  将当前的工作目录切换到系统根目录下。因为从父进程集成来的当前工作目录可能是一个被挂载的文件系统。因为守护进程通常是直到系统重启的时候才会退出,如果守护进程的工作目录在一个挂载的文件系统上,那么这个文件系统就不能被卸载(unmounted)。

    有的守护进程可能会将当前的工作目录切换到一些特定的路径,在这些路径下它们将完成它们的工作。例如,lineprinter spoolingdaemons 通常将工作目录切换为spool目录。

    5.  一些不必要的文件描述符将会被关闭。这个步骤将阻止守护进程保持从父进程集成到的任何已经打开的文件描述符(也可能是shell或其他进程)。我们可以使用 open_max 函数或 getrlimit 函数来查找当前优先级最高的文件描述符并关闭此描述符之下的所有其他描述符。
    注:保持打开的描述符将会占用系统资源并可能使某些文件不能被卸载。

    6.  有一些守护进程将打开文件描述符0, 1, 2 指向 /dev/null ,这样一来所有试图从标准输入、输出及错误读取守护进程信息的操作都不能成功。因为守护进程当前已经不与任何终端设备相关联,没有地方显示其输出或接受用户的输入。即使守护进程是从一个交互式session创建的,守护进程也将运行在后台,任何终端的登录与终止将不会影响守护进程。如果有其他用户通过当前的终端登录,我们也不希望守护进程的输出出现在终端上,并且该用户的任何输入也不会被守护进程接收。

    总结起来就是:
    1) 第一次fork将会创建父-子进程,同时使得父进程退出保证守护进程能够运行在后台。
    2) 通过setsid步骤使得进程与控制终端、登录会话以及进程组脱离。
    3) 第二次fork将确保进程重新打开控制终端,并且产生子-孙进程,而子进程退出后孙进程将成为真正的守护进程。
    4) 其他还有一些诸如工作目录设置、关闭文件描述符、设置文件创建掩码之类的操作。

二、python守护进程的一个例子

    详见:https://gist.github.com/jamiesun/3097215,样例代码如下

#! /usr/bin/env python2.7
#encoding:utf-8
#@description:一个python守护进程的例子
#@tags:python,daemon
import sys
import os
import time
import 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:
                # 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 MyDaemon(Daemon):
    def run(self):
        while True:
            time.sleep(60)
            print 'daemon runing'
 
 
 
 
if __name__ == "__main__":
    daemon = MyDaemon("/var/run/demodaemon.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)

三、编写的针对primeton ESB SERVER程序的守护进程样例

    监控程序的进程是否存在,程序的端口是否正常

#!/usr/bin/python
# encoding:utf-8
# @description:一个python守护进程
# @tags:python,daemon
import atexit
import os
from signal import SIGTERM 
import socket
import sys
import time


class ESBDaemon:
    # 构造函数
    def __init__(self, pidfile, ip, port, findCmd, runCmd, stopCmd, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
        # 需要获取调试信息,改为stdin='/dev/stdin', stdout='/dev/stdout', stderr='/dev/stderr',以root身份运行
        self.stdin = stdin
        self.stdout = stdout
        self.stderr = stderr
        self.pidfile = pidfile
        self.ip = ip
        self.port = port
        self.findCmd = findCmd
        self.runCmd = runCmd
        self.stopCmd = stopCmd
       

    def daemonize(self):
        # 第一次fork将会创建父-子进程,同时使得父进程退出保证守护进程能够运行在后台
        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)
    
        # 与父进程的环境解耦
        # 通过setsid步骤使得进程与控制终端、登录会话以及进程组脱离
        os.chdir("/") 
        os.setsid() 
        os.umask(0) 
    
        # 第二次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) 
    
        # 其他还有一些诸如工作目录设置、关闭文件描述符、设置文件创建掩码之类的操作
        # 重定向文件描述符
        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())
    
        # 创建processid文件
        atexit.register(self.delpid)
        pid = str(os.getpid())
        file(self.pidfile, 'w+').write("%s\n" % pid)

  
    def delpid(self):
        os.remove(self.pidfile)
        
    # 可用于检测程序是否正常,如检测redis是否正常,即检测redis的6379端口是否正常
    def check_aliveness(self):
        sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sk.settimeout(1)
        try:
            sk.connect((self.ip, self.port))
            return True
        except Exception:
            return False
        finally:
            sk.close()
            return False
        
    def writeFile(self, msg):
        f = file("/var/run/esb.log", 'a+')
        f.write(msg + time.strftime('%Y-%m-%d %H:%M:%S') + "\r\n")  # write text to file
        f.close()

    def start(self):
        # 检查pid文件是否存在以探测是否存在进程
        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)
            self.writeFile(message % self.pidfile)
            sys.exit(1)
        
        # 启动监控
        self.writeFile("启动监控")
        self.daemonize()
        self.run()

    def stop(self):
        self.writeFile("停止程序")
        # 从pid文件中获取pid
        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)
            self.writeFile(message % self.pidfile)
            return  # 重启不报错
 
        # 杀死进程
        try:
            while 1:
                os.kill(pid, SIGTERM)
                time.sleep(0.1)
                os.system(self.stopCmd)
        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)
                self.writeFile(str(err))
                sys.exit(1)
    def restart(self):
        self.writeFile("重启程序")
        self.stop()
        self.start()
 
    def run(self):   
        while True:
            try:
                self.writeFile("检查程序是否正常")
                esb = os.popen(self.findCmd).read().strip()
                portNormal=self.check_aliveness()
                if esb == '0' and (not portNormal):
                    self.writeFile("程序进程不存在 :" + str(esb) + " " + str(portNormal))
                    os.system(self.runCmd)
                time.sleep(30)
            except OSError, err:
               self.writeFile(str(err)) 
    
if __name__ == "__main__":
    daemon = ESBDaemon(
		"/var/run/dfsdaemon.pid",
		"172.168.27.153",
		6200,
		'ps -fe | grep "primeton" | grep "server" | grep -v "grep" | wc -l',
		'nohup /home/primeton/esb/startServer.sh > /home/primeton/esb/server.log 2>&1 &',
		'/home/primeton/esb/stopServer.sh'
		)
    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)

四、对应的shell版本

    监控程序的进程是否存在,程序的端口是否正常

#/bin/sh
host="172.168.27.153"
port=6200
appName="server"

while true;
do
    count=`ps -fe | grep "primeton" | grep "$appName" | grep -v "grep" | wc -l`
    nport=`echo ""|telnet $host $port 2>/dev/null|grep "\^]"|wc -l`
    
    echo "程序进程数:" $count "端口是否通:" $nport
    
    if [ "$count" != "1" -a $nport -eq 0 ]; then
        echo "重新启动程序"
        rm -rf /home/primeton/esb/server/EOS/_srv/work/mq_data/localhost/kr-store/data
        nohup /home/primeton/esb/startServer.sh > /home/primeton/esb/server.log 2>&1 &
    fi
    sleep 2
done
目录
相关文章
|
2月前
|
并行计算 数据处理 调度
Python中的并发编程:探索多线程与多进程的奥秘####
本文深入探讨了Python中并发编程的两种主要方式——多线程与多进程,通过对比分析它们的工作原理、适用场景及性能差异,揭示了在不同应用需求下如何合理选择并发模型。文章首先简述了并发编程的基本概念,随后详细阐述了Python中多线程与多进程的实现机制,包括GIL(全局解释器锁)对多线程的影响以及多进程的独立内存空间特性。最后,通过实例演示了如何在Python项目中有效利用多线程和多进程提升程序性能。 ####
|
2月前
|
调度 iOS开发 MacOS
python多进程一文够了!!!
本文介绍了高效编程中的多任务原理及其在Python中的实现。主要内容包括多任务的概念、单核和多核CPU的多任务实现、并发与并行的区别、多任务的实现方式(多进程、多线程、协程等)。详细讲解了进程的概念、使用方法、全局变量在多个子进程中的共享问题、启动大量子进程的方法、进程间通信(队列、字典、列表共享)、生产者消费者模型的实现,以及一个实际案例——抓取斗图网站的图片。通过这些内容,读者可以深入理解多任务编程的原理和实践技巧。
114 1
|
3月前
|
Python
Python中的多线程与多进程
本文将探讨Python中多线程和多进程的基本概念、使用场景以及实现方式。通过对比分析,我们将了解何时使用多线程或多进程更为合适,并提供一些实用的代码示例来帮助读者更好地理解这两种并发编程技术。
|
2月前
|
监控 JavaScript 前端开发
python中的线程和进程(一文带你了解)
欢迎来到瑞雨溪的博客,这里是一位热爱JavaScript和Vue的大一学生分享技术心得的地方。如果你从我的文章中有所收获,欢迎关注我,我将持续更新更多优质内容,你的支持是我前进的动力!🎉🎉🎉
30 0
|
3月前
|
数据挖掘 程序员 调度
探索Python的并发编程:线程与进程的实战应用
【10月更文挑战第4天】 本文深入探讨了Python中实现并发编程的两种主要方式——线程和进程,通过对比分析它们的特点、适用场景以及在实际编程中的应用,为读者提供清晰的指导。同时,文章还介绍了一些高级并发模型如协程,并给出了性能优化的建议。
44 3
|
4月前
|
负载均衡 Java 调度
探索Python的并发编程:线程与进程的比较与应用
本文旨在深入探讨Python中的并发编程,重点比较线程与进程的异同、适用场景及实现方法。通过分析GIL对线程并发的影响,以及进程间通信的成本,我们将揭示何时选择线程或进程更为合理。同时,文章将提供实用的代码示例,帮助读者更好地理解并运用这些概念,以提升多任务处理的效率和性能。
71 3
|
3月前
|
存储 Python
Python中的多进程通信实践指南
Python中的多进程通信实践指南
36 0
|
4月前
|
消息中间件 安全 Kafka
Python IPC机制全攻略:让进程间通信变得像呼吸一样自然
【9月更文挑战第12天】在编程领域,进程间通信(IPC)是连接独立执行单元的关键技术。Python凭借简洁的语法和丰富的库支持,提供了多种IPC方案。本文将对比探讨Python的IPC机制,包括管道与消息队列、套接字与共享内存。管道适用于简单场景,而消息队列更灵活,适合高并发环境。套接字广泛用于网络通信,共享内存则在本地高效传输数据。通过示例代码展示`multiprocessing.Queue`的使用,帮助读者理解IPC的实际应用。希望本文能让你更熟练地选择和运用IPC机制。
75 10
|
4月前
|
安全 开发者 Python
Python IPC大揭秘:解锁进程间通信新姿势,让你的应用无界连接
【9月更文挑战第11天】在编程世界中,进程间通信(IPC)如同一座无形的桥梁,连接不同进程的信息孤岛,使应用无界而广阔。Python凭借其丰富的IPC机制,让开发者轻松实现进程间的无缝交流。本文将揭开Python IPC的神秘面纱,介绍几种关键的IPC技术:管道提供简单的单向数据传输,适合父子进程间通信;队列则是线程和进程安全的数据共享结构,支持多进程访问;共享内存允许快速读写大量数据,需配合锁机制确保一致性;套接字则能实现跨网络的通信,构建分布式系统。掌握这些技术,你的应用将不再受限于单个进程,实现更强大的功能。
74 6
|
4月前
|
监控 Ubuntu API
Python脚本监控Ubuntu系统进程内存的实现方式
通过这种方法,我们可以很容易地监控Ubuntu系统中进程的内存使用情况,对于性能分析和资源管理具有很大的帮助。这只是 `psutil`库功能的冰山一角,`psutil`还能够提供更多关于系统和进程的详细信息,强烈推荐进一步探索这个强大的库。
59 1