如何在Windows中将Python脚本作为服务运行?

340

我正在为一组程序勾勒架构,这些程序共享存储在数据库中的各种相互关联的对象。我希望其中一个程序作为服务提供对这些对象进行操作的更高级别接口,而其他程序通过该服务访问这些对象。

目前,我计划使用Python和Django框架实现该服务。我相当确定如何在Linux中将Python程序设为守护进程。然而,系统应支持Windows是可选规格要求,我在Windows编程方面经验很少,也没有任何关于Windows服务的经验。

能否将Python程序作为Windows服务运行(即自动运行而无需用户登录)? 我不一定非要实现这个部分,但需要大致了解如何完成,以便决定是否按照这些线路进行设计。

编辑:感谢迄今为止所有答案,它们相当全面。我还想知道一件事:Windows如何知道我的服务?我能用原生Windows工具管理它吗? /etc/init.d中start/stop脚本的等价物是什么?

14个回答

297

是的,你可以。我使用随附于ActivePython的pythoncom库进行操作,或者可以通过pywin32(适用于Windows的Python扩展)进行安装。

这是一个简单服务的基本框架:

import win32serviceutil
import win32service
import win32event
import servicemanager
import socket


class AppServerSvc (win32serviceutil.ServiceFramework):
    _svc_name_ = "TestService"
    _svc_display_name_ = "Test Service"

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

    def SvcStop(self):
        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
        win32event.SetEvent(self.hWaitStop)

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

    def main(self):
        pass

if __name__ == '__main__':
    win32serviceutil.HandleCommandLine(AppServerSvc)

你的代码应该放到main()方法中,通常会配合一些无限循环,可以通过检查在SvcStop方法中设置的标志来中断。


32
编写完这个程序后,我该如何告诉Windows将其作为服务运行? - Kit
38
@Kit:使用带有参数“install”的命令行运行您的脚本。然后您就可以在Windows的服务列表中看到您的应用程序,您可以启动它、停止它或将其设置为自动启动。 - Ricardo Reyes
21
你在提到pythoncom时进行了特别的说明,并且在示例代码中导入了它。但问题是,你在示例代码中从未实际使用过pythoncom,只是导入了它。为什么要特别提到它却不展示它的用途呢? - Buttons840
15
socket.setdefaulttimeout(60)是什么意思? 是服务需要用到它,还是只是无意中从某个现有服务复制过来的? :) 它的作用是设置socket模块的默认超时时间为60秒。这可能是为了在服务中保证网络连接的稳定性和可靠性。无法确定它是否是从其他服务中复制而来,还是特意添加的。 - Tim
5
我同意 @Timur 的观点,socket.setdefaulttimeout(60) 的原因是什么? - ThatAintWorking
显示剩余20条评论

97

最简单的方法是使用: NSSM - 非吸管服务管理器。只需下载并解压到您选择的位置即可。它是一个自包含的实用程序,大小约为300KB(比仅出于此目的安装整个pywin32套件要少得多),不需要进行"安装"。zip文件包含该实用程序的64位和32位版本。任何一个版本都可以在当前系统上良好运行(您可以使用32位版本来管理64位系统上的服务)。

图形用户界面(GUI)方法

1- 将Python程序安装为服务。以管理员身份打开Win提示符

c:\>nssm.exe install WinService

2-在NSSM的图形用户界面控制台上:

路径:C:\Python27\Python27.exe

启动目录:C:\Python27

参数:c:\WinService.py

3-检查在services.msc上创建的服务

脚本方法(无GUI)

如果你的服务应该是自动化的、非交互式的程序的一部分,这将非常方便,这可能超出了你的控制,比如批处理程序或安装程序。假定使用管理员权限执行命令。

为了方便起见,在此将工具简单地称为nssm.exe。然而,在脚本中最好更明确地引用它,使用其完整路径c:\path\to\nssm.exe,因为它是一个自包含的可执行文件,可能位于系统未知的私有路径中。

1.安装服务

必须指定服务的名称、适当Python可执行文件的路径以及脚本的路径:

nssm.exe install ProjectService "c:\path\to\python.exe" "c:\path\to\project\app\main.py"

更明确地说:

nssm.exe install ProjectService 
nssm.exe set ProjectService Application "c:\path\to\python.exe"
nssm.exe set ProjectService AppParameters "c:\path\to\project\app\main.py"

或者你可能希望将你的Python应用程序作为Python模块启动。一个简单的方法是告诉nssm它需要更改到正确的启动目录,就像你自己从命令行启动时所做的一样:

nssm.exe install ProjectService "c:\path\to\python.exe" "-m app.main"
nssm.exe set ProjectService AppDirectory "c:\path\to\project"

这种方法在虚拟环境和自包含的Python安装中表现良好。只需确保使用通常的方法正确解决了这些环境中的任何路径问题即可。如果需要,nssm可以设置环境变量(例如PYTHONPATH),还可以启动批处理脚本。

2. 启动服务

nssm.exe start ProjectService 

3. 停止服务

nssm.exe stop ProjectService

4. 移除服务: 指定confirm参数以跳过交互确认。

nssm.exe remove ProjectService confirm

1
我曾经使用nssm.exe将我的Visual Studio C++ .exe安装为服务,现在我也可以使用nssm.exe将我的Python .pyc作为服务。谢谢。 - etoricky
4
注意:如果您的 *.py 脚本位于带有空格的文件夹中(例如:C:\Program Files\myapp.py),需要用引号指定参数:Arguments: "C:\Program Files\myapp.py" - Yury Kozlov
1
如何提供虚拟环境? - shaik moeed
6
不要浪费时间,采用NSSM方法。对于虚拟环境,您只需要指向虚拟环境文件夹内的Python可执行文件即可。 - Gustavo Gonçalves
3
nssm现在已经是一个废弃的项目了。最好使用pywin32方法。 - Aelius
显示剩余2条评论

53
虽然几周前我已经点赞了所选的答案,但在此期间我对这个主题更加困惑了。感觉像是使用特殊的Python安装和模块来运行脚本作为服务只是错误的方式。那么可移植性呢?
我偶然发现了非常棒的Non-sucking Service Manager,它使得处理Windows服务变得非常简单和合理。我想既然我可以将选项传递给已安装的服务,那么我也可以选择我的Python可执行文件并将我的脚本作为选项传递。
我还没有尝试过这个解决方案,但我现在会这样做,并在过程中更新这篇文章。我也有兴趣在Windows上使用virtualenvs,因此我可能会很快提供教程并在这里链接到它。

是的,这个方法可行而且非常容易实现。你只需要提供脚本的路径和参数即可。我成功地让我的脚本在没有控制台窗口的情况下运行,以防万一有人不小心打开了控制台窗口。 - kmcguire
虽然这似乎可行,但还有其他困难,特别是当你“不需要使用整个Apache堆栈”时:例如,gunicorn尚未在Windows上运行,这实际上是我遇到的障碍。 - mknaf
4
关键在于将python.exe作为服务运行,并将您的Python脚本作为参数传递:例如,“nssm install MyServiceName c:\python27\python.exe c:\temp\myscript.py”。 - poleguy
非常好用!在一个有多个虚拟环境的系统中,路径可以引用所需虚拟环境的Scripts目录中的Python解释器exe。似乎PowerShell中的new-service应该能够做到这一点,但是将脚本作为服务启动(和监视)显然涉及更多细节,而nssm非常好地处理了这些细节。 - Fred Schleifer
@FredSchleifer 如何为NSSM提供虚拟环境? - shaik moeed
显示剩余3条评论

39

1
我认为,这可能是您的命令或应用程序本身存在问题。无论如何,请查看此链接:https://support.microsoft.com/zh-cn/help/886695/you-receive-an-error-1053-the-service-did-not-respond-to-the-start-or - pyOwner
我的应用在服务外部运行良好,我使用相同的代码在上面,但没有结果。 - Nimer Awad
1
如何提供虚拟环境? - shaik moeed
你试过 virtualenv 吗? - pyOwner
11
这不起作用。Windows服务必须公开pywin32软件包所提供的某些接口。但是,普通的Python脚本无法满足要求。 - Siddhartha Gandhi
如果您使用PowerShell运行,则应使用sc.exe,大多数抱怨的人应该遇到了这个问题。请参见https://dev59.com/elUK5IYBdhLWcg3w9jrb。 - Gayan Kavirathne

24

逐步解释如何使其工作:

1- 首先根据上述基本框架创建一个Python文件。并将其保存到一个路径,例如:"c:\PythonFiles\AppServerSvc.py"。

import win32serviceutil
import win32service
import win32event
import servicemanager
import socket


class AppServerSvc (win32serviceutil.ServiceFramework):
    _svc_name_ = "TestService"
    _svc_display_name_ = "Test Service"


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

    def SvcStop(self):
        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
        win32event.SetEvent(self.hWaitStop)

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

    def main(self):
        # Your business logic or call to any class should be here
        # this time it creates a text.txt and writes Test Service in a daily manner 
        f = open('C:\\test.txt', 'a')
        rc = None
        while rc != win32event.WAIT_OBJECT_0:
            f.write('Test Service  \n')
            f.flush()
            # block for 24*60*60 seconds and wait for a stop event
            # it is used for a one-day loop
            rc = win32event.WaitForSingleObject(self.hWaitStop, 24 * 60 * 60 * 1000)
        f.write('shut down \n')
        f.close()

if __name__ == '__main__':
    win32serviceutil.HandleCommandLine(AppServerSvc)

第二步,我们应该注册我们的服务。

管理员身份运行命令提示符,并键入以下内容:

sc create TestService binpath= "C:\Python36\Python.exe c:\PythonFiles\AppServerSvc.py" DisplayName= "TestService" start= auto

binpath的第一个参数是python.exe的路径

binpath的第二个参数是您已经创建的Python文件的路径

请注意,在每个“=”符号后面要加上一个空格。

如果一切顺利,您应该看到:

[SC] CreateService SUCCESS

现在,您的Python服务已安装为Windows服务。您可以在“服务管理器”和注册表下看到它:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\TestService

第三步,您可以在“服务管理器”中启动您的服务。

您可以执行提供此服务框架的每个Python文件。


1
有很多不好的例子展示了如何使用SetEvent(self.hWaitStop)WaitForSingleObject。这可能是基于无思考地复制此处所选答案。这是一种良好的方法,可以干净地处理“debug”和“stop”参数。(在HandleCommandLine完成工作并且可以运行debug时,使用SC的部分似乎是多余的。) - Alias_Knagg
我花了一些时间才意识到,在等号 "=" 后面不仅必须加上空格,而且在符号前面不能加空格。 - dariox
谢谢。这个方法有效,而我没能让被接受的答案中的“install”参数起作用。 - Tim MB

23
有几种方法可以将任何Windows可执行文件安装为服务。
方法1:使用rktools.exe中的instsrv和srvany
对于Windows Home Server或Windows Server 2003(也适用于WinXP),Windows Server 2003资源工具包配备了一些实用程序,可以一起使用,称为 instsrv.exe srvany.exe 。请参阅此Microsoft KB文章KB137890以了解如何使用这些实用程序。
对于Windows Home Server,有一个名为“Any Service Installer”的用户友好的包装器,用于这些实用程序。
方法2:使用Windows NT的ServiceInstaller

还有另一种选择,可以使用适用于Windows NT的ServiceInstaller可在此下载),并提供Python说明。与其名称相反,它也适用于Windows 2000和Windows XP。以下是如何将Python脚本安装为服务的一些说明。

Installing a Python script

Run ServiceInstaller to create a new service. (In this example, it is assumed that python is installed at c:\python25)

Service Name  : PythonTest
Display Name : PythonTest 
Startup : Manual (or whatever you like)
Dependencies : (Leave blank or fill to fit your needs)
Executable : c:\python25\python.exe
Arguments : c:\path_to_your_python_script\test.py
Working Directory : c:\path_to_your_python_script

After installing, open the Control Panel's Services applet, select and start the PythonTest service.

在我最初的回答之后,我注意到 SO 上已经发布了相关的 Q&A。另请参阅:
- 我可以在 Windows 中将 Python 脚本作为服务运行吗?如何实现? - 我如何让 Windows 知道我用 Python 编写的服务?

我刚刚注意到已经有其他类似的问答了: https://dev59.com/nnVD5IYBdhLWcg3wRpeX https://dev59.com/onVD5IYBdhLWcg3wRpaX - popcnt
服务安装程序在64位架构上无法工作,因此选项1成为首选。 - Noah Campbell
以上关于ServiceInstaller的链接已经失效。我在这里找到了它:https://sites.google.com/site/conort/runanywindowsapplicationasntservice - LarsH
2
注意,我认为“NT”并不一定与名称相反,至少在程序员的口语中不是这样。它只是指“NT 架构”,而不是“NT 品牌”。话虽如此,根据维基百科上的讨论,这仍然存在争议,因为“这不是微软的官方术语”,但是有这种思路的传统。 - n611x007

6

nssm在Python 3+中的应用

(我使用pyinstaller将我的.py文件转换为.exe文件)

nssm: 如前所述

  • 运行nssm install {ServiceName}
  • NSSM控制台上:

    路径:你的程序.exe的路径

    启动目录:你的#与路径相同,但不包括你的program.exe

    参数:空

如果您不想将项目转换为.exe文件

  • 创建一个带有python {{your python.py file name}}的.bat文件
  • 并将路径设置为.bat文件

如何提供虚拟环境? - shaik moeed

6

pysc: Python的服务控制管理器

从pythonhosted.org获取的作为服务运行的示例脚本如下:

from xmlrpc.server import SimpleXMLRPCServer

from pysc import event_stop


class TestServer:

    def echo(self, msg):
        return msg


if __name__ == '__main__':
    server = SimpleXMLRPCServer(('127.0.0.1', 9001))

    @event_stop
    def stop():
        server.server_close()

    server.register_instance(TestServer())
    server.serve_forever()

Create and start service

import os
import sys
from xmlrpc.client import ServerProxy

import pysc


if __name__ == '__main__':
    service_name = 'test_xmlrpc_server'
    script_path = os.path.join(
        os.path.dirname(__file__), 'xmlrpc_server.py'
    )
    pysc.create(
        service_name=service_name,
        cmd=[sys.executable, script_path]
    )
    pysc.start(service_name)

    client = ServerProxy('http://127.0.0.1:9001')
    print(client.echo('test scm'))

Stop and delete service

import pysc

service_name = 'test_xmlrpc_server'

pysc.stop(service_name)
pysc.delete(service_name)
pip install pysc

5
我使用了pywin32来作为服务托管的工具。

一切都进行得很顺利,但我遇到了一个问题:在系统启动时,服务无法在30秒内启动(这是Windows的默认超时时间)。对我来说,这十分重要,因为Windows启动同时在一个物理机上托管了多台虚拟机,IO负载非常大。 错误信息如下:

Error 1053: The service did not respond to the start or control request in a timely fashion.

Error 7009: Timeout (30000 milliseconds) waiting for the <ServiceName> service to connect.

我曾经费尽心思地尝试解决这个问题,但最终选择使用NSSM,因为这个答案中建议使用它。很容易迁移到这个工具上。


3
这段话没有回答原始问题,但可以帮助其他想要在Windows启动时自动启动Python脚本的人: 请查看Windows任务计划程序,如果您只想在启动后启动脚本而不需要所有Windows服务功能,那么它会更加容易。
创建一个新任务,选择“在启动时”作为触发器,“启动程序”作为操作,将“C:\ Python39 \ python.exe”作为程序(或您的python.exe所在位置),“C:...\ my_dir \ xyz.py”作为参数的完整路径(如果路径包含空格,则可以使用“”)。 如果您在脚本中使用相对路径(例如用于日志记录),则还可以选择脚本的路径(不含.py文件,例如“C:...\ my_dir”)作为“启动”路径。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接