daemon.py:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import signal
__author__ = 'lnp'
import atexit
import sys
import socket
import os
#守护进程包装类
class Daemon:
def __init__(self,pid_file,stdout=os.devnull,stderr=os.devnull):
self.pid_file = pid_file
self.stderr = stderr
self.stdout =stdout
'''
编写守护进程的一般步骤步骤:
(1)创建自己成并被init进程接管:在父进程中执行fork并exit退出;
(2)创建新进程组和新会话:在子进程中调用setsid函数创建新的会话;
(3)修改子进程的工作目录:在子进程中调用chdir函数,让根目录 ”/” 成为子进程的工作目录;
(4)修改子进程umask:在子进程中调用umask函数,设置进程的umask为0;
(5)在子进程中关闭任何不需要的文件描述符
在子进程中再次fork一个进程,这个进程称为孙子进程,之后子进程退出
重定向孙子进程的标准输入流、标准输出流、标准错误流到/dev/null
那么最终的孙子进程就称为守护进程。
'''
def __daemonize(self):
# 从父进程fork一个子进程出来
pid = os.fork()
# 子进程的pid一定为0,父进程大于0
if pid:
#退出父进程
sys.exit(0)
# 子进程默认继承父进程的工作目录,最好是变更到根目录,否则回影响文件系统的卸载
os.chdir('/')
# 子进程默认继承父进程的umask(文件权限掩码),重设为0(完全控制),以免影响程序读写文件
os.umask(0)
# 让子进程成为新的会话组长和进程组长
os.setsid()
# 第2次fork,也就是子进程的子进程,我们把它叫为孙子进程
_pid = os.fork()
if _pid:
# 退出子进程
sys.exit(0)
# 此时,孙子进程已经是守护进程了,接下来重定向标准输入、输出、错误的描述符(是重定向而不是关闭, 这样可以避免程序在 print 的时候出错)
# 刷新缓冲区先,小心使得万年船
sys.stdout.flush()
sys.stderr.flush()
si = open(os.devnull, 'r')
so = open(self.stdout, 'a+')
if self.stderr:
se = open(self.stderr,'a+')
else:
se = so
# dup2函数原子化地关闭和复制文件描述符,重定向到/dev/nul,即丢弃所有输入输出
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文件
if self.pid_file:
with open(self.pid_file,'w+') as f:
f.write(str(os.getpid()))
# 程序退出后移除PID文件
def delpid(self):
os.remove(self.pid_file)
#获取pid
def get_pid(self):
if os.path.exists(self.pid_file):
with open(self.pid_file,'r') as f:
pid = int(f.read().strip())
return pid
else:
return None
# 核心业务逻辑
def __run(self):
#执行你的业务逻辑。。。。
# 启动程序
def start(self):
print("Starting...")
self.__daemonize()
self.__run()
#停止程序
def stop(self):
print("Stopping...")
pid = self.get_pid()
try:
# 终止守护进程
while True:
if pid:
os.kill(pid, signal.SIGKILL)
except OSError as err:
if str(err).find('No such process') >0:
if os.path.exists(self.pid_file):
os.remove(self.pid_file)
else:
print(str(err))
sys.exit(1) #异常退出
if __name__ == '__main__':
pid_file = "/tmp/daemon.pid" # 守护进程pid文件的绝对路径
out_fn = '/tmp/daemon.out.log' # 守护进程标准输出日志,
err_fn = '/tmp/daemon.err.log' # 守护进程启动过程中的错误日志,内部出错能从这里看到
daemon = Daemon(pid_file,out_fn,err_fn)
if sys.argv[1] == 'start':
daemon.start()
elif sys.argv[1] == 'stop':
daemon.stop()
else:
print('invalid argument!')
使用 python deamon.py start 命令就可以守护进程的方式运行程序啦!
当然,这里只支持了start和stop命令,需要的也可扩充status等命令