Python启动一个完全独立于启动过程的进程

时间:2014-05-27 06:15:21

标签: python subprocess popen httpserver socketserver

我正在使用python 2.5.2或2.7,这是一个启动各种任务的HTTP服务器(BaseHTTPServer)。其中一个过程是一个长期运行的过程。我希望能够做的是启动此过程,然后关闭我的HTTP服务器并重新启动。

问题是我的服务器关闭(关闭所有线程并且python.exe进程退出Windows显示的活动任务列表,启动的进程仍在运行,但netstat -ab显示sytem进程有我的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 Listninging 3728   [python.exe]

TCP vtxshm-po-0101:8091 vtxshm-po-0101:23193 TIME_WAIT 0   [FRAMEWORKSERVICE.EXE]

以下是启动实用程序后以及在按下Control-C并使python停止后netstat -ab的结果。 (请注意,操作系统认为此端口仍处于LISTENING状态,分配给PID 3728,但该任务管理器中不再存在该进程,现在该系统已归System所有,并且与snmp.exe有关(我们不会甚至使用))。这些连接被理解为它们是来自另一个服务器的启动该实用程序的请求。

活动连接

原始本地地址外部地址状态PID

TCP vtxshm-po-0101:8091 vtxshm-po-0101:0 Listninging 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启动了一个进程并完全让它独立于启动过程运行?如果是的话,请你分享这个秘密吗?

2 个答案:

答案 0 :(得分:1)

所以你定义:

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方法以干净地关闭套接字并退出。

答案 1 :(得分:0)

我将回答一个稍微不同的问题,因为这是我能找到的最接近的类似问题。

我遇到了一个程序,该程序监听端口并使用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。但它没有,所以你仍然可以像普通的那样与子进程通信。