我正在为一组共享存储在数据库中的各种相关对象的程序绘制体系结构草图。我希望其中一个程序充当服务,为这些对象上的操作提供更高级别的接口,而其他程序通过该服务访问对象。

我目前的目标是将Python和Django框架作为实现该服务的技术。我很确定我知道如何在Linux中守护Python程序。但是,系统应该支持Windows,这是一个可选的规格项。我几乎没有Windows编程经验,对Windows服务也没有任何经验。

是否有可能运行Python程序作为Windows服务(即自动运行,无需用户登录)?我不一定要实现这一部分,但我需要一个大致的想法,以便决定是否沿着这些路线进行设计。

编辑:谢谢你到目前为止所有的回答,他们是相当全面的。我还想知道一件事:Windows是如何感知我的服务的?我可以使用本机Windows实用程序管理它吗?在/etc/init.d中放置启动/停止脚本的等效内容是什么?


当前回答

NSSM在python 3+

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

nssm: 如前所述

运行nssm install {ServiceName} 在NSSM的控制台: \ \你\ program.exe路径:路径 启动目录:路径\to\your\ #与路径相同,但没有你的program.exe 参数:空

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

用python{{你的python.py文件名}}创建一个.bat文件 并将路径设置为。bat文件

其他回答

最简单的方法是使用本机命令sc.exe:

sc create PythonApp binPath= "C:\Python34\Python.exe --C:\tmp\pythonscript.py"

引用:

https://technet.microsoft.com/en-us/library/cc990289 (v = ws.11) . aspx 当用sc.exe创建服务时,如何传递上下文参数?

虽然我在几周前对所选的答案进行了投票,但与此同时,我在这个话题上挣扎了很多。感觉使用特殊的Python安装并使用特殊的模块将脚本作为服务运行是一种错误的方式。可移植性如何?

我偶然发现了很棒的非吸吮服务管理器,它让处理Windows服务变得非常简单和理智。我想既然我可以将选项传递给已安装的服务,那么我也可以选择我的Python可执行文件并将脚本作为一个选项传递。

我还没有尝试过这个解决方案,但我会马上尝试,并在此过程中更新这篇文章。我也对在Windows上使用virtualenvs感兴趣,所以我可能迟早会想出一个教程,并链接到这里。

NSSM在python 3+

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

nssm: 如前所述

运行nssm install {ServiceName} 在NSSM的控制台: \ \你\ program.exe路径:路径 启动目录:路径\to\your\ #与路径相同,但没有你的program.exe 参数:空

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

用python{{你的python.py文件名}}创建一个.bat文件 并将路径设置为。bat文件

这个答案是从StackOverflow上的几个来源抄袭的——上面大部分都是,但我已经忘记了其他的——对不起。它很简单,脚本可以“按原样”运行。对于发布,您测试脚本,然后将其复制到服务器并停止/启动相关的服务。它应该适用于所有脚本语言(Python, Perl, node.js),加上批处理脚本,如GitBash, PowerShell,甚至旧的DOS bat脚本。 pyGlue是位于Windows服务和脚本之间的胶水。

'''
A script to create a Windows Service, which, when started, will run an executable with the specified parameters.
Optionally, you can also specify a startup directory

To use this script you MUST define (in class Service)
1. A name for your service (short - preferably no spaces)
2. A display name for your service (the name visibile in Windows Services)
3. A description for your service (long details visible when you inspect the service in Windows Services)
4. The full path of the executable (usually C:/Python38/python.exe or C:WINDOWS/System32/WindowsPowerShell/v1.0/powershell.exe
5. The script which Python or PowerShell will run(or specify None if your executable is standalone - in which case you don't need pyGlue)
6. The startup directory (or specify None)
7. Any parameters for your script (or for your executable if you have no script)

NOTE: This does not make a portable script.
The associated '_svc_name.exe' in the dist folder will only work if the executable,
(and any optional startup directory) actually exist in those locations on the target system

Usage: 'pyGlue.exe [options] install|update|remove|start [...]|stop|restart [...]|debug [...]'
Options for 'install' and 'update' commands only:
        --username domain\\username : The Username the service is to run under
        --password password : The password for the username
        --startup [manual|auto|disabled|delayed] : How the service starts, default = manual
        --interactive : Allow the service to interact with the desktop.
        --perfmonini file: .ini file to use for registering performance monitor data
        --perfmondll file: .dll file to use when querying the service for performance data, default = perfmondata.dll
Options for 'start' and 'stop' commands only:
        --wait seconds: Wait for the service to actually start or stop.
                If you specify --wait with the 'stop' option, the service and all dependent services will be stopped,
                each waiting the specified period.
'''

# Import all the modules that make life easy
import servicemanager
import socket
import sys
import win32event
import win32service
import win32serviceutil
import win32evtlogutil
import os
from logging import Formatter, Handler
import logging
import subprocess


# Define the win32api class
class Service (win32serviceutil.ServiceFramework):
        # The following variable are edited by the build.sh script
        _svc_name_ = "TestService"
        _svc_display_name_ = "Test Service"
        _svc_description_ = "Test Running Python Scripts as a Service"
        service_exe = 'c:/Python27/python.exe'
        service_script = None
        service_params = []
        service_startDir = None

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

        # Configure logging to the WINDOWS Event logs
        def configure_logging(self):
                self.formatter = Formatter('%(message)s')
                self.handler = logHandler()
                self.handler.setFormatter(self.formatter)
                self.logger = logging.getLogger()
                self.logger.addHandler(self.handler)
                self.logger.setLevel(logging.INFO)

        # Stop the service
        def SvcStop(self):
                self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
                win32event.SetEvent(self.hWaitStop)

        # Run the service
        def SvcDoRun(self):
                self.main()

        # This is the service
        def main(self):

                # Log that we are starting
                servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE, servicemanager.PYS_SERVICE_STARTED,
                                                          (self._svc_name_, ''))

                # Fire off the real process that does the real work
                logging.info('%s - about to call Popen() to run %s %s %s', self._svc_name_, self.service_exe, self.service_script, self.service_params)
                self.process = subprocess.Popen([self.service_exe, self.service_script] + self.service_params, shell=False, cwd=self.service_startDir)
                logging.info('%s - started process %d', self._svc_name_, self.process.pid)

                # Wait until WINDOWS kills us - retrigger the wait for stop every 60 seconds
                rc = None
                while rc != win32event.WAIT_OBJECT_0:
                        rc = win32event.WaitForSingleObject(self.hWaitStop, (1 * 60 * 1000))

                # Shut down the real process and exit
                logging.info('%s - is terminating process %d', self._svc_name_, self.process.pid)
                self.process.terminate()
                logging.info('%s - is exiting', self._svc_name_)


class logHandler(Handler):
        '''
Emit a log record to the WINDOWS Event log
        '''

        def emit(self, record):
                servicemanager.LogInfoMsg(record.getMessage())


# The main code
if __name__ == '__main__':
        '''
Create a Windows Service, which, when started, will run an executable with the specified parameters.
        '''

        # Check that configuration contains valid values just in case this service has accidentally
        # been moved to a server where things are in different places
        if not os.path.isfile(Service.service_exe):
                print('Executable file({!s}) does not exist'.format(Service.service_exe), file=sys.stderr)
                sys.exit(0)
        if not os.access(Service.service_exe, os.X_OK):
                print('Executable file({!s}) is not executable'.format(Service.service_exe), file=sys.stderr)
                sys.exit(0)
        # Check that any optional startup directory exists
        if (Service.service_startDir is not None) and (not os.path.isdir(Service.service_startDir)):
                print('Start up directory({!s}) does not exist'.format(Service.service_startDir), file=sys.stderr)
                sys.exit(0)

        if len(sys.argv) == 1:
                servicemanager.Initialize()
                servicemanager.PrepareToHostSingle(Service)
                servicemanager.StartServiceCtrlDispatcher()
        else:
                # install/update/remove/start/stop/restart or debug the service
                # One of those command line options must be specified
                win32serviceutil.HandleCommandLine(Service)

现在有一些编辑,你不希望你所有的服务都叫“pyGlue”。所以有一个脚本(build.sh)来插入比特并创建一个自定义的“pyGlue”和创建一个“。exe”。就是这个“。exe”被安装为Windows服务。安装后,您可以将其设置为自动运行。

#!/bin/sh
# This script build a Windows Service that will install/start/stop/remove a service that runs a script
# That is, executes Python to run a Python script, or PowerShell to run a PowerShell script, etc

if [ $# -lt 6 ]; then
        echo "Usage: build.sh Name Display Description Executable Script StartupDir [Params]..."
        exit 0
fi

name=$1
display=$2
desc=$3
exe=$4
script=$5
startDir=$6
shift; shift; shift; shift; shift; shift
params=
while [ $# -gt 0 ]; do
        if [ "${params}" != "" ]; then
                params="${params}, "
        fi
        params="${params}'$1'"
        shift
done

cat pyGlue.py | sed -e "s/pyGlue/${name}/g" | \
        sed -e "/_svc_name_ =/s?=.*?= '${name}'?" | \
        sed -e "/_svc_display_name_ =/s?=.*?= '${display}'?" | \
        sed -e "/_svc_description_ =/s?=.*?= '${desc}'?" | \
        sed -e "/service_exe =/s?=.*?= '$exe'?" | \
        sed -e "/service_script =/s?=.*?= '$script'?" | \
        sed -e "/service_params =/s?=.*?= [${params}]?" | \
        sed -e "/service_startDir =/s?=.*?= '${startDir}'?" > ${name}.py

cxfreeze ${name}.py --include-modules=win32timezone

安装-复制'.exe'服务器和脚本到指定的文件夹。以管理员身份运行“。exe”,并选择“install”。以管理员身份打开Windows服务,并启动服务。若要升级,只需复制新版本的脚本并停止/启动服务。

现在每个服务器都是不同的——不同的Python安装,不同的文件夹结构。我为每个服务器维护一个文件夹,其中包含pyGlue.py和build.sh的副本。我创建了一个'serverBuild.sh'脚本,用于重建该服务器上的所有服务。

# A script to build all the script based Services on this PC
sh build.sh AutoCode 'AutoCode Medical Documents' 'Autocode Medical Documents to SNOMED_CT and AIHW codes' C:/Python38/python.exe autocode.py C:/Users/russell/Documents/autocoding -S -T

是的,你可以。我使用ActivePython附带的pythoncom库,或者可以使用pywin32 (Python for Windows扩展)安装。

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

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方法中设置的标志可能会中断这种循环