python 版的守护进程和Windows服务

概述

最近在写python脚本时需要用到Linux的守护进程以及Windows下的服务进程。百度了挺多,都记下来,以后如果再次遇到这种问题就不用去找了。

Linux

守护进程最重要的特性是后台运行;它必须与其运行前的环境隔离开来,这些环境包括未关闭的文件描述符、控制终端、会话和进程组、工作目录以及文件创建掩码等;它可以在系统启动时从启动脚本/etc/rc.d中启动,可以由inetd守护进程启动,也可以有作业规划进程crond启动,还可以由用户终端(通常是shell)执行。
Python有时需要保证只运行一个脚本实例,以避免数据的冲突。详细参考大神文章

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
增加守护进程(linux)
'''
import os
import datetime
import time
import sys
import atexit
import string
import subprocess
from signal import SIGTERM
from lib.function import confdeal
class Daemon:
#需要获取调试信息,改为stdin='/dev/stdin', stdout='/dev/stdout', stderr='/dev/stderr',以root身份运行。
def __init__(self, scriptPath,pidfile, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
self.stdin = stdin
self.stdout = stdout
self.stderr = stderr
self.pidfile = pidfile
self.scriptPath = scriptPath
def _daemonize(self):
try:
pid = os.fork() #第一次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)
os.chdir("/") #修改工作目录
os.setsid() #设置新的会话连接
os.umask(0) #重新设置文件创建权限
try:
pid = os.fork() #第二次fork,禁止进程打开终端
if pid > 0:
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())
#注册退出函数,根据文件pid判断是否存在进程
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):
#检查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)
sys.exit(1)
#启动监控
self._daemonize()
self._run()
def stop(self):
#从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)
return
#杀进程
# 我的程序需要,只是获取端口号
remoteDic = confdeal.getConfRemote(self.scriptPath)
if remoteDic: # 根据端口号杀掉子进程
ret = os.popen('netstat -antup | grep \":'+str(remoteDic['localPort'])+' \"')
strList = list(set(ret.read().split('\n')))
for pidStr in strList:
if pidStr != '':
pidStr = pidStr.split('/')[0]
pidStr = pidStr[pidStr.rfind(' ')+1:]
os.system("kill "+pidStr)
try:
while 1:
os.kill(pid, SIGTERM)
time.sleep(0.1)
#os.system('hadoop-daemon.sh stop datanode')
#os.system('hadoop-daemon.sh stop tasktracker')
#os.remove(self.pidfile)
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):
self.stop()
self.start()
def _run(self):
# 开启子进程
cmd = 'cd ' + self.scriptPath +' && python test.py'
child = subprocess.Popen(cmd, shell=True)
while 1:
if child.poll()==1:
child = subprocess.Popen(cmd, shell=True)
time.sleep(10)
if __name__ == '__main__':
scriptPath = os.path.dirname(os.path.realpath(__file__))
daemon = Daemon(scriptPath,'/tmp/watch_process.pid', stdout = '/tmp/watch_stdout.log')
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)

Windows

Python程序作为Windows服务启动,需要安装pywin32包。详细参考大神文章

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
增加windows服务
'''
import pythoncom
import win32serviceutil
import win32service
import win32event
import servicemanager
import socket
import os
import sys
import subprocess
import time
from lib.function import confdeal
class Daemon (win32serviceutil.ServiceFramework):
_svc_name_ = "Test Service" #服务名
_svc_display_name_ = "Test Service" #服务显示名称
_svc_description_ = "Test Service ." #服务描述
def __init__(self,args):
win32serviceutil.ServiceFramework.__init__(self,args)
self.hWaitStop = win32event.CreateEvent(None,0,0,None)
socket.setdefaulttimeout(60)
def SvcDoRun(self):
servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE,
servicemanager.PYS_SERVICE_STARTED,
(self._svc_name_,''))
self.monitorProcess()
# 创建子进程
def monitorProcess(self):
with open('C:\\filemonitor', 'r') as f:
scriptPath=f.read()
if os.path.exists(scriptPath):
cmd = 'cd '+scriptPath+' && python promain.py'
child = subprocess.Popen(cmd, shell=True)
while 1:
if child.poll()==1:
child = subprocess.Popen(cmd, shell=True)
time.sleep(10)
win32event.WaitForSingleObject(self.hWaitStop, win32event.INFINITE)
# 结束子进程
def SvcStop(self):
with open('C:\\filemonitor', 'r') as f:
scriptPath=f.read()
if os.path.exists(scriptPath):
remoteDic = confdeal.getConfRemote(scriptPath)
if remoteDic:
ret = os.popen('netstat -ano | findstr \":'+str(remoteDic['localPort'])+' \"')
strList = list(set(ret.read().split('\n')))
for pidStr in strList:
if pidStr != '':
pidStr = pidStr[pidStr.rfind(' ')+1:]
os.system("taskkill /F /T /PID "+pidStr)
else :
exit()
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
win32event.SetEvent(self.hWaitStop)
self.ReportServiceStatus(win32service.SERVICE_STOPPED)
if __name__ == '__main__':
scriptPath = os.path.dirname(os.path.realpath(__file__))
with open('C:\\filemonitor', 'w') as f:
f.write(scriptPath)
confdeal.initConf(scriptPath)
win32serviceutil.HandleCommandLine(Daemon)

然后以下命令:

1
2
3
4
5
6
7
8
9
10
11
12
# 安装服务
python PythonService.py install
# 让服务自动启动
python PythonService.py --startup auto install
# 启动服务
python PythonService.py start
# 重启服务
python PythonService.py restart
# 停止服务
python PythonService.py stop
# 删除/卸载服务
python PythonService.py remove

这里我要提一下,python脚本文件调用windows api后,他的运行路径会发生变化,所以我这里把脚本路径存到了一个绝对地址的文件里,然后再取出来。