IPC zwischen Python2 und Python3

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Marco82
User
Beiträge: 19
Registriert: Samstag 4. April 2020, 21:07

Hallo zusammen,

ich habe ein Problem für das ich im Forum und mit Google keine für mich verständliche Lösung gefunden habe.

Die Aufgabenstellung:
Ich muss eine komfortable, bidirektionale Interprozess-Kommunikation zwischen zwei Python Prozessen aufsetzten. Da sich die Prozesse später auf zwei verschiedenen Rechnern befinden werden, werde ich wohl auf eine Art Socket-Kommunikation zurückgreifen müssen. Dabei läuft ein Prozess auf Python 2.7.15 und der andere auf Python 3.7.3. Ja ich weiß, Python 2 wird nicht mehr gewartet. Aber es handelt sich dabei um eine alte Industrie-Anlage und ich habe keinen Einfluss auf die installierten Laufzeitumgebungen.

Bei meiner Recherche bin ich auf folgende Lösung gestoßen: https://stackoverflow.com/questions/692 ... -in-python.
Besonders gut gefällt mir daran, dass ich beliebige Python Objekte übertragen kann ohne mich um Serialisierung / Deserialisierung kümmern zu müssen.

Die auf multiprocessing.connection basierende Lösung funktionierte auch einwandfrei... bis ich einen Prozess in einer Python 2.7 Umgebung betreiben wollte...

Zum Problem:
Sobald ich im Python 2 Prozess Daten empfangen möchte, bekomme ich folgende Meldung angezeigt:

Code: Alles auswählen

Exception in thread Thread-1:
Traceback (most recent call last):
  File "C:\Python27\lib\threading.py", line 801, in __bootstrap_inner
    self.run()
  File "C:/_repo/IPC_Receiver/multi/async_listener.py", line 23, in run
    msg = conn.recv()
ValueError: unsupported pickle protocol: 4
Soweit so gut. Die multiprocessing.connection Bibliothek von Python 3 schein ein neueres Pickle Format zu verwenden als die aus Python 2.
Jetzt dachte ich mir so: "Es kann ja nicht so schwer sein der Send() Methode (von multiprocessing.connection -> Listener oder Client) beizubringen ein älteres Pickle Format zu verwenden..."
... und hier stehe ich jetzt und finde keine Lösung. :cry:

Zur Frage:
a) Weiß jemand, wie man Python 3 multiprocessing.connection davon überzeugt ein älteres Pickle Format zu verwenden?
b) Hat jemand eine bessere Lösung für meine in der Aufgabenstellung beschriebene Anforderung?

Ich habe schon einen halben Tag in die Suche einer Antwort auf Frage a) investiert.
Lösung a) ist abgesehen vom Problem mit dem Pickle Format fertig. Darum würde ich Lösungsansätze die darauf basieren bevorzugen.

Hoffe mir kann jemand den entscheidenden Tipp geben. Vielen Dank im Voraus!
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

A) gar nicht. Das ist schlicht kein gangbarer Weg. Multiprocessing ist nicht designed als transparente middleware mit zugesicherten Eigenschaften. Pickle ist nur die erste Hürde. Andere Details werden folgen. Und zu guter letzt ist das AFAIK auch nur IPC, aber nicht RPC. Nachtrag: dein SO-Artikel zeigt ja RPC. Ändert meine Einschätzung allerdings nicht.

B) XMLRPC. Es ist ein bisschen aus der Mode gekommen, aber ist eingebaut und einfach. Du wirst dich aber von deinem Traum beliebiger Objekte verabschieden müssen, und stattdessen die üblichen Datenstrukturen und simplen Typen nutzen.
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

a) Bin mir ehrlich gesagt nicht sicher, da ich das noch nicht versucht habe. Ich würde versuchen statt send(), das entsprechende Objekt erst selber mit dem kompatiblen Protokoll zu "pickeln" und dann über send_bytes() zu übertragen.
b) Falls es möglich ist 3rd Party Pakete zu installieren: PyZMQ
https://pyzmq.readthedocs.io/en/latest/ ... ialization
Marco82
User
Beiträge: 19
Registriert: Samstag 4. April 2020, 21:07

Danke für die Antworten. Aber einen Aussage verstehe ich nicht.

Du schreibst:
__deets__ hat geschrieben: Mittwoch 4. August 2021, 07:18 A) gar nicht. Das ist schlicht kein gangbarer Weg. Multiprocessing ist nicht designed als transparente middleware mit zugesicherten Eigenschaften. Pickle ist nur die erste Hürde. Andere Details werden folgen. Und zu guter letzt ist das AFAIK auch nur IPC, aber nicht RPC. Nachtrag: dein SO-Artikel zeigt ja RPC. Ändert meine Einschätzung allerdings nicht.
multiprocessing.connection verwendet Sockets für die Kommunikation. In wie fern soll dieser Ansatz "nur IPC" sein?

Kannst Du bitte erläutern was Du damit meinst?
Marco82
User
Beiträge: 19
Registriert: Samstag 4. April 2020, 21:07

Vielen Dank, Deine Antworten gefallen mir sehr gut.
rogerb hat geschrieben: Mittwoch 4. August 2021, 07:33 a) Bin mir ehrlich gesagt nicht sicher, da ich das noch nicht versucht habe. Ich würde versuchen statt send(), das entsprechende Objekt erst selber mit dem kompatiblen Protokoll zu "pickeln" und dann über send_bytes() zu übertragen.
b) Falls es möglich ist 3rd Party Pakete zu installieren: PyZMQ
https://pyzmq.readthedocs.io/en/latest/ ... ialization
Mit send_bytes() kann ich einfach meinen eigenen Serializer verwenden. Den Serializer von multiprocessing.connection Listener und Client austauschen zu können wäre für mich eine Luxuslösung gewesen an die ich sowieso schon fast nicht mehr geglaubt habe.


Das werde ich jetzt mal ausprobieren. Tausend Dank! :D
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

@Marco82: ich hab’s ja nochmal nachgetragen. Das war eine Fehleinschätzung, offensichtlich kann man das anders gestalten. Wobei ich das immer noch nicht für besonders geschickt halte, weil multiprocessing ultimativ als echt-paralleler Ersatz für Pythons durch das GIL beschränkte dacht ist. Nicht als IPC Lösung.
Benutzeravatar
DeaD_EyE
User
Beiträge: 1240
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Wenn du das Publisher/Subscriber-Pattern anwendest, musst das sowieso selber serialisieren und mit folgender Methode senden:
https://pyzmq.readthedocs.io/en/latest/ ... _multipart

Der Subscriber subscribed ein Topic und die interne Logik vergleicht das einfach mit dem ersten Frame. Wenn man aber diese Helfer-Funktionen nutzt, geschieht das nicht und der Subscriber würde die Nachricht auch nicht bekommen. Es gibt noch andere Muster: https://learning-0mq-with-pyzmq.readthe ... terns.html


Hier mal ein minimales Beispiel mit Publisher/Subscriber.
Server:

Code: Alles auswählen

import time
import msgpack
from zmq import Context, PUB

data = {"counter": 0}


with Context() as ctx:
    with ctx.socket(PUB) as sock:
        sock.bind("tcp://127.0.0.1:5001")
        while True:
            time.sleep(0.01)
            sock.send_multipart([b"server>client", msgpack.dumps(data)])
            data["counter"] += 1
Client:

Code: Alles auswählen

import msgpack
from zmq import Context, SUB


with Context() as ctx:
    with ctx.socket(SUB) as sock:
        sock.connect("tcp://127.0.0.1:5001")
        sock.subscribe(b"server>client")
        while True:
            topic, payload = sock.recv_multipart()
            print(msgpack.loads(payload))
Das Witzige ist, dass man den Publisher oder den Client als Server nutzen kann. In diesem Beispiel ist der Publisher der Server. Dort wird die Methode bind verwendet.
Beim Client ist es die Methode connect, die sich dann mit dem Server verbindet.

Der Vorteil ist, dass die Reihenfolge, wann welcher Dienst gestartet wird, keine Rolle spielt.
Man kann also zuerst den Client starten, der sich versucht mit einem nicht geöffnetem Port zu verbinden.
Sobald der Server läuft, verbindet sich der Client dann mit dem nachträglich geöffnetem Port.

Für die Serialisierung habe ich msgpack verwendet.

Du kannst auch einen Unix-Domain-Socket verwenden, in dem du den connect string anpasst:

Code: Alles auswählen

# server
sock.bind("ipc://unix.sock")


# client
sock.connect("ipc://unix.sock")
Auch hier gilt, dass man den Client z.B. als Erstes starten kann (unix.sock existiert noch nicht) und nachdem der Server gestartet ist, bekommt der Client dann auch die Nachrichten.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Marco82
User
Beiträge: 19
Registriert: Samstag 4. April 2020, 21:07

__deets__ hat geschrieben: Mittwoch 4. August 2021, 09:37 @Marco82: ich hab’s ja nochmal nachgetragen. Das war eine Fehleinschätzung, offensichtlich kann man das anders gestalten. Wobei ich das immer noch nicht für besonders geschickt halte, weil multiprocessing ultimativ als echt-paralleler Ersatz für Pythons durch das GIL beschränkte dacht ist. Nicht als IPC Lösung.
Genau das habe ich mir auch gedacht als, ich nach einer Socket-basierten IPC Lösung gesucht und dann auf multiprocessing gestoßen bin. In diesem Namespace hätte ich keine Socket Lösung erwartet. Wenn man nach Python IPC googelt stößt man eben öfter auf multiprocessing.
Marco82
User
Beiträge: 19
Registriert: Samstag 4. April 2020, 21:07

rogerb hat geschrieben: Mittwoch 4. August 2021, 07:33 a) Bin mir ehrlich gesagt nicht sicher, da ich das noch nicht versucht habe. Ich würde versuchen statt send(), das entsprechende Objekt erst selber mit dem kompatiblen Protokoll zu "pickeln" und dann über send_bytes() zu übertragen.
b) Falls es möglich ist 3rd Party Pakete zu installieren: PyZMQ
https://pyzmq.readthedocs.io/en/latest/ ... ialization
Ich habe jetzt mal Vorschlag a) umgesetzt. Funktioniert prima und der Code wirkt immer noch sehr intuitiv. Allerdings bin ich wie @__deets__ ein wenig misstrauisch dabei multiprocessing zur verwenden um zwischen zwei System zu kommunizieren.

Zu Vorschlag b)
Ja ich kann 3rd Party Pakete installieren, allerdings nur native Pakete. Compiler für das System habe ich leider keinen.
Aber PyZMQ hat sich installieren lassen. Jetzt schau ich mir mal das gute Stück mal an.
Marco82
User
Beiträge: 19
Registriert: Samstag 4. April 2020, 21:07

DeaD_EyE hat geschrieben: Mittwoch 4. August 2021, 09:42 Wenn du das Publisher/Subscriber-Pattern anwendest, musst das sowieso selber serialisieren und mit folgender Methode senden:
https://pyzmq.readthedocs.io/en/latest/ ... _multipart

Der Subscriber subscribed ein Topic und die interne Logik vergleicht das einfach mit dem ersten Frame. Wenn man aber diese Helfer-Funktionen nutzt, geschieht das nicht und der Subscriber würde die Nachricht auch nicht bekommen. Es gibt noch andere Muster: https://learning-0mq-with-pyzmq.readthe ... terns.html


Hier mal ein minimales Beispiel mit Publisher/Subscriber.
Server:

Code: Alles auswählen

import time
import msgpack
from zmq import Context, PUB

data = {"counter": 0}


with Context() as ctx:
    with ctx.socket(PUB) as sock:
        sock.bind("tcp://127.0.0.1:5001")
        while True:
            time.sleep(0.01)
            sock.send_multipart([b"server>client", msgpack.dumps(data)])
            data["counter"] += 1
Client:

Code: Alles auswählen

import msgpack
from zmq import Context, SUB


with Context() as ctx:
    with ctx.socket(SUB) as sock:
        sock.connect("tcp://127.0.0.1:5001")
        sock.subscribe(b"server>client")
        while True:
            topic, payload = sock.recv_multipart()
            print(msgpack.loads(payload))
Das Witzige ist, dass man den Publisher oder den Client als Server nutzen kann. In diesem Beispiel ist der Publisher der Server. Dort wird die Methode bind verwendet.
Beim Client ist es die Methode connect, die sich dann mit dem Server verbindet.

Der Vorteil ist, dass die Reihenfolge, wann welcher Dienst gestartet wird, keine Rolle spielt.
Man kann also zuerst den Client starten, der sich versucht mit einem nicht geöffnetem Port zu verbinden.
Sobald der Server läuft, verbindet sich der Client dann mit dem nachträglich geöffnetem Port.

Für die Serialisierung habe ich msgpack verwendet.

Du kannst auch einen Unix-Domain-Socket verwenden, in dem du den connect string anpasst:

Code: Alles auswählen

# server
sock.bind("ipc://unix.sock")


# client
sock.connect("ipc://unix.sock")
Auch hier gilt, dass man den Client z.B. als Erstes starten kann (unix.sock existiert noch nicht) und nachdem der Server gestartet ist, bekommt der Client dann auch die Nachrichten.
Da bin ich jetzt beeindruckt. Meine Implementierung von multiprocessing.connection hatte zwar einen ähnlichen Funktionsumfang
  • Publisher oder den Client als Server nutzen
  • die Startreihenfolge war auch egal
aber ich musste halt alles selbst implementieren.

Durch die kleine Exkursion im Namespace multiprocessing.connection habe ich wieder einiges gelernt. Ich werde jetzt aber doch auf PyZMQ setzen.

Vielen Dank an alle Beteiligten! :D
Antworten