我正在使用 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 [FrameworkService.exe]
以下是启动实用程序后以及按 Control-C 并让 python 停止后 netstat -ab 的结果。 (请注意,操作系统认为该端口仍处于监听状态,分配给 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 启动了一个进程,并且完全让它独立于启动进程运行?如果是这样,您能分享一下这个 secret 吗?
最佳答案
我将回答一个稍微不同的问题,因为这是我能找到的最接近的类似问题。
我在使用一个程序时遇到问题,该程序监听端口并使用 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)
现在 sleep 不再与监听端口绑定(bind):
$ netstat -pant | grep 0.0.0.0:8000 $
发生这种情况的原因是子进程从其父进程继承打开的文件描述符。让我失望的是其中包括监听端口。我之前看到过“close_fds”选项,但认为它也关闭了 STDIN、STDOUT 和 STDERR。但事实并非如此,因此您仍然可以像平常一样与子进程通信。
关于Python启动进程完全独立于启动进程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23882256/