Python Script als Windows Dienst funktioniert nicht

Code-Stücke können hier veröffentlicht werden.
Antworten
c0d4r
User
Beiträge: 5
Registriert: Mittwoch 2. März 2016, 11:11

Mittwoch 2. März 2016, 11:36

Hallo,

ich möchte gerne ein Python Script als Dienst auf einem Windows Server laufen lassen. Dies gelingt mir jedoch nicht so ganz.
Das Script ließt Daten aus einer S7 aus und Triggert unter anderem von 4 Webcams Bilder an die auf einem Laufwerk abgelegt werden.
Starte ich das Script unter dem angemeldeten Benutzer mit "python.exe SCRIPTNAME.PY" oder "pythonw.exe SCRIPTNAME.PY"
läuft es wie es soll. Starte ich es als Dienst so beendet sich das Script nach ein paar Sekunden wieder. Der Dienst wird unter dem
selben Benutzer mit entsprechenden Berechtigungen ausgeführt.

Kann mir vielleicht jemand einen Tipp geben was da verkehrt läuft?
Vielen Dank im vorraus.

Hier das Script:

Code: Alles auswählen

import socket
import time
import os
import urllib2
from thread import start_new_thread
Quelle='10.120.0.2'
Ziel='10.120.0.5'
Cam1='10.110.1.2'
Cam2='10.110.1.3'
Cam3='10.110.1.4'
Cam4='10.110.1.5'
Port=2001
datain='   ' 
dataout='   '

s_udp_sock = socket.socket( socket.AF_INET,  socket.SOCK_DGRAM )
#host = socket.gethostname()
s_udp_sock.bind((Quelle, Port))


print 'Emfange von S7'    
print 'Quelle',Quelle
print 'Port=',Port

 
def empf(): #wartet in einer Endlosschleife auf eingehende Nachrichten.
 
   
 global datain
 while 1:  # Endlosschleife 192.168.1.111
       print 'test'
       datain, addr = s_udp_sock.recvfrom( 1024 ) # Puffergröße: 1024 Bytes
       print datain
       vera()
       
def send(): #sendet Daten    
    global dataout
    global Port
    global Ziel
    print "Nachricht:", dataout 
    s_udp_sock.sendto( dataout, (Ziel,Port) )
    
def druck(Dru):
    global datain
    global dataout
    l=len (datain) - 1
    PDF= datain[1:l+1]
    datain="  " 
    PDFP='C:\\"Program Files (x86)"\Adobe\\"Reader 11.0"\Reader\\acrord32.exe /h /t L:\\'
    druck = PDFP+PDF+".PDF"+" "+Dru
    os.system("taskkill /im AcroRD32.exe")
    os.system("exit")
    os.system(druck)
    
def vera(): #Verarbeitet Daten
    global datain
    global dataout
    if datain[0]=="a":
        l=len (datain) - 1
        buffer = urllib2.urlopen('http://'+Cam1+'/axis-cgi/jpg/image.cgi').read()
        PDF=datain[1:l+1]+"_1.jpg"
        f = open( os.path.join('P:', PDF), "wb" )
        f.write(buffer)
        f.close()
        buffer = urllib2.urlopen('http://'+Cam2+'/axis-cgi/jpg/image.cgi').read()
        PDF=datain[1:l+1]+"_2.jpg"
        f = open( os.path.join('P:', PDF), "wb" )
        f.write(buffer)
        f.close()
        dataout="T"
        send()
    if datain[0]=="b":
        l=len (datain) - 1
        buffer = urllib2.urlopen('http://'+Cam3+'/axis-cgi/jpg/image.cgi').read()
        PDF=datain[1:l+1]+"_3.jpg"
        f = open( os.path.join('P:', PDF), "wb" )
        f.write(buffer)
        f.close()
        buffer = urllib2.urlopen('http://'+Cam4+'/axis-cgi/jpg/image.cgi').read()
        PDF=datain[1:l+1]+"_4.jpg"
        f = open( os.path.join('P:', PDF), "wb" )
        f.write(buffer)
        f.close()
        dataout="T"
        send()
    if datain[0]=="s":
        l=len (datain) - 1
        PDF="L:\\"+datain[1:l+1]+".pdf"
        if os.path.isfile( PDF ): dataout="T"
        else: dataout="F"
        send()
    if datain[0]=="l":
        Dru='"Dr1"'
        druck(Dru)
    if datain[0]=="m":
        Dru='"Dr1ohnelogo"'
        druck(Dru)
    if datain[0]=="n":
        Dru='"Dr2"'
        druck(Dru)
    if datain[0]=="o":
        Dru='"Dr2ohnelogo"'
        druck(Dru)
empf()        
BlackJack

Mittwoch 2. März 2016, 14:29

@c0d4r: So auf Anhieb sehe ich keinen konkreten Fehler aber ein *sehr* schlechtes Programm. Werd diese ganzen ``global``\s los und schreibe echte Funktionen. Werte, ausser Konstanten, betreten eine Funktion oder Methode als Argument und verlassen sie gegebenfalls als Rückgabwert. ``global`` hat in einem sauberen Programm nichts zu suchen. Auf Modulebene sollte nur Code stehen der Konstanten, Funtkionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer `main()`-Funktion. Das hilft auch dabei, dass man nicht aus versehen doch auf globale Variablen zugreifen kann.

Vernünftige Namen, also solche bei denen man nicht raten oder in Kommentaren nachlesen muss was sie bedeuten, wären auch hilfreich zum Verständnis des Programms, und an den Style Guide for Python Code dürfte sich Namensschreibweise und Einrückung auch gerne halten.

Das `thread`-Modul wird importiert aber nicht verwendet. $GOTT sei Dank, denn das sollte man seit Existenz des `threading`-Moduls sowieso nicht mehr tun.

`os.system()` sollte man auch nicht verwenden sondern besser externe Programme mittels `subprocess`-Modul und dort dann ohne eine unnötige Shell starten.

Um den Programmablauf nachvollziehen und eventuell auch Fehler finden zu können, sollte man mindestens Ausnahmen auf der obersten Programmebene protokollieren. In der Standardbibliothek gibt es das `logging`-Modul.
c0d4r
User
Beiträge: 5
Registriert: Mittwoch 2. März 2016, 11:11

Mittwoch 2. März 2016, 14:47

Hi,

erstmal vielen Dank für deine Antwort. Ich kenne mich leider mit Python überhaupt nicht aus. Ich habe nur die Aufgabe es als Dienst zum laufen zu bringen. Dies scheint mir aber dann doch wohl nicht so einfach wie gedacht.
BlackJack

Mittwoch 2. März 2016, 16:46

Das ganze mal etwas sauberer (und ungetestet):

Code: Alles auswählen

import os
import socket
from subprocess import call
from urllib import urlretrieve

SERVER_IP = '10.120.0.2'
PORT = 2001
TARGET_IP = '10.120.0.5'
CAMERA_1_IP = '10.110.1.2'
CAMERA_2_IP = '10.110.1.3'
CAMERA_3_IP = '10.110.1.4'
CAMERA_4_IP = '10.110.1.5'
CAMERA_URL_TEMPLATE = 'http://{0}/axis-cgi/jpg/image.cgi'
IMAGE_PATH = 'P:'
PDF_PATH = 'L:'
TRUE, FALSE = 'T', 'F'
ACROBAT_READER_EXE_PATH = (
    r'C:\Program Files (x86)\Adobe\Reader 11.0\Reader\acrord32.exe'
)


def print_pdf(printer_name, name):
    call(['taskkill', '/im', 'AcroRD32.exe'])
    call(
        [
            ACROBAT_READER_EXE_PATH,
            '/h',
            '/t',
            os.path.join(PDF_PATH, name + '.PDF'),
            printer_name,
        ]
    )


def process_image_retrieval(command, filename):
    command2camera_data = {
        'a': [(CAMERA_1_IP, '_1.jpg'), (CAMERA_2_IP, '_2.jpg')],
        'b': [(CAMERA_3_IP, '_3.jpg'), (CAMERA_4_IP, '_4.jpg')],
    }
    for camera_ip, filename_suffix in command2camera_data[command]:
        urlretrieve(
            CAMERA_URL_TEMPLATE.format(camera_ip),
            os.path.join(IMAGE_PATH, filename + filename_suffix)
        )
    return TRUE


def process_pdf_check(_command, name):
    return (
        TRUE if os.path.isfile(os.path.join(PDF_PATH, name + '.pdf')) else FALSE
    )


def process_print_command(command, name):
    command2printer_name = {
        'l': 'Dr1',
        'm': 'Dr1ohnelogo',
        'n': 'Dr2',
        'o': 'Dr2ohnelogo',
    }
    print_pdf(command2printer_name[command], name)
    return None


COMMAND_TO_FUNCTION = {
    'a': process_image_retrieval,
    'b': process_image_retrieval,
    's': process_pdf_check,
    'l': process_print_command,
    'm': process_print_command,
    'n': process_print_command,
    'o': process_print_command,
}


def process_packet(packet):
    command, data = packet[0], packet[1:]
    process_func = COMMAND_TO_FUNCTION.get(command)
    if process_func:
        return process_func(command, data)
    else:
        print 'Unbekanntes Kommando: {0!r}'.format(command)
        return None


def main():
    udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    udp_socket.bind((SERVER_IP, PORT))
    print 'Empfange von S7'   
    print 'Quelle', SERVER_IP
    print 'Port=', PORT
    while True:
        packet, _ = udp_socket.recvfrom(1024)
        print packet
        response = process_packet(packet)
        if response:
            print 'Nachricht:', response
            udp_socket.sendto(response, (TARGET_IP, PORT))


if __name__ == '__main__':
    main()
c0d4r
User
Beiträge: 5
Registriert: Mittwoch 2. März 2016, 11:11

Donnerstag 3. März 2016, 07:08

Wow super, ich danke dir vielmals für deine Mühe. Ich werde es nachher gleich mal testen ob es als Dienst läuft.
BlackJack

Donnerstag 3. März 2016, 10:27

@c0d4r: Das wird sehr wahrscheinlich das gleiche Problem haben wie das Original, vorausgesetzt ich habe keinen Fehler gemacht und neue Probleme eingebaut, denn es ist ja äquivalent zum Original, nur halt sauberer und kürzer, weil ohne globale Variablen und Codewiederholungen.
c0d4r
User
Beiträge: 5
Registriert: Mittwoch 2. März 2016, 11:11

Donnerstag 3. März 2016, 11:51

Kann es sein, dass er als Dienst wegen der Schleife auf einen Timeout läuft? Bzw. die Schleife nicht richtig abgearbeitet wird?
Das Script/der Dienst läuft solange, bis er das erste mal angetriggert wird. Danach beendet er sich.
BlackJack

Donnerstag 3. März 2016, 12:29

@c0d4r: Wohin gehen eigentlich Ausgaben bei einem Windows-Dienst? Darf der welche haben? Gibt es Unterschiede zwischen der Standardausgabe und der Standardfehlerausgabe?

Wie schon gesagt, ich würde da mal Logging einbauen, so dass man nachvollziehen kann was passiert.

Und dann vielleicht eher eine Windows-Frage als eine Python-Frage: Wie versuchst Du das überhaupt als Dienst laufen zu lassen? Müssen Programme die als Dienst laufen sollen vielleicht irgendwelche Bedingungen erfüllen? Sich registrieren, auf irgendwie geartete Signale reagieren, so was halt.
c0d4r
User
Beiträge: 5
Registriert: Mittwoch 2. März 2016, 11:11

Donnerstag 3. März 2016, 12:43

Das Script soll eigentlich nur das im Hintergrund machen was es in der Box im Usercontext auch tut.
Die Fehlerausgaben sehe ich ja nicht wenn es als Dienst läuft. Ich registriere die python.exe mit dem script als Parameter mit dem
Befehl "sc create". Er wird auch sauber angelegt.

Blöde frage aber wie baue ich ein logging ein? Vielleicht kommen wir dem so ja auf die Spur. Könntest du mir dabei helfen? Das wäre natürlich spitze.
BlackJack

Montag 7. März 2016, 18:26

@c0d4r: Nach kurzer Recherche müssen Windowsdienste bestimmte Eigenschaften haben, das heisst sich beim Betriebssystem als Dienst registrieren und auf Anfragen vom System reagieren sowie einen Namen und eine Beschreibung liefern. Das was unter Dienste bei Windows angezeigt wird. Da gibt es zum einen wohl ein Werkzeug von Microsoft mit dem man jede beliebige ausführbare Datei ”wrappen” kann (srvany.exe), zum anderen kann man mit dem entsprechenden Modulen auch in Python Windowsdienste implementieren. Einfach mal eine Suchmaschine bemühen.
Antworten