Externe Übergabe von Parametern an mein Python-Skript

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
allrounder93
User
Beiträge: 5
Registriert: Donnerstag 27. August 2015, 14:54

Hi,

ich bin auf der Suche nach tutorielles wie ich in Python einen Netzwerkdienst implementiere, um über eine SSH-Verbindung Befehle bzw. Parameter an ein Python-Skript zu übergeben. Ich bin nicht nur neu im Forum, sondern befasse mich leider auch erst seit neusten mit der Python-Materie. Jedoch beherrsche einige andere Programmiersprachen, also ist keine Grundlagenforschung nötig ;-)
Ich habe mich mit den Raspberry PI’s angefreundet und steuere bereits erfolgreich via Python-Skript WS2801-RGB-LEDs an. Nun würde ich dies gerne von extern ermöglichen um von meinem Backligth-System entferne LEDs anzusteuern… Für etwas mehr Effekt ;-)
Also nach der ganzen Drumherum Story, die konkrete Frage:
Wie ermögliche ich eine externe Übergabe von Parametern über mein lokales Netzwerk an mein Python-Skript?

Gruß Chris
Benutzeravatar
sparrow
User
Beiträge: 4535
Registriert: Freitag 17. April 2009, 10:28

Ich bin mir nicht ganz sicher was du meinst, aber:
Du machst einen ServerSocket auf und lauscht darauf?
allrounder93
User
Beiträge: 5
Registriert: Donnerstag 27. August 2015, 14:54

Perfekt, danke!
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@allrounder93 Hab hier ertwas interessantes gefunden: http://www.binarytides.com/python-socke ... e-example/
BlackJack

@allrounder93: Ignorier den Blogpost den Alfons da verlinkt hat — nicht robuster Socket-Code, Verwendung des `thread`-Moduls, einige Variablen und Code auf Modulebene wobei eine Variable sogar noch so geschrieben wird als wäre es eine Konstante. Und wenn da schon ein einfacher Echo-Server nicht robust hinbekommen wird, wie soll das dann erst bei der Implementierung eines Protokolls werden…

Zudem ist das direkt mit dem Socket eine Ebene tiefer programmiert als sparrow das mit dem `SocketServer`-Modul vorgeschlagen hat.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@allrounder93 Mit dem Server hat BlackJack recht.

Hast Du schon daran gedacht, json zur Parameterübergabe zu verwenden?

Du gibst in Python Syntax ein, der Client wandelt in json um. Hier zum Ausprobieren:

Code: Alles auswählen

import json

while True:
	data = input(">")
	try: print(json.dumps((eval(data))))
	except: print("FAIL")
Im json Format geht es zum Server und der wandelt dann wieder über json nach Python zurück
Benutzeravatar
sparrow
User
Beiträge: 4535
Registriert: Freitag 17. April 2009, 10:28

@Alfons:

Hinter einen Doppelpunkt gehört ein Newline.

Was soll der Quatsch mit input und eval? Beides ist für den Threadstarter absolut sinnlos, hat nichts mit dem Thema zu tun und ist zudem auch noch total unnötig. Ist das wieder ein "ich will eval benutzen"-Trolling?

Das json-Modul kann die Python-Datenstrukturen wandeln, das hat mit irgendwelchem Syntax nichts zu tun. Als Austauschformat eignet es sich natürlich.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

sparrow hat geschrieben:Was soll der Quatsch mit input und eval? Beides ist für den Threadstarter absolut sinnlos, hat nichts mit dem Thema zu tun und ist zudem auch noch total unnötig. Ist das wieder ein "ich will eval benutzen"-Trolling?
Es geht um Parameterübergabe für ein Pythonprogramm.

Und gut ist da, wenn man Pythonsyntax für die Parameter verwenden kann. Und mit eval kannst Du dann json.dumps verwenden. Ich hab es mal gemacht.

client.py:

Code: Alles auswählen

import socket
import sys
import json

HOST, PORT = "localhost", 8888

# SOCK_DGRAM is the socket type to use for UDP sockets
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# As you can see, there is no connect() call; UDP has no connections.
# Instead, data is directly sent to the recipient via sendto().
import json

while True:
    data = input(">")
    try:
        json_data=json.dumps((eval(data)))
        print(json_data)
        sock.sendto(bytes(json_data, "utf-8"), (HOST, PORT))
        received = sock.recv(16)
    except:
        print("FAIL")
Und server.py:

Code: Alles auswählen

import socketserver
import json

class MyUDPHandler(socketserver.BaseRequestHandler):

    def handle(self):
       
        parameters = json.loads(str(self.request[0], "utf-8"))
        print(parameters)
        socket = self.request[1]
        socket.sendto(bytes('OK', "utf-8"), self.client_address)

if __name__ == "__main__":
    HOST, PORT = "localhost", 8888
    print("Parameterserver",HOST,PORT)
    server = socketserver.UDPServer((HOST, PORT), MyUDPHandler)
    server.serve_forever()
Und da kommt bei parameters eine Python-Liste heraus und zwar mit den richtigen Datentypen

Und jetzt könnte man das Ganze noch mit Funktionsaufrufen verbinden
Zuletzt geändert von Damaskus am Freitag 11. September 2015, 09:22, insgesamt 1-mal geändert.
Grund: Unnötige Kommentare gegen andere User entfernt
BlackJack

@Alfons Mittelmeyer: Wenn man die Python-Daten aber dann sowieso in JSON umwandelt, also die Möglichkeiten auf das eingeschränkt wird was JSON kann, dann kann man den Benutzer auch gleich JSON eingeben lassen und sich so die Sicherheitslücke mit `eval()` sparen. Selbst wenn man den Benutzer Python-Datentypen eingeben lassen will, würde man besser `ast.literal_eval()` verwenden um Angriffe zu vermeiden.

Die Ausnahmebehandlung ist sehr schlecht, denn egal was nicht funktioniert: Das Auswerten der Eingabe, das serialisieren als JSON, das versenden, oder das empfangen, es wird immer einfach nur 'FAIL' ausgegeben und weitergemacht als wäre nichts passiert.

UDP kann man so nicht sinnvoll dafür verwenden. UDP Pakete müssen nicht ankommen. Wenn eines der Pakete, Anfrage oder Antwort, verloren geht, dann wartet man vergeblich aber unendlich auf die Antwort. Entweder man sichert das ab, mit Timeouts, ggf. erneut versenden, nachfragen warum die Antwort nicht gekommen ist, und so weiter, oder man verwendet TCP, was genau all diese Sachen macht. UDP sollte man nur verwenden wenn es nichts ausmacht das Pakete verloren gehen. Andernfalls ist man schnell dabei TCP nachzubauen, meistens in schlechterer Form und mit Fehlern.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@BlackJack Wenn es nur beim Client ist, ist eval keine Sicherheitslücke. Der Benutzer wird ja wissen, was er eingeben will. Zum Server kommen schließlich nur Daten und da gibt es kein eval
Mit der Json Syntax bin ich noch nicht so vertraut und mit eval sieht man bereits beim Client, ob die Syntax stimmt oder nicht, etwa ein Anführungszeichen vergessen.

Ja man könnte statt eval ja in Python wandeln, dann sieht man auch, ob die Syntax stimmt. Das wäre eigentlich die richtige Idee. Dann wäre eben das der Check beim Client:

Code: Alles auswählen

    json_data = "["+input(">")+"]"
    try:
        print(json.loads(json_data))
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@BlackJack Wäre das dann richtig?

Code: Alles auswählen

import socketserver
import json

class MyTCPHandler(socketserver.BaseRequestHandler):

    def handle(self):
        # self.request is the TCP socket connected to the client
        parameters = json.loads(str(self.request.recv(1024),"utf-8"))
        #print("{} wrote:".format(self.client_address[0]))
        print(parameters)
        # just send back the same data, but upper-cased
        self.request.sendall(bytes('OK',"utf-8"))

if __name__ == "__main__":
    HOST, PORT = "localhost", 8891

    print("Parameterserver",HOST,PORT)
    # Create the server, binding to localhost on port 9999
    server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)

    # Activate the server; this will keep running until you
    # interrupt the program with Ctrl-C
    try: server.serve_forever()
    finally: server.close()
BlackJack

@Alfons Mittelmeyer: Das Server-Objekt hat ein `allow_reuse_address`-Attribut das man vor dem verbinden auf `True` setzen kann damit das Betriebssystem den Port nicht eine Weile blockiert.

Das ist nicht robust weil `recv()` nicht die ganze Nachricht liefern muss. Der Aufruf kann 1 bis 1024 Bytes liefern, also im Extremfall auch einfach nur das erste Byte von dem was auf der anderen Seite gesendet wurde.

Am einfachste wäre es wenn man ein zeilenbasiertes Protokoll verwendet und beim Client jede Anfrage auf eine Zeile beschränkt. Was bei JSON ja möglich ist. Dann kann man im Handler die Dateiobjekte statt der Socket-Methoden verwenden und einfach die ganze Zeile einlesen. Mit `next()` würde ich das machen.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Hab TCP nicht robust hinbekommen, darum bin ich vorerst bei UDP geblieben. Bei TCP ging es einmal und dann immer broken pipe.

Habe jetzt den Server mit einem Signal Framework verbunden und kann dadurch direkt tkinter GUI ansprechen.

Hier das GUI Progamm kontakt.py:

Code: Alles auswählen

import tkinter as tk # GUI
import proxy as myproxy # own proxy signal framework
import extern_proxy # proxy for connection with other threads
import server # server for receiving messages from a client application


def main():
	
	proxy = myproxy.Proxy(extern_proxy.proxy) # create proxy for signal framework

	root = tk.Tk()
	root.title("Kontakte")

	tk.Label(root,text="Vorname").grid(row='0')
	tk.Label(text="Name").grid(row='1')
	tk.Label(text="Telefon").grid(row='2')

	vname = tk.Entry(root)
	vname.grid(column='1',row='0')
	name = tk.Entry(root)
	name.grid(column='1',row='1')
	tel = tk.Entry(root)
	tel.grid(column='1',row='2')


	# define callback functions for signals
	def set_vname(message):
		vname.delete(0,'end')
		vname.insert(0,message)

	def set_name(message):
		name.delete(0,'end')
		name.insert(0,message)

	def set_tel(message):
		tel.delete(0,'end')
		tel.insert(0,message)
	
	proxy.do_receive(None,"VNAME",set_vname)
	proxy.do_receive(None,"NAME",set_name)
	proxy.do_receive(None,"TEL",set_tel)
	
	# set default entries via signals 
	proxy.send("VNAME","Max")
	proxy.send("NAME","Mustermann")
	proxy.send("TEL","0800-0000")
	
	# define signals for receiving via server
	proxy.do_receive_extern(("NAME","VNAME","TEL"))


	# poll for external signals
	def do_poll():
		proxy.trigger()
		root.after(100,do_poll)

	do_poll()

	return root
	
main().mainloop()
Dann als Nächstes der Proxy des Signal Frameworks, proxy.py:

Code: Alles auswählen

import threading
import queue

class Proxy:
 
    def __init__(self,extern_proxy=None):

        # if there is an extern proxy, multiple threads may communicate
        # via this extern proxy without knowing each other
        if extern_proxy == None: self.extern_proxy = self
        else: self.extern_proxy = extern_proxy
 
        self.reset()

    def __del__(self):
        # unregister extern registered callbacks
        if self.extern_proxy != self: self.extern_proxy.undo_receiveAll_extern(self)

    # extern trigger for communication between threads
    # extern trigger before start of event loop
    # - before there is an event loop, extern triggers by events will not have an effect
    # - for tkinter the extern trigger may be used or polling
    def _noop(self): pass

    def reset(self):

        # Configuration for triggers at start, which may be changed by the user
        self.trigger = self.do_work # intern trigger at start is direct call of 'do_work'.
        self.extern_trigger = self._noop # extern trigger at start is set to do nothing. feel free to change this in your application

        self._Dictionary = {} # contains registered message ids and registered callback functions for these message ids
        self._owners = {} # contains owners and their callback functions:
                         # callback functions may be registerd for an owner (or for None in case of existence of the callbacks until end)
                         # if the owner becomes deleted it should unregister all it's callback functions
                         # because of this dictionary this may be done at once for all callback functions via undo_receiveAll or undo_receiveAll_extern

        self._Queue = queue.Queue() # Queue for sending messages
        self._Queue_HighPrio = queue.Queue() # Queue for register and unregister callback functions
        self._register("execute_function",lambda msg: msg()) # very useful callback function: executes messages, which are lambda expressions
        self._running = False # regulates calls of method 'do_work' - start value False enables call of method 'work'
        self._looping = False # regulates ending event loop. After 'loop' is called, the loop may be ended by calling 'exit_loop'

    # process a message in a queue ==========
    def work(self,*args):

        # get message from Queue
        if not self._Queue_HighPrio.empty(): data = self._Queue_HighPrio.get()
        elif not self._Queue.empty(): data = self._Queue.get()
        else: return False

        # look up message id and registered callbacks for it and call callback
        msgid = data[0]
        msgdata = data[1]
        if msgid in self._Dictionary:
            receivers = self._Dictionary[msgid].items() # items contain callback function and optional_parameter
            for callback,optional_parameter in receivers:
                if type(optional_parameter) is bool:
                    if optional_parameter: callback((msgid,msgdata))
                    else: callback(msgdata)
                else: callback((optional_parameter,(msgid,msgdata)))
        return True

    # process all messages in the queues ========
    def do_work(self,*args):
        if self._running: return
        self._running = True
        while self.work(): pass
        self._running = False

    # set both triggers at once, if it's the same ========
    def set_trigger(self,trigger):
        self.trigger = trigger
        self.extern_trigger = trigger

    # loop for event driven threads - don't use this in a GUI event loop (mainloop) - it will block the GUI
    def loop(self):
        self.event = threading.Event()
        self.set_trigger(self.event.set)
        self.event.set()
        self._looping = True
        while self._looping:
            self.event.wait()
            self.event.clear()
            self.do_work()

    # exit event loop 
    def exit_loop(self):
        self._looping = False
        self.trigger()

    # send functions invoke registered callbacks for the message id =======

    # send function within same thread
    def send(self,msgid,msgdata=None):
        self._Queue.put((msgid,msgdata))
        self.trigger()

    # send function called by another thread - use it, if you directly want to access the proxy of another thread
    def send_extern(self,msgid,msgdata=None):
        self._Queue.put((msgid,msgdata))
        self.extern_trigger()

    # send function called by another thread - you may use it also use it, if you directly want to access the proxy of another thread
    # this send function is used also in connection with communication via external proxy
    def receive_extern(self,message):
        self._Queue.put(message)
        self.extern_trigger()

    # extern send and receive callbacks connected with the external proxy =======

    # registering a list of message ids, which shall be received via the external proxy
    def do_receive_extern(self,message_ids):
        for mid in message_ids: self.extern_proxy.do_receive_extern_one(self,mid,self.receive_extern,True)

    # registering a list of message ids, which shall be sent to other tasks via the external proxy
    def do_send_extern(self,message_ids):
        for mid in message_ids: self.do_receive(self,mid,self.send_intern_extern,True)

    # callback for do_send_extern
    def send_intern_extern(self,message): self.extern_proxy.receive_extern(message)

    # register callbacks ================================================

    # normal case - we use the queue - different reasons
    def do_receive(self,owner,msgid,callback,optional_parameter=False):
        self._Queue_HighPrio.put(("execute_function",lambda: self._do_receive(owner,msgid,callback,optional_parameter)))
        self.trigger()

    # normally not used - if you want to receive messages from another thread without using the external proxy
    # or if another thread wants to receive messages from this thread without the external proxy
    def do_receive_extern_one(self,owner,msgid,callback,optional_parameter=False):
        self._Queue_HighPrio.put(("execute_function",lambda: self._do_receive(owner,msgid,callback,optional_parameter)))
        self.extern_trigger()

    # register callback for the owner, so that it may be unregistered by undo_receive_All
    # and call register callback
    def _do_receive(self,owner,msgid,callback,optional_parameter):
        if owner != None:
            if not owner in self._owners: self._owners[owner] = {}
            self._owners[owner][callback]=msgid
        self._register(msgid,callback,optional_parameter)

    # register message id, callback and optional_parameter in the message id dictionary
    def _register(self,msgid,callback,optional_parameter=False):
        if msgid not in self._Dictionary: self._Dictionary[msgid] = {}
        self._Dictionary[msgid][callback] = optional_parameter

    # unregister callback ================================================

    # normal case - we use the queue - different reasons
    def undo_receive(self,owner,msgid,callback):
        self._Queue_HighPrio.put(("execute_function",lambda: self._undo_receive(owner,msgid,callback)))
        self.trigger()
 
    # normally not used - only interesting for callbacks in connection with other threads without using the external proxy
    def undo_receive_extern(self,owner,msgid,callback):
        self._Queue_HighPrio.put(("execute_function",lambda: self._undo_receive(owner,msgid,callback)))
        self.extern_trigger()

    # unregister callback for the owner
    # and call unregister register callback
    def _undo_receive(self,owner,msgid,callback):
        if owner != None:
            if owner in self._owners:
                if callback in self._owners[owner]: del self._owners[owner][callback]
        self._unregister1(msgid,callback)

    # unregister message id, callback (and optional_parameter) by removing the entry from the dictionary
    def _unregister1(self,msgid,callback):
        if msgid in self._Dictionary:
            receivers = self._Dictionary[msgid]
            if callback in receivers:
                del receivers[callback]
                if len(receivers) == 0: del self._Dictionary[msgid]

    # unregister Owner ================================================

    # unregister all callbacks of an owner
    def undo_receiveAll(self,owner):
        self._Queue_HighPrio.put(("execute_function",lambda: self._undo_receiveAll(owner)))
        self.trigger()

    # unregister all callbacks of an owner, which are registered by the proxy of another thread
    def undo_receiveAll_extern(self,owner):
        self._Queue_HighPrio.put(("execute_function",lambda: self._undo_receiveAll(owner)))
        self.extern_trigger()

    # remove owner from the owners dictionary and unregister its callbacks ============
    # undo_receiveAll should be called, if an owner is destroyed and the callbacks would become not valid after this
    def _undo_receiveAll(self,owner):
        if owner in self._owners:
            messages = self._owners[owner]
            del self._owners[owner]
            for callback,msgid in messages.items(): self._unregister1(msgid,callback)
Dann der externe Proxy zum Verbinden mehrerer Threads, extern_proxy.py

Code: Alles auswählen

# this thread contains a proxy, which other threads shall know for communication between them
# this proxy will send messages, which are registered by 'do_receive_extern' to the threads, which registered the messages
# and the threads register messages for their proxies via do_send_extern, which shall be sent to this extern proxy

import threading
import proxy as myproxy

proxy = None

class MyThread(threading.Thread):
 
    def run(self):
        global proxy
        self.proxy = myproxy.Proxy()
        proxy=self.proxy
        self.proxy.loop()

mythread = MyThread()
mythread.daemon = True
mythread.start()
Der server server.py:

Code: Alles auswählen

import socketserver
import json

import extern_proxy
import threading


HOST, PORT = "localhost", 8888
 
class MyUDPHandler(socketserver.BaseRequestHandler):
 
	def handle(self):
	   
		parameters = json.loads(str(self.request[0], "utf-8"))
		extern_proxy.proxy.receive_extern(parameters)
		socket = self.request[1]
		socket.sendto(bytes('OK', "utf-8"), self.client_address)
 
class MyThread(threading.Thread):

    def run(self):
        print("Parameterserver",HOST,PORT)
        server = socketserver.UDPServer((HOST, PORT), MyUDPHandler)
        try: server.serve_forever()
        finally: server.close()

mythread = MyThread()
mythread.daemon = True
mythread.start()
Und der client, client.py:

Code: Alles auswählen

import socket
import sys
import json

HOST, PORT = "localhost", 8888

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
 
# Connect to server and send data
sock.connect((HOST, PORT))

while True:
    json_data = "["+input(">")+"]"
    try:
        print(json.loads(json_data))
        sock.sendto(bytes(json_data, "utf-8"), (HOST, PORT))
        received = sock.recv(16)
    except: print("FAIL")

sock.close()
In einer Konsole startet man kontakt.py, in einer anderen client.py

Dann kann man vom Client aus die Einträge, Vorname, Name und Telefon setzen mit:

"VNAME","MeinVorname"
"NAME","MeinName"
"TEL","MeinTelefon"
Benutzeravatar
Kebap
User
Beiträge: 776
Registriert: Dienstag 15. November 2011, 14:20
Wohnort: Dortmund

allrounder93 hat geschrieben:wie ich in Python einen Netzwerkdienst implementiere, um über eine SSH-Verbindung Befehle bzw. Parameter an ein Python-Skript zu übergeben.
Was spricht dagegen, sich per SSH dort anzumelden, und das Python-Skript samt Parametern dort zu starten?
MorgenGrauen: 1 Welt, 8 Rassen, 13 Gilden, >250 Abenteuer, >5000 Waffen & Rüstungen,
>7000 NPC, >16000 Räume, >200 freiwillige Programmierer, nur Text, viel Spaß, seit 1992.
BlackJack

@Alfons Mittelmeyer: Der Server übertreibt es mit dem threading ein bisschen. Der Hauptthread ist am Ende während der Server in einem eigenen Thread läuft. Das ist unnötig komplex.

Und beim Client wird unnötigerweise eine Liste um die Eingabe gesetzt, so dass man gar keine Einzelwerte übergeben könnte.

Und das Problem mit UDP bleibt bestehen das der Client hängen bleibt sobald eines der beiden Pakete verloren geht. Was bei etwas höherer Netzauslastung durchaus passiert.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@Alfons:
Dein missionarischer Eifer für Dein eval & Tk nervt. Warum kaperst Du diesen Thread damit? Wir haben Sektionen für Codeschnipsel und Projektideen.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

jerch hat geschrieben:@Alfons:
Dein missionarischer Eifer für Dein eval & Tk nervt. Warum kaperst Du diesen Thread damit? Wir haben Sektionen für Codeschnipsel und Projektideen.
Sorry da war nirgends ein eval. Ich habe es ja wieder heraus gemacht, weil man auch direkt Json eingeben kann. Und mit Tk hat das wenig zu tun, sondern ist Netzwerk. Die tk Applikation war nur ein Bespiel. Der Proxy hat auch nichts mit Tk zu tun. Kann man aber auch für Tk prima nutzen.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

BlackJack hat geschrieben:@Alfons Mittelmeyer: Der Server übertreibt es mit dem threading ein bisschen. Der Hauptthread ist am Ende während der Server in einem eigenen Thread läuft. Das ist unnötig komplex.
Nein das muss so sein, habe die ganze Sache noch erweitert. Aus dem Server ist ein Sende- und Empfangsteil für eine Applikation geworden, umfasst jetzt Client und Server. Und das müssen eigene Threads sein. Es gibt nämlich einen zentralen Thread, über den andere Threads miteinander kommunizieren. Und wenn man für Sender und Empfänger keine eigenen Threads macht, blockiert man die zentrale Kommunikation mit langsameren Requests.

Und jetzt können beliebig viele Anwendungen miteinander kommunizieren. Da habe ich nämlich jetzt noch einen Router gemacht, mit dem sich andere Applikationen verbinden können.
Der Router hat eine feste Port Adresse. Wenn eine Applikation über ihren Client ihm diese Message schickt: "PORT?", dann bekommt sie eine Portadresse vom Router mitgeteilt und richtet dann ihren Server ein mit dieser Portadresse.

Applikationen schicken dann an den Router Listen, welche Messages sie empfangen möchten und geben dabei noch ihre Port Adresse an. Wenn der Router eine Message bekommt, dann ist die Portadresse der Empfänger registriert und er kann die Message an sie weiter schicken.

Habe vorhin einmal das ausprobiert mit folgenden vier Applikationen:

- Router
- Client
- GUI Applikation kontakt.py
- GUI Applikation kontakt2.py (fast dasselbe)

Der Client schickt wie vorher seine Messages, aber die gehen zum Router
Beim Router ist der Empfänger von kontakt.py für folgende Messages registriert: VNAME, NAME, TEL, daher werden die Entry Einträge entsprechend gefüllt wie vorher.

Doch, die Routinen zum Füllen der Entry-Felder senden nun gleichzeitig die Messages VNAME2, NAME2, TEL2
Diese gehen dann wieder zum Router und bei dem hat sich die Applikation kontakt2.py dafür registriert. So werden dann bei dieser Applikation auch die Einträge entsprechend gefüllt.
BlackJack hat geschrieben:Und beim Client wird unnötigerweise eine Liste um die Eingabe gesetzt, so dass man gar keine Einzelwerte übergeben könnte.
Man kann den Client doch anpassen, damit man es so eingeben kann, wie man will. Nur dann wird die Message als ein Tuple auf einer Queue abgelegt: (MessageID,Message)
Und wenn wan mehrere Parameter braucht, sieht das dann so aus: (MessageID,(Par1,Par2,Par3))

Die Funktionen erhalten dann die Message als einen Parameter. Wenn jemand seine Funktionen mit den Parametern getrennnt aufrufen will, dann kann er die Callbackfunktion ja so angeben: lambda message: function(*message)

Kannn doch jeder so machen, wie er will, oder? Nur auf die Queue muss es als ein Wert - und zwar als Tuple mit zwei Elementen.
BlackJack hat geschrieben:Und das Problem mit UDP bleibt bestehen das der Client hängen bleibt sobald eines der beiden Pakete verloren geht. Was bei etwas höherer Netzauslastung durchaus passiert.
Hatte es mit TCP probiert, nur da blieb der Client immer wegen "broken pipe" hängen. Naja, vielleicht bekomme ich ja noch heraus, woran das lag.

Naja, wenn der Client bei jeder geringfügigen Übertragung die Verbindung jedesmal aufbaut und abbaut, könnte es klappen. Aber richtig ist das auch nicht. Das Problem ist ja, dass es keine Requests sind sondern nur Datenübermittlung an andere Applikationen - die wenn überhaupt, irgendwann später vielleicht etwas zurücksenden. Und bei TCP müßte dann der Router wohl sich alle Verbindungen merken, wenn ich sie nicht jedesmal neu aufbaue.
BlackJack

@Alfons Mittelmeyer: Also in der Version in der ich den zusätzlichen Thread unnötig finde muss das nicht so sein, da ist das einfach nur ein zusätzlicher Thread während sich der Hauptthread damit langweilt darauf zu warten das dieser Zusätzliche beendet ist.

Wenn Du jetzt noch einen Router implementierst dann sind wir aber wirklich bei einem System angelangt wo man etwas bereits vorhandenes verwenden kann. Also eine der vielen Message-Queues.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@BlackJack Warum soll ich vorhandenes nehmen, wenn sich das hier bestens in mein System einpasst. Der Router ist ganz einfach, besteht nur aus zwei kleinen Teilen.
Nämlich dem Emfangsteil router.py:

Code: Alles auswählen

import socketserver
import json

import router_proxy
import threading

port_address = 8888

HOST, PORT = "localhost", port_address

port_address_lock = threading.Lock()
 
class MyUDPHandler(socketserver.BaseRequestHandler):
 
	def handle(self):
	   
		global port_address
		data = str(self.request[0], "utf-8")
		if data == "PORT?":
			port = port_address + 1
			with port_address_lock: port_address = port
			socket = self.request[1]
			socket.sendto(port.to_bytes(2,byteorder='big'), self.client_address)
		else:
			message = json.loads(data)
			router_proxy.proxy.receive_extern(message)
			socket = self.request[1]
			socket.sendto(bytes('OK', "utf-8"), self.client_address)

print("Router",HOST,PORT)
server = socketserver.UDPServer((HOST, PORT), MyUDPHandler)
try: server.serve_forever()
except: pass
Bei der Message "PORT?" wird eine Portadresse zurückgesendet. Ansonsten wird die Message dem Sendeteil übergeben.

Sendeteil router_proxy.py:

Code: Alles auswählen

import socket
import threading
import proxy as myproxy
import json

HOST = "localhost"

proxy = None

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

class MyThread(threading.Thread):

    def do_send(self,message_with_port):
        port = message_with_port[0]
        message = message_with_port[1]
        data = json.dumps(message)
        sock.sendto(bytes(data, "utf-8"), (HOST, port))
        sock.recv(16)

    def register_messages(self,message_with_port):
        port = message_with_port[0]
        message_ids=message_with_port[1]
        for mid in message_ids: self.proxy.do_receive(port,mid,self.do_send,port)

    def run(self):
        global proxy
        self.proxy = myproxy.Proxy()
        proxy = self.proxy
        self.proxy.do_receive(None,"ROUTE",self.register_messages)
        self.proxy.do_receive(None,"UNROUTE",self.proxy.undo_receiveAll)
        self.proxy.loop()

mythread = MyThread()
mythread.daemon = True
mythread.start()
Dieser Teil behandelt die Message "ROUTE". Diese Message enthält die Portadresse einer Anwendung und eine Liste der Messages, die sie erhalten will. Und diese Message wird behandelt durch die Methode register_messages. Dort werden dann die Message IDs mit ihrer Portadresse registriert, wobei dann für deren Behandlung die Methode do_send angegeben wird.

Und do_send sendet dann diese Messages an die Empfänger mit der betreffenden Portadresse.

Weiss nur nicht, ob das sock.recv(16) für etwas gut ist, braucht man doch nicht und kostet nur Wartezeit. Naja, wenigstens kommt es dann nicht zur Überlastung durch Messages.
Antworten