Twister : Server wieder stoppen

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
MaticPeel
User
Beiträge: 13
Registriert: Donnerstag 1. Februar 2007, 12:45

Hallo,
habe eben mal Twisted ausprobiert und soweit funktioniert es auch.

Code: Alles auswählen

from twisted.protocols import basic
from twisted.internet import reactor, protocol
 
 
class MyReceiver(basic.LineReceiver):
    delimiter = '\0'
 
    def connectionMade(self):
      print "Got new client!"
      self.factory.clients.append(self)

    def connectionLost(self, reason):
      print "Lost a client!"
      self.factory.clients.remove(self)
 
   
    def lineReceived(self,line):
        print line
                     

class XMLSocket(protocol.Factory):
    clients=[]

    def __init__(self, protocol=None):
      self.protocol=protocol
 
       

def main():
    reactor.listenTCP(8002, XMLSocket(MyReceiver))
    reactor.run()


if __name__ == '__main__':
    main()
Allerdings frage ich mich, wie ich nun den "Server"
auch wieder stoppen kann.



Wenn einmal eine listenTPC-Verbindung aufgebaut
wurde besteht die anscheinend auch nach Beendigung
des Programms, weil dann folgender Fehler kommt
twisted.internet.error.CannotListenError: Couldn't listen on any:8002: (10048, 'Address already in use').
Benutzeravatar
Masaru
User
Beiträge: 425
Registriert: Mittwoch 4. August 2004, 22:17

Eigentlich recht merkwürdig .. unter Windows funktioniert es bei mir problemlos. Wenn das Script beendet wird, wird auch das reactor Objekt dadurch terminiert.

Welche Version von Twisted verwendest Du denn?

Ich meine, es gab mal Schwierigkeiten bezüglich des twisted.internet.error.CannotListenErrors, die aber gefixed wurden.

Gruß,
>>Masaru<<
MaticPeel
User
Beiträge: 13
Registriert: Donnerstag 1. Februar 2007, 12:45

Wenn ich es über die Konsole ausführe und diese dann schließe ist die Verbindung auch weg. Allerdings möchte ich das ganze in eine GUI packen
und dort funktioniert das irgendwie nicht so, wie ich mir das denke.


Code: Alles auswählen

from Tkinter import *
from twisted.internet import tksupport
from twisted.protocols import basic
from twisted.internet import reactor, protocol


class MyReceiver(basic.LineReceiver):
    delimiter = '\0'

    def connectionMade(self):
      print "Got new client!"
      self.factory.clients.append(self)

    def connectionLost(self, reason):
      print "Lost a client!"
      self.factory.clients.remove(self)


root = Tk()

# Install the Reactor support
tksupport.install(root)


class XMLSocket(protocol.Factory):
    clients=[]

    def __init__(self, protocol=None):
      self.protocol=protocol

class App:

    def __init__(self, master):
        frame = Frame(master)
        frame.pack()

        self.button = Button(frame, text="START", command=self.starten)
        self.button.pack(side=LEFT)

        self.hi_there = Button(frame, text="STOP", command=self.beenden)
        self.hi_there.pack(side=LEFT)

        self.hi_there = Button(frame, text="CLOSE", command=frame.quit  )
        self.hi_there.pack(side=LEFT)

    def starten(self):
        print "start"
        reactor.listenTCP(8013, XMLSocket(MyReceiver))
        reactor.run()

    def beenden(self):
        print "stop!"
        reactor.stop()

app = App(root)
root.mainloop()
Wenn man aber reactor.stop() auführt, dann hält er nicht wirklich an, weil dann immer noch eine Verbindung zu dem Port möglich ist. Und wenn man das Programm erneut startet, dann erscheint wieder die Fehlermeldung.
Benutzeravatar
Masaru
User
Beiträge: 425
Registriert: Mittwoch 4. August 2004, 22:17

Köntest Du es nicht in einem Thread starten und dann im Falle von "beenden" über die GUI diesen einfach abschießen?
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Masaru hat geschrieben:Köntest Du es nicht in einem Thread starten und dann im Falle von "beenden" über die GUI diesen einfach abschießen?
Man kann Threads nicht von außen beenden, sie beenden sich, wenn sie fertig sind.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Benutzeravatar
Masaru
User
Beiträge: 425
Registriert: Mittwoch 4. August 2004, 22:17

Man kann Threads von aussen schon dazu bewegen, sich zu beenden.

Nur ein kleines Beispiel:

Code: Alles auswählen

>>> import threading

>>> e = threading.Event()
>>> def do_loop(event):
... 	while not event.isSet():
... 		pass
... 	
>>> t = threading.Thread(target=do_loop, args=(e,))
>>> t.start()
>>> t.isAlive()
True
>>> e.set()
>>> t.isAlive()
False
Vielleicht lassen sie sich (mit den regulär mitgelieferten Python Möglichkeiten) nicht direkt mittels eine Process ID killen/crashen, aber getriggert beenden funktioniert bedingt schon Leonidas ;).

In diesem Falle jedoch würde ein solcher Ansatz dennoch nicht funktionieren, da zum "reactor.run()" dafür sorgt, dass man keine weitere Ablaufkontrolle im Anschluss hat (es läuft und läuft und läuft) und zum anderen twisted mit SINGALs jongliert, die leider nur im MainThread funktionieren :(.

Code: Alles auswählen

>>> import signal
>>> import threading

>>> def do_signal():
... 	signal.signal(signal.SIGINT, signal.SIG_DFL)
... 	
>>> t = threading.Thread(target=do_signal)
>>> t.start()
Exception in thread Thread-1:Traceback (most recent call last):
  File "c:\_PYTHON_\python23\lib\threading.py", line 442, in __bootstrap
>>>     self.run()
  File "c:\_PYTHON_\python23\lib\threading.py", line 422, in run
    self.__target(*self.__args, **self.__kwargs)
  File "<interactive input>", line 2, in do_signal
ValueError: signal only works in main thread
Vergessen wir also lieber threads an dieser Stelle.

Eine "Quick'n'Dirty"-Lösung, die mir aber noch einfällt, wäre den reactor Part zu einem separaten Programm/Script umzudesignen, und die "Oberfläche" dieses dann als Prozess aufrufen zu lassen.

Man könnte dann z.B. das Script entweder mit win32process.TerminateProcess (Windows) oder os.kill (Unix) kill .. *hust* *hust* ... beenden.

>>Masaru<<
Zuletzt geändert von Masaru am Dienstag 27. Februar 2007, 13:15, insgesamt 1-mal geändert.
Benutzeravatar
Masaru
User
Beiträge: 425
Registriert: Mittwoch 4. August 2004, 22:17

Beispiel mit Python 2.4+ (wegen dem subprocessModul)

Struktur:

Code: Alles auswählen

\myapp\+ mygui.py      #<- GUI selbst und Startlokation
       | 
       + myreactor.py  #<- hier das stand-alone script des reactors
       | 
       + logs\         #<- Macht der Gewohnheit, ein Verzeichniss für logs

Ausgelagerter reactor (myreactor.py):

Code: Alles auswählen

#! /usr/bin/env python

import sys # <- neu hinzugekommen

import twisted.internet.error # <-neu hinzugekommen

from twisted.protocols import basic
from twisted.internet import reactor, protocol

class MyReceiver(basic.LineReceiver):
    """Receiver Class"""
    delimiter = '\0'

    def connectionMade(self):
      print "Got new client!"
      self.factory.clients.append(self)

    def connectionLost(self, reason):
      print "Lost a client!"
      self.factory.clients.remove(self)

    def lineReceived(self,line):
        print line


class XMLSocket(protocol.Factory):
    """Protocol Class"""
    clients=[]

    def __init__(self, protocol=None):
      self.protocol=protocol


def main():
    port = 8002   # <- quasi ein default
    if len(sys.argv) >= 2:
        try:
            port = int(sys.argv[1])  
        except ValueError: # <- damit das Script nicht abkachelt
            # wenn der erwartete Port Parameter doch kein Integer Wert ist
            pass

        
    # mit dem Modul "optparse" lässt sich natürlich ein schöneres
    # 'command option' handling noch zaubern

    exit_code = -1
    try:
        reactor.listenTCP(port, XMLSocket(MyReceiver))
        reactor.run()
        exit_code = 0
    except twisted.internet.error.CannotListenError:
        exit_code = 42   # <- um anzuzeigen, dass der Socket eben nicht gebunden werden kann (code ist natürlich frei wählbar)
    except Exception, err:
        # hier besteht Möglichkeit Fehler gut zu loggen
        exit_code = 1
    sys.exit(exit_code)

if __name__ == '__main__':
    main()
Das Hauptscript (mygui.py) müsste dann noch angepasst werden:

Code: Alles auswählen

#! /usr/bin/env python 

import os
import subprocess

from Tkinter import *
from twisted.internet import tksupport

if os.name == 'nt':
    import win32api
    import win32process
    
DEFAULT_REACTOR_PORT = 8002

class App:
    """Application Class"""
    
    def __init__(self, master, base_dir):
        """Class intialization"""
        self.__reactor_process = None
        self.__reactor_script = os.path.join(base_dir, 'myreactor.py')
        if not os.path.isfile(self.__reactor_script):
            raise EnvironmentError("Reactor not found: %s" % self.__reactor_script)
            # oder andersweitig reagieren
            
        frame = Frame(master)
        frame.pack()

        self.button = Button(frame, text="START", command=self.starten)
        self.button.pack(side=LEFT)

        self.hi_there = Button(frame, text="STOP", command=self.beenden)
        self.hi_there.pack(side=LEFT)

        self.hi_there = Button(frame, text="CLOSE", command=frame.quit  )
        self.hi_there.pack(side=LEFT)
        
        
    def __is_reactor_active(self)
        if not self.__reactor_process:
            return False
        if self.__reactor_process.poll() == None:
            return True
        return False
    is_reactor_active = property(fget=__is_reactor_active)
        
    
    def starten(self):
        """Starten des externen Reactor Scriptes"""
        if self.is_reactor_active:
            return = None

        try:
            self.__reactor_process = \
                subprocess.Popen([self.__reactor_script, str(port)], shell=True)
        except Exception, err:
            # Fehler loggen oder zurückgeben
            pass
        
        return self.is_reactor_active

        
    def beenden(self):
        """Beenden des externen Reactor Scriptes"""
        if not self.is_reactor_active:
            return None
            
        pid = self.__reactor_process.pid
        reactor_exit_code = 0
        
        if os.name == 'posix':
            try:
                os.kill(pid, reactor_exit_code)
            except:
                # Fehler loggen oder zurückgeben
                return False
        elif os.name == 'nt':
            try:
                handle = win32api.OpenProcess(1,0,pid)
                win32process.TerminateProcess(handle, reactor_exit_code)
            except:
                # Fehler loggen oder zurückgeben
                return False
        else:
            raise NotImplementedError 

        self.__reactor_process = None

        return self.is_reactor_active
                

def main()
    root = Tk()
    tksupport.install(root)

    base_dir = os.path.dirname(__file__)
        
    app = App(root, base_dir)
    root.mainloop()

if __name__ = '__main__':
    main()
... und mit ein paar Bugfixes *g* (denn mein Code ist nicht getestet) sollte es eigentlich zum Laufen zu bringen sein.

>>Masaru<<
Zuletzt geändert von Masaru am Mittwoch 28. Februar 2007, 01:41, insgesamt 4-mal geändert.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Masaru hat geschrieben:Vielleicht lassen sie sich (mit den regulär mitgelieferten Python Möglichkeiten) nicht direkt mittels eine Process ID killen/crashen, aber getriggert beenden funktioniert bedingt schon Leonidas ;).
Das ist mir schon klar - dann sind sie aber immer noch fertig wenn sie fertig sind - also wenn der Event getriggert wird, aber das beschleunigt ja sowieso nur den Zeitpunkt an dem sie fertig werden. Wenn der Event eintritt sind sie fertig. Aber wenn der Thread sich festfährt (rechenintensive Operation etc.) dann hilft das auch nicht.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Benutzeravatar
Masaru
User
Beiträge: 425
Registriert: Mittwoch 4. August 2004, 22:17

Leonidas hat geschrieben:Aber wenn der Thread sich festfährt (rechenintensive Operation etc.) dann hilft das auch nicht.
Das stimmt .. leider gibt es noch kein Framework in Python, welches dennoch ein "Abschießen von (solchen) Threads" ermöglichen würde.

Was für ein Glück sprach ich zuletzt auch lediglich von einem "bedingtem beenden" ;).
Antworten