SocketServer und epoll

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
der_Angler
User
Beiträge: 25
Registriert: Montag 28. Januar 2013, 00:48

mein Fehler :(

aber hey, jetzt gibt es Abwechslung

Code: Alles auswählen

something went wrong
exception
something went wrong
exception
something went wrong
exception
something went wrong
exception
something went wrong
exception
Die beiden Zeilen wechseln sich sehr schnell ab, reagieren tut es sonst nicht.

Ich habe aber das Gefühl das wir dicht dran sind, bzw. eher du :)

PS: Python Einsteiger Buch ist bereits bestellt.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

dann müssen wir wohl doch auf epoll umsteigen:

Code: Alles auswählen

from quick2wire.gpio import GPIOPin, In, Out, Pin, exported
import asyncore
import socket
import select

pin_bed = 0
pin_bed_switch = 4
pin_nightlight = 3

class Switch(object):
    def __init__(self, switch, light):
        self.switch = switch
        self.light = light
        asyncore.socket_map[switch.fileno()] = self
    
    def readable(self):
        return True

    def writable(self):
        return False

    def handle_read_event(self):
        self.light.value = 0 if self.light.value else 1

    def handle_write_event(self):
        print "shouldn't happen"
        
    def handle_expt_event(self):
        print "exception"
        
    def handle_error(self):
        print "something went wrong"


class Client(asyncore.dispatcher_with_send):
    def __init__(self, socket=None, pollster=None):
        asyncore.dispatcher_with_send.__init__(self, socket)
        self.data = ''
        if pollster:
            self.pollster = pollster
            pollster.register(self, select.EPOLLIN)

    def handle_close(self):
        if self.pollster:
            self.pollster.unregister(self)
        
    def handle_read(self):
        receivedData = self.recv(8192)
        if not receivedData:
            self.close()
            return
        receivedData = self.data + receivedData
        while '\n' in receivedData:
            line, receivedData = receivedData.split('\n',1)
            self.handle_command(line)
        self.data = receivedData

    def handle_command(self, line):
        print line
        # TODO: switch on/off lights


class Server(asyncore.dispatcher):
    def __init__(self, listen_to, pollster):
        asyncore.dispatcher.__init__(self)
        self.pollster = pollster
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.bind(listen_to)
        self.listen(5)

    def handle_accept(self):
        newSocket, address = self.accept()
        print "Connected from", address
        Client(newSocket,self.pollster)


def readwrite(obj, flags):
    try:
        if flags & select.EPOLLIN:
            obj.handle_read_event()
        if flags & select.EPOLLOUT:
            obj.handle_write_event()
        if flags & select.EPOLLPRI:
            obj.handle_expt_event()
        if flags & (select.EPOLLHUP | select.EPOLLERR | select.EPOLLNVAL):
            obj.handle_close()
    except socket.error, e:
        if e.args[0] not in asyncore._DISCONNECTED:
            obj.handle_error()
        else:
            obj.handle_close()
    except asyncore._reraised_exceptions:
        raise
    except:
        obj.handle_error()


if __name__ == "__main__":
    with exported(GPIOPin(pin_bed, direction=Out)) as light_bed, \
        exported(GPIOPin(pin_bed_switch, Pin.In, Pin.Rising)) as light_bed_switch, \
        exported(GPIOPin(pin_nightlight, direction=Out)) as light_nightlight:

        light_bed.value = 0
        light_nightlight.value = 0

        pollster = select.epoll()
        pollster.register(Server(("",54321),pollster), select.EPOLLIN)
        pollster.register(Switch(light_bed_switch, light_bed), select.EPOLLIN | select.EPOLLET)
        while True:
            evt = pollster.poll()
            for fd, flags in evt:
                readwrite(fd, flags)
der_Angler
User
Beiträge: 25
Registriert: Montag 28. Januar 2013, 00:48

Hmm, du machst das auch öfters? Ich meine in Python programmieren :)

Okay, bekomme folgendes Ergebnis

Code: Alles auswählen

pi@raspberrypi ~ $ python RaPiServer2.py
  File "RaPiServer2.py", line 100
    if flags & select.EPOLLIN:
                 ^
SyntaxError: invalid syntax
sind diese Zeilen so wirklich richtig?

Code: Alles auswählen

        if flags & select.EPOLLIN:
            obj.handle_read_event()
        if flags & select.EPOLLOUT:
            obj.handle_write_event()
        if flags & select.EPOLLPRI:
            obj.handle_expt_event()
        if flags & (select.EPOLLHUP | select.EPOLLERR | select.EPOLLNVAL):
BlackJack

@der_Angler: Da ist der Syntaxhighlighter in der Forensoftware schuld — der macht aus einem '&' ein '&'.
der_Angler
User
Beiträge: 25
Registriert: Montag 28. Januar 2013, 00:48

habs mir schon gedacht, aber man weiß ja nie ;)
der_Angler
User
Beiträge: 25
Registriert: Montag 28. Januar 2013, 00:48

es wird heiß, ich kann es spüren ...

Code: Alles auswählen

pi@raspberrypi ~ $ python RaPiServer2.py
Traceback (most recent call last):
  File "RaPiServer2.py", line 129, in <module>
    pollster.register(Switch(light_bed_switch, light_bed), select.EPOLLIN | select.EPOLLET)
TypeError: argument must be an int, or have a fileno() method.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

der_Angler hat geschrieben:es wird heiß, ich kann es spüren ...
naja, freu Dich nicht zu früh.

das sind nur so Kleinigkeiten, die beim Remote-Debuggen nach und nach auftauchen.
»fileno« fehlt:

Code: Alles auswählen

class Switch(object):
    def __init__(self, switch, light):
        self.switch = switch
        self.light = light
        self.fileno = switch.fileno
        asyncore.socket_map[switch.fileno()] = self
der_Angler
User
Beiträge: 25
Registriert: Montag 28. Januar 2013, 00:48

@Sirius3
Sagmal, du hättest kein Interesse an einem RaspberryPi?

Weil du hilfst mir hier dermaßen, ich frag mich die ganze Zeit wie ich mich bedanken kann.
Und da ich hier zuhause 3-4 Raspberrys rumliegen habe würde ich dir einfach einen schenken, quasi als Dankeschön und zum Testen :)
der_Angler
User
Beiträge: 25
Registriert: Montag 28. Januar 2013, 00:48

naja, freu Dich nicht zu früh.
Hmm, da hattest du wohl recht, ein neuer Fehler

Code: Alles auswählen

pi@raspberrypi ~ $ python RaPiServer2.py
Traceback (most recent call last):
  File "RaPiServer2.py", line 134, in <module>
    readwrite(fd, flags)
  File "RaPiServer2.py", line 117, in readwrite
    obj.handle_error()
AttributeError: 'int' object has no attribute 'handle_error'
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

der_Angler hat geschrieben:PS: Python Einsteiger Buch ist bereits bestellt.
Welches? Hoffentlich keines von Galileo.
In specifications, Murphy's Law supersedes Ohm's.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Ich hoffe, Du verstehst noch einigermaßen, wie das Programm aufgebaut ist,
falls nicht, fragen! und nicht nur Fehlermelden¡

epoll nimmt zwar file-like-Objekte entgegen, gibt aber nur Filehandles zurück :(
Deshalb noch ein kleiner epoll-Wrapper

Code: Alles auswählen

class EPoll(object):
    def __init__(self):
        self.epoll = select.epoll()
        self.fdmap = {}
        
    def register(self, obj, flags):
        fd = obj.fileno()
        self.epoll.register(fd, flags)
        self.fdmap[fd] = obj

    def unregister(self, obj):
        fd = obj.fileno()
        del self.fdmap[fd]
        self.epoll.unregister(fd)
        
    def poll(self):
        evt = self.epoll.poll()
        for fd, flags in evt:
            yield self.fdmap[fd], flags

if __name__ == "__main__":
    with exported(GPIOPin(pin_bed, direction=Out)) as light_bed, \
        exported(GPIOPin(pin_bed_switch, Pin.In, Pin.Rising)) as light_bed_switch, \
        exported(GPIOPin(pin_nightlight, direction=Out)) as light_nightlight:

        light_bed.value = 0
        light_nightlight.value = 0

        pollster = EPoll()
        pollster.register(Server(("",54321),pollster), select.EPOLLIN)
        pollster.register(Switch(light_bed_switch, light_bed), select.EPOLLIN | select.EPOLLET)
        while True:
            evt = pollster.poll()
            for obj, flags in evt:
                readwrite(obj, flags)
der_Angler
User
Beiträge: 25
Registriert: Montag 28. Januar 2013, 00:48

naja, ich blicke so halbwegs durch.

Ich versuche halt immer auf die schnelle deine Änderungen und poste das Ergebnis damit du entsprechendes Feedback hast.

Wenn alles richtig läuft setze ich mich hin und gehe jede Zeile einzel durch.
Die prinzipielle Funktionsweise verstehe ich aber schon.

Okay, habe das Programm entsprechend geändert.

Nach dem Starten erscheint:

Code: Alles auswählen

pi@raspberrypi ~ $ python RaPiServer2.py
something went wrong
was aber nicht schlimm ist weil der Hardware-Button funktioniert.

Drücke ich den Taster geht die LED an, bzw. wieder aus.
Trotzdem erscheint jedes mal wenn ich den Taster drücke die Fehlermeldung "something went wrong" - das sieht dann so aus

Code: Alles auswählen

pi@raspberrypi ~ $ python RaPiServer2.py
something went wrong
something went wrong
something went wrong
something went wrong
Beflügelt vom Erfolg wollte ich dann auch mal die Netzwerkfunktionen testen und hab die Handy-App gestartet.
Leider kam direkt beim starten der App (und damit beim verbinden mit dem Skript) ein Fehler und das Skript hat auch nicht auf die Netzwerkbefehle reagiert.

Code: Alles auswählen

pi@raspberrypi ~ $ python RaPiServer2.py
something went wrong
something went wrong
something went wrong
something went wrong
Connected from ('192.168.2.113', 43029)
error: uncaptured python exception, closing channel <__main__.Server listening :54321 at 0x4056b4e0> (<type 'exceptions.AttributeError'>:'module' object has no attribute 'EPOLLNVAL' [RaPiServer2.py|readwrite|107])
error: uncaptured python exception, closing channel <__main__.Client connected 192.168.2.113:43029 at 0x4056b7d8> (<type 'exceptions.AttributeError'>:'module' object has no attribute 'EPOLLNVAL' [RaPiServer2.py|readwrite|107])
Ds verstehen fällt mir zum teil noch schwer, für ein Anfänger ist das Thema aber auch durchaus komplex.
Mein Glück das ich nen paar andere Programmiersprachen kenne und sich alle ähnlich sind.

Seltsamerweise fand ich die erste Version des Skripts von dir (die ohne epoll) einfacher zu verstehen.
Mit epoll habe ich irgendwie Schwierigkeiten.
BlackJack

`select.EPOLLNVAL` gibt es nicht.
der_Angler
User
Beiträge: 25
Registriert: Montag 28. Januar 2013, 00:48

okay wenn ich in der entsprechenden Zeile folgendes

Code: Alles auswählen

        if flags & (select.EPOLLHUP | select.EPOLLERR | select.EPOLLNVAL):
durch

Code: Alles auswählen

        if flags & (select.EPOLLHUP | select.EPOLLERR | select.POLLNVAL):
austausche dann scheint tatsächlich alles zu funktionieren.
Ich bekomme zwar immernoch die Meldung

Code: Alles auswählen

pi@raspberrypi ~ $ python RaPiServer2.py
something went wrong
something went wrong
something went wrong
something went wrong
Connected from ('192.168.2.113', 48439)
something went wrong
something went wrong
something went wrong
something went wrong
wenn ich die Hardware Taste drücke, aber es funktioniert :)

Sollte also anstatt "EPOLLNVAL" -> "POLLNVAL" der ganze Fehler gewesen sein?
Und ist es okay das einfach so abzuändern?

Und schon mal vielen Dank an alle und an Sirius im Besonderen :)
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Das something went wrong ist die sehr einfache Fehlerbehandlung.
Du solltest hier:

Code: Alles auswählen

def readwrite(obj, flags):
    try:
        if flags & select.EPOLLIN:
            obj.handle_read_event()
        if flags & select.EPOLLOUT:
            obj.handle_write_event()
        if flags & select.EPOLLPRI:
            obj.handle_expt_event()
        if flags & (select.EPOLLHUP | select.EPOLLERR | select.EPOLLNVAL):
            obj.handle_close()
    except socket.error, e:
        if e.args[0] not in asyncore._DISCONNECTED:
            obj.handle_error()
        else:
            obj.handle_close()
    except asyncore._reraised_exceptions:
        raise
    except:
        obj.handle_error()
vor allen die letzten beiden Zeilen noch erweitern, damit Du auch
weißt was für eine Exception, samt Traceback aufgetreten ist.
Es werden nämlich alle Fehler (auch falsch geschriebene Variablennamen)
einfach geschluckt.
Aber das überlass ich dir als Hausaufgabe.
der_Angler
User
Beiträge: 25
Registriert: Montag 28. Januar 2013, 00:48

Hmm, dann will ich mich am Wochenende mal hinsetzen und werde über den Hausaufgaben brüten.

Ich hoffe ich kann dann am Montag eine gute Lösung posten :)
der_Angler
User
Beiträge: 25
Registriert: Montag 28. Januar 2013, 00:48

So,
ich habe mal versucht mich in das Thema "Error-Handling" einzulesen, muss aber sagen das es im Augenblick noch meinen Horizont übersteigt.
Ich konnte also meine Hausaufgabe nicht machen, da das Script aber trotzdem tut was es soll scheint der Fehler auch nicht weiter schlimm zu sein.

ich nutze mittlerweile 2 x Versionen. Eine auf dem RaspberryPi mit Hardware-Buttons (und Fehlermeldung) und eine auf dem Media-Server ohne Buttons (und ohne Fehler).
Dank dir bin ich jetzt soweit das ich eine USB-Steckdosenleiste, diverse Funkempfänger von HomeEasy und xbmc bequem übers Handy steuern kann.

Dafür wollte ich mich noch einmal herzlich bedanken :)
der_Angler
User
Beiträge: 25
Registriert: Montag 28. Januar 2013, 00:48

Hallo,

nach langer Zeit muss ich mich mal wieder melden.

Erst einmal danke an das Script, das funktioniert soweit unter Linux sehr gut :)

Dafür tausend Dank!!!

Aber, unter Windows mit python 3.2 sieht die Welt leider ganz anders aus, versuche ich das Script unter Windows laufen zu lassen, dann bekomme ich jede Menge bezüglich ".epoll()" - es scheint als kenne python unter windows diesen Befehl nicht.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@der_Angler: falls Du die Dokumentation gelesen hast, wüstest Du, dass es epoll nur unter Linux gibt.
der_Angler
User
Beiträge: 25
Registriert: Montag 28. Januar 2013, 00:48

okay, dann habe ich das schon einmal richtig verstanden ... hatte sowas gelesen aber gedacht ich hätte es falsch verstanden.

Hmm, okay, dann muss ich in dem Fall also ein Script für Linux und eins für Windows nutzen?

Schade, da ich unter Windows mit Eclipse entwickle, da bekomme ich dann auch in zukunft weiter Fehler angezeigt.
Trotzdem erst mal danke.
Antworten