"""
Created on Wed Aug 30 09:44:40 2017
守护进程的特性
1.在后台运行
2.与其运行前的环境隔离开来。这些环境包括未关闭的文件描述符、控制终端、会话和进程组、工作目录以及文件创建掩码等。这些环境通常是守护进程从执行它的父进程(特别是shell)中继承下来的。
3.启动方式特殊,它可以在系统启动时从启动脚本/etc/rc.d中启动,可以由inetd守护进程启动,可以由crond启动,还可以由用户终端(通常是shell)执行。
总之,除开这些特殊性以外,守护进程与普通进程基本上没有什么区别。因此,编写守护进程实际上是把一个普通进程按照上述的守护进程的特性改造成为守护进程。
守护进程编程规则
1.在后台运行,调用fork ,然后使父进程exit
2.脱离控制终端,登录会话和进程组,调用setsid()使进程成为会话组长
3.禁止进程重新打开控制终端
4.关闭打开的文件描述符,调用fclose()
5.将当前工作目录更改为根目录。
6.重设文件创建掩码为0
7.处理SIGCHLD 信号
@author: webzhang
"""
#!/usr/bin/env python
#encoding: utf-8
#description: 一个守护进程的简单包装类, 具备常用的start|stop|restart|status功能, 使用方便
# 需要改造为守护进程的程序只需要重写基类的run函数就可以了
#date: 2015-10-29
#usage: 启动: python3 mydaemon.py start
# 关闭: python3 mydaemon.py stop
# 状态: python3 mydaemon.py status
# 重启: python3 mydaemon.py restart
# 查看: ps -axj | grep mydaemon
以下为程序过程
import smtplib,re
from email.mime.text import MIMEText
from email.utils import formataddr
import requests,time#引入 正则 时间 和 网络控件
from bs4 import BeautifulSoup
import atexit, os, sys, time, signal
import os,re,urllib,uuid,json,datetime
import pandas as pd
#from PIL import Image, ImageDraw, ImageFont #pip install pillow
class CDaemon:
'''''
a generic daemon class.
usage: subclass the CDaemon class and override the run() method
stderr 表示错误日志文件绝对路径, 收集启动过程中的错误日志
verbose 表示将启动运行过程中的异常错误信息打印到终端,便于调试,建议非调试模式下关闭, 默认为1, 表示开启
save_path 表示守护进程pid文件的绝对路径 umask=0 u=rwx,g=rwx,o=rwx ;umask=0o022 # u=rwx,g=rx,o=rx
0o1,0o20,0o377 #八进制 (1,16,255) >>>0x01,0x10,0xFF
#十六进制 (1,16,255)>>>0b1,0b10000,0b11111111 #二进制
2umask的含义
一 权限掩码umask
umask是chmod配套的,总共为4位(gid/uid,属主,组权,其它用户的权限),不过通常用到的是后3个,
例如你用chmod 755 file(此时这文件的权限是属主读(4)+写(2)+执行(1),同组的和其它用户有读写权限)
二 umask的作用 默认情况下的umask值是022(可以用umask命令查看),此时你建立的文件默认权限是644(6-0,6-2,6-2),
建立的目录的默认 权限是755(7-0,7-2,7-2),可以用ls -l验证一下哦 现在应该知道umask的用途了吧,它是为了控制默认权限,不要使默认的文件和目录具有全权而设的
三 修改umask值 知道了umask的作用后,你可以修改umask的值了,例如:umask 024则以后建立的文件和目录的默认权限就为642,753了
总结: 因为老师例子是在往文件里面写内容,所以需要获取文件的可执行权限。
os.umask(0) #修改文件模式,让进程有最大权限,保证进程有读写执行权限,这个不是一个好的方法。
os.setsid() #该方法做一系列的事:首先它使得该进程成为一个新会话的领导者,接下来它将进程转变一个新进程组的领导者
#最后该进程不再控制终端, 运行的时候,建立一个进程,linux会分配个进程号。然后调用os.fork()创建子进程。若pid>0就是自己,自杀。子进程跳过if语句,通过os.setsid()成为linux中的独立于终端的进程(不响应sigint,sighup等)。
'''
def __init__(self, save_path, stdin=os.devnull, stdout=os.devnull, stderr=os.devnull, home_dir='.', umask=0o022, verbose=1):
self.stdin = stdin #标准输入 例如键盘输入
self.stdout = stdout #标准输出 命令窗口输出
self.stderr = stderr #标准出错
self.pidfile = save_path #pid文件绝对路径
self.home_dir = home_dir #主目录 根目录
self.verbose = verbose #调试开关
self.umask = umask #文件权限设置掩码
self.daemon_alive = True #进程是否在运行
def daemonize(self): #进程守护化 2次fork
try:
pid = os.fork() #fork
if pid > 0: #正常fork后 为0 否则fork没有成功
sys.exit(0) #无错误 退出,exit(1):有错误退出
except OSError as e:
sys.stderr.write('fork #1 failed: %d (%s)\n' % (e.errno, e.strerror))
sys.exit(1) #有错误退出
os.chdir(self.home_dir) #换到根目录
os.setsid() #获得独立进程号
os.umask(self.umask)#设置文件权限掩码
try:
pid = os.fork() #再次 fork
if pid > 0:
sys.exit(0)
except OSError as e:
sys.stderr.write('fork #2 failed: %d (%s)\n' % (e.errno, e.strerror))
sys.exit(1)
sys.stdout.flush() #清除标准输入
sys.stderr.flush() #清除错误信息
si = open(self.stdin, 'r') #打开标准输入
so = open(self.stdout, 'a+') #打开标准输出
if self.stderr: #如果有自定义的出错输出
# se = file(self.stderr, 'a+', 0)
se = open(self.stderr, 'a+') #打开出错输出
else:
se = so
os.dup2(si.fileno(), sys.stdin.fileno()) #os.dup2() 方法用于将一个文件描述符 fd 复制到另一个 fd2。
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())
def sig_handler(signum, frame):
self.daemon_alive = False
signal.signal(signal.SIGTERM, sig_handler)
signal.signal(signal.SIGINT, sig_handler)
if self.verbose >= 1:
print ('daemon process started ...') #进程开始
atexit.register(self.del_pid) #先删除的原有pid文件
pid = str(os.getpid())
open(self.pidfile, 'w+').write('%s\n' % pid) #把pid写到文件里面
def get_pid(self): #取得PID号
try:
pf = open(self.pidfile, 'r')
pid = int(pf.read().strip())
pf.close()
except IOError:
pid = None
except SystemExit:
pid = None
return pid
def del_pid(self): #删除pid文件
if os.path.exists(self.pidfile):
os.remove(self.pidfile)
def start(self, *args, **kwargs):#启动开始
if self.verbose >= 1:
print( 'ready to starting ......')
#check for a pid file to see if the daemon already runs
pid = self.get_pid() #取得pid号
if pid:
msg = 'pid file %s already exists, is it already running?\n' #pid文件已经存在,已经运行
sys.stderr.write(msg % self.pidfile) #写出错
sys.exit(1)
#start the daemon
self.daemonize()
self.run(*args, **kwargs)
def stop(self): #停止
if self.verbose >= 1:
print( 'stopping ...')
pid = self.get_pid()
if not pid:
msg = 'pid file [%s] does not exist. Not running?\n' % self.pidfile
sys.stderr.write(msg)
if os.path.exists(self.pidfile):
os.remove(self.pidfile)
return
#try to kill the daemon process
try:
i = 0
while 1:
os.kill(pid, signal.SIGTERM)
time.sleep(0.1)
i = i + 1
if i % 10 == 0:
os.kill(pid, signal.SIGHUP)
except OSError as 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)
if self.verbose >= 1:
print ('Stopped!')
def restart(self, *args, **kwargs):
self.stop()
self.start(*args, **kwargs)
def is_running(self):
pid = self.get_pid()
#print(pid)
return pid and os.path.exists('/proc/%d' % pid)
def run(self, *args, **kwargs):
'NOTE: override the method in subclass'
print ('base class run()')
在这加入我们需要运行的程序并定义成一个函数。比如def get()
class ClientDaemon(CDaemon): #守护程序 实例
def __init__(self, name, save_path, stdin=os.devnull, stdout=os.devnull, stderr=os.devnull, home_dir='.', umask=0o022, verbose=1):
CDaemon.__init__(self, save_path, stdin, stdout, stderr, home_dir, umask, verbose)
self.name = name #派生守护进程类的名称
def run(self, output_fn, **kwargs): #运行的程序
在这运行我们加入的函数 get()
if __name__ == '__main__':
help_msg = 'Usage: python %s
' % sys.argv[0]
if len(sys.argv) != 2:
print( help_msg)
sys.exit(1)
p_name = 'clientd' #守护进程名称
pid_fn = '/tmp/baoxian.pid' #守护进程pid文件的绝对路径
log_fn = '/tmp/baoxian.log' #守护进程日志文件的绝对路径
err_fn = '/tmp/baoxian.err.log' #守护进程启动过程中的错误日志,内部出错能从这里看到
cD = ClientDaemon(p_name, pid_fn, stderr=err_fn, verbose=1) #守护程序 实例开始
if sys.argv[1] == 'start':
cD.start(log_fn)
elif sys.argv[1] == 'stop':
cD.stop()
elif sys.argv[1] == 'restart':
cD.restart(log_fn)
elif sys.argv[1] == 'status':
alive = cD.is_running()
if alive:
print ('process [%s] is running ......' % cD.get_pid())
else:
print ('daemon process [%s] stopped' %cD.name)
else:
print ('invalid argument!')
print (help_msg)
直接放入linux下自动运行就行了!