Python 启动一个完全独立于启动进程的进程
Posted
技术标签:
【中文标题】Python 启动一个完全独立于启动进程的进程【英文标题】:Python launch a process completely independent of the launching process 【发布时间】:2014-07-15 22:38:33 【问题描述】:我正在使用 python 2.5.2 或 2.7,这是一个启动各种任务的 HTTP 服务器 (BaseHTTPServer)。其中一个进程是一个长时间运行的进程。我想做的是启动这个过程,然后关闭我的 HTTP 服务器并重新启动。
问题是我的服务器关闭(关闭所有线程并且python.exe进程退出Windows显示的活动任务列表,启动的进程仍在运行,但netstat -ab显示系统进程有我的HTTP 服务器在 LISTENING 状态下侦听的端口,并与曾经是我的 HTTP 服务器的进程 ID 相关联。该端口保持打开状态,直到启动的进程完成,这使得我无法重新启动我的 HTTP 服务器。
无论我是杀死 python 进程,还是 CTRL-C 窗口,都会表现出相同的行为。我已经阅读了大量文档,每个人都建议使用 subprocess.Popen,但即使使用它似乎也会将主进程的某些部分与启动的进程相关联。
我按如下方式启动该实用程序:
try:
# NOTE: subprocess.Popen is hanging up the 8091 port until the utility finishes.
# This needs to be addressed, otherwise, I'll never be able to restart the
# client when the utility has been launched.
listParams = [ 'C:/MyPath/My.exe', '-f', os.path.join ( sXMLDir, sXmlFile ) ]
proc = subprocess.Popen ( listParams, cwd='C:/MyPath', creationflags=0x00000008 )
iSts = 200
sStatus = 'Utility was successfully launched.'
except:
iSts = CMClasses.HTTPSTS_STARTSLEDGE_SYSTEM
sStatus = 'An exception occurred launching utility: ' + str ( sys.exc_type ) + ":" + str ( sys.exc_value ) + '.'
我的 HTTP 服务器实现如下,它允许我的主程序处理 CTRL-C:
class LaunchHTTPServer ( Thread ):
def __init__ ( self, sPort, CMRequestHandler ):
Thread.__init__ ( self )
self.notifyWindow = None
self.Port = sPort
self.CMRequestHandler = CMRequestHandler
self.bExecute = True
def run ( self ):
server = stoppableHttpServer(('',self.Port), self.CMRequestHandler )
server.serve_forever()
server.socket.close()
def getExecute ( self ):
return ( self.bExecute )
def endThread ( self ):
pass
class stoppableHttpServer ( BaseHTTPServer.HTTPServer ):
def serve_forever ( self ):
self.stop = False
while not self.stop:
self.handle_request()
def main ( argv ):
...
try:
....
tLaunchHTTPServer = LaunchHTTPServer ( iCMClientPort, CMRequestHandler )
tLaunchHTTPServer.start()
...
except KeyboardInterrupt:
logging.info ( 'main: Request to stop received' )
# End the communication threads
logging.info ( 'Requesting CMRequestHandler to close.' )
conn = httplib.HTTPConnection ( "localhost:%d" % iCMClientPort )
conn.request ( "QUIT", "/" )
conn.getresponse()
conn.close()
这是启动实用程序之前 netstat -ab 的结果(我的 python 进程是 3728,我的端口是 8091):
活动连接
原始本地地址外部地址状态PID
TCP vtxshm-po-0101:8091 vtxshm-po-0101:0 监听 3728 [python.exe]
TCP vtxshm-po-0101:8091 vtxshm-po-0101:23193 TIME_WAIT 0 [框架服务.exe]
以下是 netstat -ab 在启动实用程序后以及按 Control-C 并停止 python 后的结果。 (请注意,操作系统认为此端口仍处于侦听状态,分配给 PID 3728,但该进程不再存在于任务管理器中,现在归系统所有,并且与 snmp.exe 有某种关联(我们不甚至使用))。这些连接被理解为来自另一台服务器的启动实用程序的请求。
活动连接
原始本地地址外部地址状态PID
TCP vtxshm-po-0101:8091 vtxshm-po-0101:0 监听 3728 [系统]
TCP vtxshm-po-0101:8091 CH2ChaosMonkeyServer:2133 TIME_WAIT 0 TCP vtxshm-po-0101:8091 CH2ChaosMonkeyServer:2134 TIME_WAIT 0 TCP vtxshm-po-0101:8091 vtxshm-po-0101:23223 TIME_WAIT 0 [snmp.exe]
有没有人成功地从 python 启动了一个进程并完全独立于启动进程运行?如果是这样,你能分享一下这个秘密吗?
【问题讨论】:
看看setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 【参考方案1】:我将回答一个稍微不同的问题,因为这是我能找到的最接近的类似问题。
我在使用 subprocess.Popen() 侦听端口并在子进程中执行脚本的程序时遇到问题。如果我正在执行的脚本在后台(通过 &)运行长时间运行的作业,并且我终止了主程序,那么长时间运行的作业将接管主程序的侦听端口。
主程序:
import BaseHTTPServer
from BaseHTTPServer import BaseHTTPRequestHandler
import subprocess
class RequestHandler(BaseHTTPRequestHandler):
def do_GET(self):
proc = subprocess.Popen('start_long_proc.sh', shell=True)
stdout, stderr = proc.communicate(input)
self.send_response(200)
self.end_headers()
self.wfile.write('request served')
httpd = BaseHTTPServer.HTTPServer(('', 8000), RequestHandler)
httpd.serve_forever()
当 httpd 服务器收到请求时,它会运行“start_long_proc.sh”。该脚本如下所示:
sleep 600&
start_long_proc.sh 立即返回,请求服务完成。现在问题来了:
如果我在sleep
仍在运行时终止网络服务器,sleep
将接管侦听端口:
$ netstat -pant | grep 0.0.0.0:8000
tcp 0 0 0.0.0.0:8000 0.0.0.0:* LISTEN 24809/sleep
解决方案这里是在调用脚本时关闭除0、1、2(stdin、stdout、stderr)以外的所有文件描述符。 Popen() 为此提供了一个标志 'close_fds':
proc = subprocess.Popen('start_long_proc.sh', shell=True, close_fds=True)
现在睡眠不再与监听端口绑定:
$ netstat -pant | grep 0.0.0.0:8000 $
发生这种情况的原因是子进程从其父进程继承了打开的文件描述符。让我失望的是,其中包括监听端口。我之前看过'close_fds'选项,但认为它也关闭了STDIN、STDOUT和STDERR。但事实并非如此,因此您仍然可以像往常一样与子进程通信。
【讨论】:
【参考方案2】:所以你定义:
def run ( self ):
server = stoppableHttpServer(('',self.Port), self.CMRequestHandler )
server.serve_forever()
server.socket.close()
这里没有在您的实例中保留对变量server
的引用。这是一个问题,因为这意味着无法更改下面类中的self.stop
标志:
class stoppableHttpServer ( BaseHTTPServer.HTTPServer ):
def serve_forever ( self ):
self.stop = False
while not self.stop:
self.handle_request()
当您执行server_forever
方法时,它会阻塞其线程。由于没有保留对其父实例的引用,因此您无法设置self.stop = True
。事实上,在代码中并没有尝试这样做,因此套接字很可能会挂起。如果handle_request
阻塞(如果您没有设置超时),这也是一个问题。
您应该注意serve_forever
的默认实现可以用server.shutdown
停止,因此检查self.stop
标志的状态是多余的。
我建议将您的 LaunchHTTPServer
类更新为:
# as the only part of the code that needs to run as a thread is the serve_forever method
# there is no need to have this class as a thread
class LaunchHTTPServer (object):
def __init__ ( self, sPort, CMRequestHandler ):
self.notifyWindow = None
self.Port = sPort
self.CMRequestHandler = CMRequestHandler
self.bExecute = True
self.server = None
self.server_thread = None
def start ( self ):
# Here you can use the default HTTPServer implementation, as the server is already stoppable
self.server = BaseHTTPServer.HTTPServer(('',self.Port), self.CMRequestHandler )
self.server_thread = Thread(target=self.server.serve_forever)
self.server_thread.start()
def stop( self ):
try:
self.server.shutdown()
self.server.socket.close()
self.server_thread.join()
self.server,self.server_thread = None,None
except Exception as error:
pass # catch and raise which ever errors you desire here
def getExecute ( self ):
return ( self.bExecute )
def endThread ( self ):
pass
通过上述设置,您现在可以从键盘捕获 Ctrl-C 中断,并确保调用实例的 stop
方法以干净地关闭套接字并退出。
【讨论】:
尽管您提供了对 HTTP 服务器的更清晰的拆解,但仍存在相同的行为。在调用 stop() 时,也不例外。我对 subprocess.Popen() 的理解不正确,存在错误,或者这是不可能的。感谢您的意见。 对不起,它没有更多用处。也许您可以考虑设置 Python 守护程序,而不是启动进程并退出。 抱歉这么久才回复。修改了启动守护线程的代码,它所做的只是执行相同的 subprocess.Popen() 并且效果很好。 +1 用于跟进此问题(一个好的新用户问题令人耳目一新)。以上是关于Python 启动一个完全独立于启动进程的进程的主要内容,如果未能解决你的问题,请参考以下文章