在Windows中将Python网络服务器作为服务运行

8 投票
1 回答
9104 浏览
提问于 2025-04-11 09:32

我写了一个小的网络服务器应用程序,使用Python从数据库中获取一些数据,并将其以XML格式返回给用户。这个部分运行得很好——我可以在命令行中启动这个Python网络服务器应用,并且客户端可以连接到它并获取数据。目前,我需要以管理员身份登录到我们的服务器,并手动启动这个网络服务器。我希望这个网络服务器能够在系统启动时自动作为服务启动,并在后台运行。

我参考了ActiveState网站和StackOverflow上的代码,已经对如何创建一个服务有了不错的了解,我觉得这部分我已经搞定了——我可以把我的网络服务器安装并作为Windows服务启动。不过,我现在还搞不清楚如何再次停止这个服务。我的网络服务器是基于BaseHTTPServer创建的:

server = BaseHTTPServer.HTTPServer(('', 8081), SIMSAPIServerHandler)
server.serve_forever()

serve_forever()这个调用,自然是让网络服务器进入一个无限循环,等待HTTP连接(或者ctrl-break按键,这对服务来说没用)。从上面的示例代码中,我明白了你的main()函数应该在一个无限循环中运行,只有在遇到“停止”条件时才会跳出这个循环。我的main调用了serve_forever()。我有一个SvcStop函数:

def SvcStop(self):
    self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
    exit(0)

当我在命令行中输入“python myservice stop”时,这个函数似乎被调用了(我可以在里面放一个调试行,输出到文件),但它并没有真正退出整个服务——后续调用“python myservice start”时出现错误:

启动服务时出错:服务的一个实例已经在运行。

而后续调用停止服务时则出现:

停止服务时出错:服务此时无法接受控制消息。(1061)

我觉得我需要一个替代serve_forever的函数(比如serve_until_stop_received,或者其他什么的),或者我需要某种方法来修改SvcStop,使其能够停止整个服务。

这是完整的代码列表(我已经删减了一些包含的内容和注释以节省空间):

class SIMSAPIServerHandler(BaseHTTPServer.BaseHTTPRequestHandler):
    def do_GET(self):
        try:
            reportTuple = self.path.partition("/")
            if len(reportTuple) < 3:
                return
            if reportTuple[2] == "":
                return
            os.system("C:\\Programs\\SIMSAPI\\runCommandReporter.bat " + reportTuple[2])
            f = open("C:\\Programs\\SIMSAPI\\out.xml", "rb")
            self.send_response(200)
            self.send_header('Content-type', "application/xml")
            self.end_headers()
            self.wfile.write(f.read())
            f.close()
            # The output from CommandReporter is simply dumped to out.xml, which we read, write to the user, then remove.
            os.unlink("C:\\Programs\\SIMSAPI\\out.xml")
            return
        except IOError:
            self.send_error(404,'File Not Found: %s' % self.path)



class SIMSAPI(win32serviceutil.ServiceFramework):
    _svc_name_ = "SIMSAPI"
    _svc_display_name_ = "A simple web server"
    _svc_description_ = "Serves XML data produced by SIMS CommandReporter"

    def __init__(self, args):
        win32serviceutil.ServiceFramework.__init__(self, args)
        self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)           

    def SvcStop(self):
            self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
            exit(0)

    def SvcDoRun(self):
        import servicemanager
        servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE,servicemanager.PYS_SERVICE_STARTED,(self._svc_name_, '')) 

        self.timeout = 3000

        while 1:
            server = BaseHTTPServer.HTTPServer(('', 8081), SIMSAPIServerHandler)
            server.serve_forever()

def ctrlHandler(ctrlType):
    return True

if __name__ == '__main__':
    win32api.SetConsoleCtrlHandler(ctrlHandler, True)
    win32serviceutil.HandleCommandLine(SIMSAPI)

1 个回答

4

这是我所做的:

我没有直接使用 BaseHTTPServer.HTTPServer 这个类,而是从它那里创建了一个新的子类,并添加了一个“停止”方法:

class AppHTTPServer (SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
    def serve_forever(self):
        self.stop_serving = False
        while not self.stop_serving:
            self.handle_request()

    def stop (self):
        self.stop_serving = True

然后,在你已经有的 SvcStop 方法里,我调用这个方法来打破 serve_forever() 的循环:

def SvcStop(self):
    self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
    self.httpd.stop()

(self.httpd 是实现了网络服务器的 AppHTTPServer() 的实例)

如果你在后台线程上正确使用 setDaemon(),并且正确中断服务中的所有循环,那么在 SvcStop() 中的指令

exit(0)

就不应该是必要的了。

撰写回答