Client soll drucgehend lauschen

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
W33Z
User
Beiträge: 6
Registriert: Mittwoch 23. Oktober 2019, 14:28

Hallo,
kann mir jemand einen Tipp geben?
Ich habe im Netz 1mio beispiele gefunden für client-server (echo) anwendungen, leider habe ich eine andere Anwendung vor.
Ich will ein Konsolen-Programm schreiben, dass UDP sendet(per usereingabe) und gleichzeitig immer schaut ob UDP SIgnale reinkommen. (Fernsteuerung)
Vermutlch threadbasiert: 1 thread hört, einer sendet.
Ist socketserver.ThreadingMixIn das was ich dafü brauche und wenn ja, wie verwende ich es?
Netzwerk ist neu für mich.
W33Z
User
Beiträge: 6
Registriert: Mittwoch 23. Oktober 2019, 14:28

Ergänzung
Beide Sockets sollen im selben Netzwerk sein. Ohne Verschlüsselung und alles - ganz simpel.
Einfach nur UDP Nachrichten an einen Server schicken und immer höhren ob was reinkam von dem Server. Jedoch halt IMMER danach hören, da auch ohne Nutzereingabe nachrichten vom server kommen können.
__deets__
User
Beiträge: 14543
Registriert: Mittwoch 14. Oktober 2015, 14:29

Kann man mit Threads machen. Kann man aber je nach gewuenschter Benutzeroberflaeche auch besser machen. Womit programmierst du die?

Ich habe im uebrigen fuer solche Dinge in der Vergangenheit stattdessen nanomsg benutzt, und das wuerde ich auch hier empfehlen. Damit kannst du eine bi-direktionale PAIR-Verbindung aufmachen, es ist auch egal, ob client und server mal abschmieren, es verbindet sich alles automagisch neu, und im Gegensatz zu normalen sockets kann man auch periodisch pollen, wenn das einfacher sein sollte - ohne auf Threads zurueckgreifen zu muessen. Im Hintergrund werkeln da auch welche, aber damit muss man sich nicht rumscheren.
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@W33Z: Das ist auch gut so, da 999957 der Beispiele Schrott sind.

Für UDP möchtest Du wahrscheinlich socketserver.ThreadingUDPServer verwendet. Was verstehst Du am Beispiel in der Dokumentation nicht?

Bist Du sicher, dass Du UDP verwenden möchtest?
Benutzeravatar
__blackjack__
User
Beiträge: 13116
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@W33Z: Um die letzte Frage von Sirius3 noch ein bisschen zu präzisieren: Dir ist klar das bei UDP Pakete auch einfach verloren gehen können?
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
W33Z
User
Beiträge: 6
Registriert: Mittwoch 23. Oktober 2019, 14:28

danke an euch alles schonmal an die Anteilnahme :)

Ja ich kenne UDP, also die Eigenschaften. Wenn mal ein Paket verloren geht, ist das halt so. Das ist nicht schlimm. Dann bekommt man keine antwort und weiß, das signal kam nicht an. Es soll ganz simpel und verbindungslos sein.

https://docs.python.org/3.4/library/socketserver.html
auf der Seite gibts ein Beispiel. Es wird ein server erstellt, der für jede Nachricht eines clienten ein thread aufmacht und die nachricht zurückschickt. Da ich mit threads auch noch nichts am hut hatte, wollte ich die komplexität klein halten und auch keine extra lib downloaden.

Ich würde einfach nur gern einen Listener haben, der immer horscht ob ein SIgnal reinkommt (das würde ich unter dem requestHandler verstehen)
und gleichzeitig sollen Nutzereingaben möglich sein - also signale per UDP verschicken.
Ich lerne immer gut an Beispielen, nur find ich dazu nichts im Netz - das irgendwie in die Richtung geht - meiner Meinung nach (ich bin kein Profi)
__deets__
User
Beiträge: 14543
Registriert: Mittwoch 14. Oktober 2015, 14:29

Der UDPServer ist prinzipiell geeignet. Du musst den halt in deinen mainloop integrieren. Oder einen Thread starten. Weniger Komplexitaet geht nicht. Ausser man benutzt externe Bibliotheken, aber das willste ja nicht 🤷‍♂️
W33Z
User
Beiträge: 6
Registriert: Mittwoch 23. Oktober 2019, 14:28

Danke für eure ANtworten, ich habs:

Code: Alles auswählen

import socket
import threading
import socketserver

import sys


class myRequestHandler(socketserver.BaseRequestHandler):

    def handle(self):
        self.data = self.request[0].strip()
        print(self.data)
        
class ThreadedUDPServer(socketserver.ThreadingMixIn, socketserver.UDPServer):
    pass

class steuerungLampe():

    def __init__(self, ipLight, portLight):
        
        self.s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.s.bind(('',0))
        ipAddr = socket.gethostbyname(socket.gethostname()) 
        port = self.s.getsockname()[1] 
        self.server = ThreadedUDPServer((ipAddr, port),myRequestHandler)
        self.ipLight = ipLight
        self.portLight = portLight 
        server_thread = threading.Thread(target=self.server.serve_forever)
        server_thread.daemon = True
        server_thread.start()   

    # sende daten
    def sendData(self, message):
        sent = self.s.sendto(str.encode(message), (self.ipLight, self.portLight))


    # schließe socket und thread
    def closeListener(self):

        self.s.shutdown()
        self.server.shutdown()
        self.server.server_close()

		
		
		
steuerung = steuerungLampe('192.168.178.1', 1000)

while True:
	message = input()
	steuerung.sendData(message)
Jetz wollte ich einen Schritt weiter als geplant gehen. Ich möchte gern die empfangenen Daten aus dem "Handler" raus bekommen, also nicht nur auf der Konsole ausgeben sondern diese außerhalb der Klasse in einer Variablen zur verfügung haben. Was ist da der beste Weg ?
__deets__
User
Beiträge: 14543
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du musst zuerst ein bisschen aufraeumen. Dazu gehoert

- sinnlose "my"-Praefixe loswerden.
- SteuerungLampe statt steuerungLampe benutzen. In Python schreibt man Klassennamen CamelCase, nicht dromedarCase
- globale Variablen steuerung, message muessen verschwinden. Stattdessen eine main-Funktion einfuehren, und die aufrufen. IN der main-Funktion liegst du dann steuerung und message an.
- da kannst du dann auch eine Instanz einer Queue aus dem queue-Modul erzeugen. Die uebergibst du dann an SteuerungLampe als Argument im Konstruktor.
- die SteuerungLampe erzeugt dann den UDP-Server mit einer kleinen Aenderung: statt direkt die Klasse RequestHandler (ohne sinnlosen my-Praefix) anzugeben, gibst du stattdessen eine Factory-Funktion an, die du mit dem functools.partial und RequestHandler baust. Die bekommte die Queue-Instanz angebunden. Dadurch werden nun alle RequestHandler mit dieser Queue versorgt.

Code: Alles auswählen

self.server = ThreadedUDPServer((ipAddr, port), partial(RequestHandler, message_queue))
- RequestHandler bekommt einen Konstruktor, in dem es sich die uebergebene message_queue merkt, und natuerlich den Konstrukter der Oberklasse aufruft. Und wenn jetzt Daten ankommen, stopfst es die in die Queue.

Code: Alles auswählen


class RequestHandler(socketserver.BaseRequestHandler):

    def __init__(self, message_queue):
        super().__init__()
        self._queue = message_queue

    def handle(self):
        self._queue.put(self.request[0].strip())
- in der Hautpschleife kannst du vor input pruefen, ob es Nachrichten gibt in der Queue. Und die dann bei Bedarf rausholen. Siehe dazu die Dokumentation zu queue.Queue.get.
W33Z
User
Beiträge: 6
Registriert: Mittwoch 23. Oktober 2019, 14:28

Vielen Danke für die ganzen Tipps, ich habe auf die ganzen "Formatierungssachen" nicht so geachtet bis jetzt. Bin eigentlich kein Programmierer, bring mir das nebenbei seit dem Sommer bei.

Hab mir deine Ergänzungen angesehen (functools und queue) und auch in meinen Code aufgenommen, um die Stellen die du vorgeschlagen hast.
jetzt kommt nur folgende Fehlermeldung

Code: Alles auswählen

Exception happened during processing of request from ('192.168.178.1', 64975)
Traceback (most recent call last):
  File "C:\ProgramData\Anaconda3\lib\socketserver.py", line 647, in process_request_thread
    self.finish_request(request, client_address)
  File "C:\ProgramData\Anaconda3\lib\socketserver.py", line 357, in finish_request
    self.RequestHandlerClass(request, client_address, self)
TypeError: __init__() takes 2 positional arguments but 5 were given
Ich gehe davon aus, dass der Fehler im Konstruktor des RequestHandlers geworfen wird. (also klar der Fehler kommt aus de mSocketserver Modul, aber die stelle in meinem Code)
Im Zusammenhang mit der Vererbung und dem Aufruf "super()._init__()".
Er will nur request, client_address, doch was sind die 5 Argumente ((ipAddr, port),functools.partial(RequestHandler, message_queue)..und ?! )
Wenn ich das richtige sehe hat der BaseRequestHandler ein setup(), das denk ich gleichzusetzen ist mit __init__() - Diese macht im normalfall erstmal nichst laut doku (wenn man daran nichts tut) , daher frage ich mich, wie der Fehler kommt.
__deets__
User
Beiträge: 14543
Registriert: Mittwoch 14. Oktober 2015, 14:29

Bitte zeig den gesamten Code & die vollständige Fehlermeldung.
W33Z
User
Beiträge: 6
Registriert: Mittwoch 23. Oktober 2019, 14:28

Das ist die "Fehlermeldung" , diese kommt, wenn ich ein UDP Paket erhalte. Habe von einem anderen Rechner mir UDP Pakete schicken lassen un das kommt dann. Nicht mehr und nicht weniger
Also das Programm kompiliert und kann ausgeführt werden. Nur beim Emfangen wird statt die gesendete Nachricht der oben genannte Fehler angezeigt. Ich habe einfach den alten Code von mir genutzt um etwas zu senden.

Code: Alles auswählen

import socket
import threading
import sys
import socketserver
import functools
import queue



class RequestHandler(socketserver.BaseRequestHandler):

    def __init__(self, message_queue):
        super().setup()
        self._queue = message_queue

    def handle(self):
        self._queue.put(self.request[0].strip())
        print(self.request[0].strip())
        
class ThreadedUDPServer(socketserver.ThreadingMixIn, socketserver.UDPServer):
    pass

class SteuerungLampe():

    def __init__(self, ipLight, portLight, message_queue):
        self.s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.s.bind(('',0))
        ipAddr = socket.gethostbyname(socket.gethostname()) 
        port = self.s.getsockname()[1] 
        self.handle = RequestHandler
        self.server = ThreadedUDPServer((ipAddr, port),functools.partial(self.handle, message_queue))
        self.ipLight = ipLight
        self.portLight = portLight 
        server_thread = threading.Thread(target=self.server.serve_forever)
        server_thread.daemon = True
        server_thread.start()   

    # sende daten
    def sendData(self, message):
        sent = self.s.sendto(str.encode(message), (self.ipLight, self.portLight))

    # schließe socket und thread
    def closeListener(self):
        self.s.shutdown()
        self.server.shutdown()
        self.server.server_close()

		
if __name__ == "__main__":

    message_queue = queue.Queue()
    steuerung = SteuerungLampe('192.168.178.1', 1000, message_queue)

    while True:
        receive = message_queue.get()
        print(receive)
        message = input()
        steuerung.sendData(message)
__deets__
User
Beiträge: 14543
Registriert: Mittwoch 14. Oktober 2015, 14:29

Die Zuweisung an "self.handle" ist Unfug und verwirrend. Und im Konstruktor von RequestHandler rufst du nicht __init__ der Oberklasse auf. Sondern irgendeine Funktion "setup". Das ist falsch. Das muss __init__ sein.

Was ich aber vergessen habe ist, etwaige andere Parameter (die es offensichtlich gibt) weiter zu leiten:

Code: Alles auswählen

class RequestHandler(socketserver.BaseRequestHandler):

    def __init__(self, message_queue, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._queue = message_queue
Probier mal das.
Antworten