kleiner TCP-Server mit SocketServer?

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Lonestar
User
Beiträge: 147
Registriert: Samstag 9. August 2008, 08:31

Sonntag 17. August 2008, 11:48

Hi, ich bin noch ziemlicher Python Anfänger, ich versuche mich seit knapp 3 Wochen ein wenig einzuarbeiten... als etwas größeres Projekt habe ich mir vorgenommen einen kleinen Server aufzusetzen, der von meinem Client Verbindungen annimmt, und die empfangenen Daten via Queue an ein anderes Modul weitergibt. Da ich mich zu Anfang nicht groß mit irgendwelchen Protokollen ärgern wollte dachte ich mich ich nehme TCP-IP, da ich dort ein wenig Vorkenntnisse durch meinen Cisco-Kurs habe, und ich dachte das es da auch genug Dokumentationen geben sollte.
Um die Kommunikation einfach zu halten sendet mein Cient immer eine Zeile, die dann auch vom Server eingelesen werden und weitergegeben werden soll.
Im Netz habe ich HIER einen minimalen Server gefunden.
Der Sever lief auch von Anfang an. Nachdem ich hier im Forum auch noch darauf aufmerksam gemacht wurde das ich den Server in einem eingenem Thread laufen lassen müsste um paralel mein Hauptprogramm laufen zu lassen konnte ich auch endlich mein kleines Programm vor den Server setzen, das die eigentliche Arbeit machen sollte.
Jetzt wo es ein wenig aufwendiger bei mir werden sollte soll der Server den eingehenden Stream nach einem Label durchsuchen, und das Label mit einem Zusatz an den Client zurücksenden. Auch das läuft soweit. Am besten poste ich hier mal meinen erweiterten minimalen Server:

Code: Alles auswählen


import SocketServer
from threading import *
import Queue

PORT = 9999

class Service(SocketServer.StreamRequestHandler):
        #queue wird nach erzeugen der Klasse als Varianble 'WarteschlangeRaus' angef�gt
        
    def ParseLabel(self, daten):
        """Das Label Heraussuchen, mit dem Hinweis ack versehen
        und als String zur Empfangsbestätigung zurückgeben"""
        Labelende = daten.find("]") +1 
        Label = daten[:Labelende]
        ack = Label +"ack\r\n"
        return(ack)

    def AufgabeAnClientSenden(self):
        """von P900App ausgehende Aufgaben abarbeiten"""
        print"nun sollte die ausgehende Warteschlange abgearbeitet werden"
        if self.ServerWarteschlangeAusgehend.empty() == False:
            return self.ServerWarteschlangeAusgehend.get()
        else:
            return "[ClientBored]NONE\r\n"
        
    def handle(self):
        """Eigener TCP- Handler für Serverklasse"""
        #auf übertragene Daten warten
        data = self.rfile.readline()
        
        #Nachrichtentyp herausfinden und client Accnowledge senden
        LabelAcc = self.ParseLabel(data)
        if LabelAcc <> "[ClientBored]ack\r\n":
            self.wfile.write(LabelAcc)
            #daten per queue an die P900App schicken
            self.ServerWarteschlangeEingehend.put(data)
            #client mit in die Queue schreiben und an die P900App übergeben
            client = "[TCPClient]" + self.client_address[0]
            self.ServerWarteschlangeEingehend.put(client)
            
            #wenn Clientanfrage nach Aufgaben kam -> nachsehen ob Aufgaben vorhanden sind
        else:
            ClientAufgabe = self.AufgabeAnClientSenden()
            self.wfile.write(ClientAufgabe)
            #client in die Queue schreiben und an die P900App übergeben
            client = "[TCPClient]" + self.client_address[0]
            self.ServerWarteschlangeEingehend.put(client)
            
class Server(Thread):
    """Serverklasse """
    def __init__(self, notify_window, ServerWarteschlangeEingehend, ServerWarteschlangeAusgehend):
        """Init Worker Thread Class."""
        Thread.__init__(self)
        
        #Warteschlangen für den Server
        self.ServerWarteschlangeEingehend = ServerWarteschlangeEingehend
        self.ServerWarteschlangeAusgehend = ServerWarteschlangeAusgehend
        
        #beim initieren gibt es noch keinen dispacher und service
        self.dispatcher = None
        self.HTTPService = None
        
        #bei schliessen des Main-Threads auch Server beenden
        self.setDaemon(True)

        self.start()

    def run(self):
        """TCPServer auf PORT starten"""
        self.HTTPService = Service
        #Queue für externe Daten die an die App gehen an den Service weitergeben
        self.HTTPService.ServerWarteschlangeEingehend = self.ServerWarteschlangeEingehend
        #Queue für interne Daten die an den Client gehen an den Service weitergeben
        self.HTTPService.ServerWarteschlangeAusgehend = self.ServerWarteschlangeAusgehend
        
        self.dispatcher = SocketServer.TCPServer(('', PORT), self.HTTPService)
        #server starten
        self.dispatcher.serve_forever()
    
Zunächst mal vorweg - ich weiss das ich ein wenig lange Variablennamen für die Warteschlangen genommen habe - die sollten wenn der Server fertig ist geändert werden. Auch weiss ich das ich den Server noch nicht sauber beenden kann. Vor allem das initialisieren mit der Methode run gefällt mir persönlich nicht.... ach ja, aber warum soll ich den code in Ordnung bringen, wenn ich nicht weiss ob ich mein Hauptproblem überhaupt lösen kann :roll:

Nun fal fix zu meinem eigentlichen Problem:
Wenn mein Client sich mit dem Server verbindet läuft die Kommunikation wie erwartet ab - das Label in eckigen Klammern wird aus dem eingehendem Datensatz rausgesucht, und mit einem 'ack' versehen an den client zurückgesendet. Versucht der Client aber nachdem er vom Server den Datensatz mit dem 'ack' empfangen hat noch einmal über den selben stream Daten zu senden, dann versucht er dies vergebens. Der Server akzeptiert über den Stream keine Daten mehr. Das einzige was mir bleibt ist für jede Clientanfrage einen neuen Stream auf einem neuen Port zu erstellen. Da ich mittlerweile alle 2-5 Sekunden eine Serveranfrage generiere sieht das schon ganz interessant aus. Auch habe ich beim programmieren des Client leichte Probleme da ich andauernd einen neuen Stream generieren muss.

Wie erstelle ich einen Server, der den Stream offen hält, und weitere Kommunikation über diesen unterstützt? Muss ich einen völlig anderen Server benutzen, oder kann man meine kleine Variante dahin ausbauen? Und vor allem woran liegt es denn das ich keine Daten mehr empfange? Ich vermute das hat etwas damit zu tun, das der eigentliche Server auf port 9999 wartet und nur von dort Berbindungen entgegennimmt. Aber momentan bin ich etwas überfragt

Schon einmal danke an alle die bis hier hin mit dem lesen gekommen sind

mfg
Sebastian
Benutzeravatar
Trundle
User
Beiträge: 591
Registriert: Dienstag 3. Juli 2007, 16:45

Sonntag 17. August 2008, 16:59

`handle` wird nur einmal aufgerufen, nämlich dann, wenn die Verbindung akzeptiert wurde. Wenn `handle` dann verlassen wird, wird auch der Socket geschlossen. Soll dein Server also mehrere Befehle vom Clienten behandeln können, musst du eine Schleife in deine `handle`-Methode einbauen, in der ein Befehl gelesen und behandelt wird, danach (beim nächsten Schleifendurchlauf) der nächste Befehl gelesen wird, etc.
"Der Dumme erwartet viel. Der Denkende sagt wenig." ("Herr Keuner" -- Bertolt Brecht)
Lonestar
User
Beiträge: 147
Registriert: Samstag 9. August 2008, 08:31

Sonntag 17. August 2008, 23:18

Hey Trundle, vielen Dank für den Hinweis. Genau das wars. Da hätte ich aber eigentlich auch mal irgendwie selber draufkommen können. Ich habe jetzt eine Endlosschleife eingebaut die erst unterbrochen wird wenn mir 'self.rfile.readline()' leere Zeilen übergibt. Als ich das zu Anfang nicht gemacht hatte hatte sich Pyhton bei mir aufgehängt :roll: muss ja auch mal sein.

Wo ich schon meinen fiesen Code hier gepostet habe schmeisse ich einfach noch einmal eine Frage dazu:

Ich rufe ja in der Methode 'RUN' der Server-Klasse beim instanziieren das self.HTTPService - als Objekt vom Typ Server auf. (Ich hoffe mal da hab ich mich gerade nicht verhaspelt). Wie man sieht übergebe ich, nachdem ich das Objekt erstellt habe die Queues (meine ewig langen Warteschlangen) an das HTTPService - Objekt. Das ist natürlich nicht wirklich sauber das weiss ich selber. Wie mache ichs richtig? Normalerweise ja über den Konstruktor soweit ich das in der Doku gesehen habe soll ich den aber nicht überladen.
http://docs.python.org/lib/node634.html
hier wird vorgeschlagen das über die Methode setup() zu machen. Mache ich das, meckert Python aber rum das meine Service-Klasse kein Readfile mehr beherscht.

Code: Alles auswählen

----------------------------------------Exception happened during processing of request from ('192.168.68.1', 4153)
----------------------------------------
Traceback (most recent call last):
 File "E:\Programme\Python2_5\lib\SocketServer.py", line 222, in handle_request
self.process_request(request, client_address)
 File "E:\Programme\Python2_5\lib\SocketServer.py", line 241, in process_request
self.finish_request(request, client_address)
 File "E:\Programme\Python2_5\lib\SocketServer.py", line 254, in finish_request
self.RequestHandlerClass(request, client_address, self)
 File "E:\Programme\Python2_5\lib\SocketServer.py", line 522, in __init__
self.handle()
 File "E:\Eigene Dateien\Projekte\Client-ServerP900\Working\Python\TCPServer.py", line 57, in handle
data = self.rfile.readline()
AttributeError: Service instance has no attribute 'rfile'
sobald ich die Methode setup() rausnehme macht mein Server wieder genau das was er soll. Habe ich hier was übersehen oder falsch verstanden?
BlackJack

Montag 18. August 2008, 03:10

Ohne Quelltext ist da schwer was zu sagen. Ich würde vermuten, dass die Doku lügt und die `setup()` vom `StreamRequestHandler` doch etwas tut und Du die in der überschriebenen Methode nicht aufrufst!?
Lonestar
User
Beiträge: 147
Registriert: Samstag 9. August 2008, 08:31

Montag 18. August 2008, 08:46

Moin, die Doku ist glaube ich einfach nich ausführlich genug gewesen. Sie spricht allgegmein von RequestHandler- Objekten. Teilweise haben die auch nichts in der setup() stehen. Mein StreamRequestHandler hat jedenfalls noch ein paar Aufrufe gehabt. Erst mal danke für den Tip mit dem Quellcode.

Code: Alles auswählen

class StreamRequestHandler(BaseRequestHandler):

    """Define self.rfile and self.wfile for stream sockets."""

    # Default buffer sizes for rfile, wfile.
    # We default rfile to buffered because otherwise it could be
    # really slow for large data (a getc() call per byte); we make
    # wfile unbuffered because (a) often after a write() we want to
    # read and we need to flush the line; (b) big writes to unbuffered
    # files are typically optimized by stdio even when big reads
    # aren't.
    rbufsize = -1
    wbufsize = 0

    def setup(self):
        self.connection = self.request
        self.rfile = self.connection.makefile('rb', self.rbufsize)
        self.wfile = self.connection.makefile('wb', self.wbufsize)

    def finish(self):
        if not self.wfile.closed:
            self.wfile.flush()
        self.wfile.close()
        self.rfile.close()
Wenn ich den Code in meine setup- Methode mit übernehme dann läufts.

Allerdings bin ich meinem Ziel den Code ordentlich zu machen noch nicht wirklich viel weiter. Nun kann ich zwar in der setup- Methode meine Queues erst mal definieren, aber übergeben bekomme ich sie ja immer noch nach dem Instanziieren in der Methode run aus der Serverklasse. Gibt es da noch Möglichkeiten, die ich als Anfänger noch nicht kenne? Oder ist das schon Ordentlich genug für einen vernünftigen Python-Code? Als nächstes würde ich mich dann an meine beiden Methoden und einen auferäumteren handler machen...
Lonestar
User
Beiträge: 147
Registriert: Samstag 9. August 2008, 08:31

Donnerstag 21. August 2008, 14:54

Hey, ich bins mal wieder.
Ich bin zwar mit meiner Übergabe der Queues noch nich wirklich weiter, aber da das für mich erst mal nur ein Schönheitsfehler ist lasse ich das bis ich mal über eine Lösung stolper :roll: . Da ich bei Python noch ziemlich am Anfang stehe lerne ich ja sowieso andauernd dazu.

Momentan habe ich bei obigem Server - der mittlerweile ein klein wenig aufgeräumter aussieht :? ein anderes Problem festgestellt. Zwischenzeitlich öffnet der Server mehrere HTTPService parralel. An sich wäre das nicht das Problem, nur werden diese nicht mehr geschlossen. Erst wenn ich meinen Server beende wird das alles wieder aufgefäumt (ich hab mir das mit TCP-View angeguckt).
Warum genau mehrere Services gleichzeitig geöffnet werden kann ich noch nich genau reproduzieren. Allerdings dachte ich an eine zwischenzeitliche Lösung dadurch das ich beim Einlesen von Daten aus dem Stream via

data = self.rfile.readline()

einen toten Stream mit einem Timeout erkennen könnte. Nur leider suche ich nu schon ne ganze zeit nach einer Möglichkeit sowas zu realisieren. Das FileObject das ich benutze gibt mir die Möglichkeit nicht soeit ich das sehen kann (http://docs.python.org/lib/bltin-file-objects.html). Gibt es vielleicht eine einfache Variante selbst ein Timeout zu basteln? Ich bin da für jede Anregung dankbar.
Benutzeravatar
Trundle
User
Beiträge: 591
Registriert: Dienstag 3. Juli 2007, 16:45

Donnerstag 21. August 2008, 15:13

In deiner `setup`-Methode, nach dem ``self.connection = self.request`` einfach ``self.connection.settimeout(60)`` einbauen, mit deinem gewünschten Timeout eben. Dann wird eine `socket.timeout`-Ausnahme geworfen, wenn in der gewünschten Zeit keine Daten kommen.

Außerdem kannst du in deiner `setup`-Methode einfach die `setup`-Methode der Klasse, von der du erbst, aufrufen, anstatt den Inhalt zu kopieren. Geht mit ``StreamRequestHandler.setup(self)``.
"Der Dumme erwartet viel. Der Denkende sagt wenig." ("Herr Keuner" -- Bertolt Brecht)
Lonestar
User
Beiträge: 147
Registriert: Samstag 9. August 2008, 08:31

Donnerstag 21. August 2008, 17:13

Hi, danke für die schnelle Hilfe. Mit dem timeout scheint das nu so zu laufen wie ich mir das dachte. Zumindest habe ich bislang keine überflüssigen offenen Ports mehr von meinem Server finden können. Ich hoffe mal ich hab das ganze richtig eingebaut. Ich stelle oben im Setup wie du sagtest mein timout für die Verbindung ein. Ein Timeout fange ich dann per Exception auf:

Code: Alles auswählen

            #auf übertragene Daten warten - timeout abfangen
            try:
                data = self.rfile.readline()
            except: 
                #bei Fehler stream schliessen
                data = ""
das mit dem leeren Datenstring ergibt sich bei mir dadurch das ich so auch einen Verbindungsabbruch vom Client so abfange.

Oder spricht irgendwas dagegen das mit Exceptions zu machen?

die Setup-Methode vom requesthandler aufzurufen geht bei mir irgendwie schief. Momentan siehts bei mir so aus:

Code: Alles auswählen

class Service(SocketServer.StreamRequestHandler):

def setup(self):
           
        self.connection = self.request
        self.connection.settimeout(30)
        self.rfile = self.connection.makefile('rb', -1)
        self.wfile = self.connection.makefile('wb', 0)
wenn ich nun einfach den ganzen Block durch

StreamRequestHandler.setup(self)

ersetzte bekomme ich als Fehlermeldung:
AttributeError: Service instance has no attribute 'StreamRequestHandler'

das ich die paar Zeilen übernehmen musste stört mich zwar wenig, aber ich werd mir das noch mal genauer ansehen müssen wie ich so eine Methode sauber überschreibe (ich hoffe mal das das ein einfaches Überschreiben der Methode ist, oder nicht?)
DasIch
User
Beiträge: 2452
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Donnerstag 21. August 2008, 17:30

Ein reines try except ist sicherlich keine gute Idee ;)
Benutzeravatar
Trundle
User
Beiträge: 591
Registriert: Dienstag 3. Juli 2007, 16:45

Donnerstag 21. August 2008, 17:36

Wie DasIch schon schrieb, sollte bei dem try..except.. nur eine `socket.timeout`-Ausnahme abgefangen werden und nicht jede.

Und deine Service-Klasse könnte (ungetestet) so aussehen:

Code: Alles auswählen

class Service(SocketServer.StreamRequestHandler):
    def setup(self):
        self.request.settimeout(30)
        SocketServer.StreamRequestHandler.setup(self)
"Der Dumme erwartet viel. Der Denkende sagt wenig." ("Herr Keuner" -- Bertolt Brecht)
Lonestar
User
Beiträge: 147
Registriert: Samstag 9. August 2008, 08:31

Donnerstag 21. August 2008, 18:18

oh mann, ich war mir sicher die Setupvariante mit

SocketServer.StreamRequestHandler.setup(self)

auch getestet zu haben - so kann man sich täuschen. Gut das ich das wenigstens nicht behauptet habe :wink:
Nu läufts - Supersache, wenn ich so weitermache wird der Code auf dauer noch überschaubarer.

Aber nochmal zurück zu dem except:
Ich dachte zuerst auch das ich am besten nur auf ein timeout reagiere, dann ist mir aber aufgefallen das es mir doch im Prinzip egal ist was für ein Fehler auftritt, sobald etwas in meinem Server nicht so läuft wie ich es vorgesehen habe wird dadurch mein Stream geschlossen. Von nun an wird doch sobald ein Fehler auftritt meine Verbindung direkt erst mal geschlossen, ohne das die evtl. eingegangenen Daten verarbeitet werden.
Danach ist alles wieder gut, und der Server wartet auf neue eingehende Verbindungen auf seinem Port.

Zur Fehlersuche wäre es evtl. besser wenn ich mir den aufgetretenen Fehler genauer anzeigen lassen könnte, aber sonst sehe ich da keinen Nachteil. Habe ich da etwas übersehen? Oder sehe ich das ganze gerade einfach zu blauägig?
DasIch
User
Beiträge: 2452
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Donnerstag 21. August 2008, 19:03

Lonestar hat geschrieben:[...]sobald etwas in meinem Server nicht so läuft wie ich es vorgesehen habe wird dadurch mein Stream geschlossen[...]
Dafür solltest du ein try..finally Konstrukt oder das with statement aber kein allgemeines except.
Lonestar
User
Beiträge: 147
Registriert: Samstag 9. August 2008, 08:31

Freitag 22. August 2008, 11:19

Hi, das mann nicht einfach alle Fehler mit except abfangen soll, das hab ich auch schon mal gehört. Normalerweise versuche ich auch nur Fehler abzufangen die ich Vorhergesehen habe, und bearbeiten kann. Aber bei meinem Streamhandler verhält sich das ganze anders als ich das bislang kannte (sicher hab ich in solchen Sachen auch nicht die größte Erfahrung). Ich habs mal nach euren Vorschlägen geändert. Momentan fange ich, wenn ichs richtig verstanden habe nur ein 'socket.timeout' ab. Alle anderen Fehler sollten das Modul anhalten, richtig?
Der Einfachheit halber mal eine Gekürzte Version meins handlers:

Code: Alles auswählen

    def handle(self):
        """
        Eigener TCP- Handler für Serverklasse
        """
        
        while  self.StreamOffen:
            
            #auf übertragene Daten warten - timeout abfangen
            try:
                data = self.rfile.readline()
            
            #bei bei Timeout stream schliessen
            except socket.timeout:   
                data = ""
            
            #wenn Leerzeilen eingelesen werden wurde Verbindung unterbrochen
            if data == "":
                self.StreamOffen = False
                print "Debug- TCP-Handle leere Daten gelesen"
                self.QueueIn.put("[MSG]Verbindung mit Client unterbrochen  \n")
                
                
            else:
                #print "debug TCP-Handler: Got  bytes from s" , self.client_address
                print "debug TCP-Handler: Nachricht: ", data
   
        
        print "stream wird geschlossen..."
Die vorgeschlagene Variante mit einem try - finally macht für mich keinen Sinn, da ich dann doch in jedem Fall den Code der nach Finally kommt noch ausführe, auch wenn kein Fehler auftritt - oder habe ich das falsch verstanden?

Trotzdem wüsste ich ganz gerne was nun genau in meinem Fall der Nachteil wäre wenn ich ein allgemeines except einsetze. Denn wenn irgendein Fehler auftritt wird doch so wie ich das sehe mein handler auch beendet - ich schreibe in data einen leeren String, und darauf steigt der handler aus der while- Schleife aus. Damit ist mein Modul abgearbeitet, und zu Ende. Über eine kleine Aufklärung würde ich mich schon freuen :roll:

bis hierhin schon mal besten Dank für eure Hilfe
Benutzeravatar
Rebecca
User
Beiträge: 1662
Registriert: Freitag 3. Februar 2006, 12:28
Wohnort: DN, Heimat: HB
Kontaktdaten:

Freitag 22. August 2008, 11:26

Lonestar hat geschrieben:Trotzdem wüsste ich ganz gerne was nun genau in meinem Fall der Nachteil wäre wenn ich ein allgemeines except einsetze. Denn wenn irgendein Fehler auftritt wird doch so wie ich das sehe mein handler auch beendet - ich schreibe in data einen leeren String, und darauf steigt der handler aus der while- Schleife aus. Damit ist mein Modul abgearbeitet, und zu Ende. Über eine kleine Aufklärung würde ich mich schon freuen :roll:
Weil du damit auch alle daemlichen Programmierfehler verschluckst. Alles bis auf Syntax- und Einrueckungsfehler loest naemlich erst zur Laufzeit Exceptions aus. Z.B. bekommst du, wenn du dich einfach nur im Code verschreibst, einen NameError oder AttributeError, der von einem allgemeinen Except geschluckt wird.
Offizielles Python-Tutorial (Deutsche Version)

Urheberrecht, Datenschutz, Informationsfreiheit: Piratenpartei
Lonestar
User
Beiträge: 147
Registriert: Samstag 9. August 2008, 08:31

Freitag 22. August 2008, 12:13

na das ging aber mal schnell,
und wenns mir dann so erklärt wird, dann verstehe sogar ich das Problem. Ich werde also die exception so lassen wie ichs hier schon gepostet habe und mal gucken was mein Spielzeug so produziert.


Aber nur des Verständises halber - ich denke nicht das ich das jemals weiter umsetzen möchte - würde ich gerne noch weiterfragen - vielleicht bringt ja jemand die Geduld auf und kann mir auch meine nächsten Fragen beantworten:

Um also andere Fehler als 'socket.timeout' abzufangen, die durch meinen Befehl 'data = self.rfile.readline()' ausgelöst werden(nur hier kann etwas in meinem Modul passieren das ich nicht vorhersehen kann) müsste ich mich genauer mit allen Objekten auseinanersetzen die ich einbinde - ob erben oder aufrufen ist da ja egal. Dann muss ich gucken welche Fehler für mich relevant sind, und diese abarbeiten. Oder gibt es da Wege die ich einfach nicht kenne? Speziell bei einem Netzwerkservice finde ich es halt schon wichtig das ganze Stabil zu machen. Da ich ja vorerst lediglich auf einem Port lausche und die Daten nur durchreiche kann ja nicht wirklich viel passieren. Für mein privates spiel im LAN ist das ja noch völlig ausreichend.
Antworten