Externe Übergabe von Parametern an mein Python-Skript

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
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.
BlackJack

@Alfons Mittelmeyer: Wie schon gesagt: UDP-Pakete müssen nicht beim Empfänger ankommen und das ist nicht nur theoretisch so, sondern kommt in der Praxis durchaus vor. Ein schöner Grund etwas vorhandenes zu nehmen: Das funktioniert zuverlässig und ist schon in der Praxis getestet. Und man müsste sich das nicht aus verschiedenen Beiträgen in einem Forum zusammenkopieren wenn man es benutzen möchte.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@BlackJack Das mit UDP war ja nur mal zum Ausprobieren und sollte auf dem eigenen PC über localhost wohl auch sicher sein. Und für Anderes würde man dann etwas Bewährtes nehme. Da bräuchte man ja nur das Empfangsteil austauschen und ein paar Zeilen im Sendeteil. Hier dieses Sende und Empfangsteil:

Code: Alles auswählen

import socket
import socketserver
import threading
import json
import extern_proxy
import proxy as myproxy

HOST, PORT = "localhost", 8888
MYPORT = 0
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# Sender gets port for receiver ==================================

sock.sendto(bytes("PORT?", "utf-8"), (HOST, PORT))
MYPORT = int.from_bytes(sock.recv(16), byteorder='big')


# RECEIVER ===== receives from router =========================

class MyUDPHandler(socketserver.BaseRequestHandler):
 
	def handle(self):
	   
		message = json.loads(str(self.request[0], "utf-8"))
		extern_proxy.proxy.receive_extern(message)
		socket = self.request[1]
		socket.sendto(bytes('OK', "utf-8"), self.client_address)



class ServerThread(threading.Thread):

    def run(self):
        print("Receiver",HOST,MYPORT)
        server = socketserver.UDPServer((HOST, MYPORT), MyUDPHandler)
        server.serve_forever()

receiverthread=ServerThread()
receiverthread.daemon = True
receiverthread.start()


# SENDER ===== sends to router - own thread, because shouldn't block tranmissions between threads ==


proxy = None

class MyThread(threading.Thread):

    def send_to_router(self,message):
        data=json.dumps(message)
        sock.sendto(bytes(data, "utf-8"), (HOST, PORT))
        sock.recv(16)

    def do_send_to_router(self,message_ids):
        proxy.do_receive_extern(message_ids)
        for mid in message_ids: proxy.do_receive(None,mid,self.send_to_router,True)

    def do_receive_from_router(self,message_ids):
        message = ("ROUTE",(MYPORT,message_ids))
        self.send_to_router(message)

    def run(self):
        global proxy
        self.proxy = myproxy.Proxy(extern_proxy.proxy)
        proxy = self.proxy
        self.proxy.loop()

mythread = MyThread()
mythread.daemon = True
mythread.start()

def do_send_to_router(message_ids):
	proxy.send_extern_highprio("execute_function",lambda: mythread.do_send_to_router(message_ids))
	
def do_receive_from_router(message_ids):
	proxy.send_extern_highprio("execute_function",lambda: mythread.do_receive_from_router(message_ids))
Auszutauschen wäre das, nämlich dass man eine Port und/oder IP Adresse erhält - im Falle eines eigenen Routers.

Wenn man etwas anderes nimmt, geschieht das wohl von selbst. Nur man braucht diese Identifikation, damit der Router die Messages für diese Anwendung dieser Anwendung schicken kann.

Code: Alles auswählen

# Sender gets port for receiver ==================================
sock.sendto(bytes("PORT?", "utf-8"), (HOST, PORT))
MYPORT = int.from_bytes(sock.recv(16), byteorder='big')
Auszutauschen wäre der Empfang. Wichtig ist nur der Ersatz für:

Code: Alles auswählen

message = json.loads(str(self.request[0], "utf-8"))
extern_proxy.proxy.receive_extern(message)
Und auszutauschen im Sendeteil wäre:

Code: Alles auswählen

sock.sendto(bytes(data, "utf-8"), (HOST, PORT))
sock.recv(16)
Erläuterungen:

do_send_to_router erhält eine Liste der Message IDs die zum Router gehen sollen. Diese Message IDs werden eingetragen bei der Zentralen Routing Task für Messageaustausch zwischen Treads, damit diese diese Messages der Queue des Sendeteils übergibt: proxy.do_receive_extern(message_ids)

Dann werden diese Message IDs im Sendeteil eingetragen, damit diese Messages an den Router geschickt werden: for mid in message_ids: proxy.do_receive(None,mid,self.send_to_router,True)

do_receive_from_router sendet an den Router eine Message mit der ID "ROUTE", die Port Adresse des Empfangsteil und eine Liste der Message IDs, die der Router an das Empfangsteil senden soll.
Das Empfangsteil übergibt empfangene Messages der Queue der zentralen Message Routing Task (zwischen Threads) - extern_proxy.proxy

Vielleicht sollte ich bei do_send_to_router noch einen zusätzlichen Parameter machen - den owner (self des Threads) - jetzt auf None gesetzt, für den Fall, dass man einen Thread beendet. Dann kann dieser Thread die im zugeordneten Messages zum Router wieder austragen.

do_receive_from_router gilt allerdings applikationsweit, da der Router nur den Port der Anwendung hat und keine Referenz auf einen Thread. Aber da kann auch nichts passieren. Wenn man diese Messages nicht austrägt, werden sie sowieso intern nicht weiter geleitet.

Implementieren muss ich noch eine Funktion, nämlich die Abmeldung der Applikation beim Router mittels "UNROUTE". Das sollte bei einer GUI Anwendung nach der mainloop geschehen und sollte auf Rückmeldung warten. Also eine Pollschleife für die Rückmeldung. Die Frage ist allerdings, was bei gewaltsamem Abbruch geschieht, etwa Stoppen des Prozesses.

Naja, dann kann der Router eben nicht mehr an diese Port Adresse senden - und könnte in diesem Falle selbst die Message IDs austragen, falls diese noch von einer anderen Anwendung gesendet werden.

Also try für senden und timeout für warten auf Bestätigung und bei den eingetragenen Messages muss man auch den Owner mit berücksichtigen, sonst löscht man diese Messages für alle - zur Zeit werden nur die Callbacks berücksichtigt. Das ist noch ein Fehler - für das externe Routing.

Also beim Router ist das falsch: self.proxy.do_receive(None,"UNROUTE",self.proxy.undo_receiveAll)
Und Messages zum Router müssen auch pro Owner eingetragen werden aber nur einmal aufgerufen werden - damit ein sich abmeldender Thread nicht diese Message auch für andere Thread abmeldet. Aber man kann sie auch drin lassen, stört ja nicht - ist wohl das Einfachste.

Die Lösung wäre statt Callback auch ein Tuple aus Callback und Owner zulassen. Nein, jetzt habe ich es: Ownersdictionary pro Callback mit Referenzzähler auf die Owner bzw. Dictionary empty
Das heißt, dass dann der optionale Parameter auch ein Tuple sein kann, welches zusätzlich ein Owners Dictionary hat.

Das kann ich dann auch mal testen. Zur Zeit kommt ein und dieselbe Message wohl nur bei einer Applikation an, weil die Applikationen die MessageID beim Router überschreiben Ja, ist so!

Also, mir geht es nicht um TCP, UDP oder sonst etwas, sondern um die Signal Framework Logik.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Fall gelöst mithilfe der Funktion do_receive_options: self.proxy.do_receive_options(port,mid,self.do_send,port)
Die option ist hier der letzte Parameter port. Dabei wird der optionale Parameter hier nicht überschrieben, sondern in ein Dictionary für den optionalen Parameter eingetragen.
Der Router kann somit an alle ports für diese Message senden.

Code: Alles auswählen

    # register message id, callback and optional_parameter in the message id dictionary
    def _register_options(self,msgid,callback,optional_parameter=False):
        if msgid not in self._Dictionary: self._Dictionary[msgid] = {}
        if callback not in self._Dictionary[msgid]: self._Dictionary[msgid][callback] = {}
        self._Dictionary[msgid][callback][optional_parameter] = None
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@allrounder93 Hi, hab das Ganze wohl ein wenig übertrieben. Hast Du es jetzt mit TCP gemacht? Und wie sieht Deine Lösung aus?
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

BlackJack hat geschrieben: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.
@BlackJack Was wäre denn zu empfehlen, wenn man über TCP/IP, Intranet und Internet gehen will?

Oder reicht es, über TCP zu gehen? Statt Portadresse könnte man IP Adresse + Port nehmen. Dann müßte das auch zwischen verschiedenen Rechnern funktionieren. Hab aber nur eine einzige LAN Verbindung. Oder gibt es eine Smartphone Applikation, zu der man zu Testzwecken etwas übertragen kann?

Gut wäre so etwas wie ein Android Message Router.
Antworten