UDP Server mit QUdpSocket

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
DL3AD
User
Beiträge: 68
Registriert: Montag 31. August 2015, 19:03

Hallo in die Runde,
ich versuche gerade eine Testanwendung zu bauen mit der ich per UDP im localen LAN kleine Befehle als String verschicken und empfangen kann.
der Client ist ein kleines Modul LAN <=> UART . Das Modul funktioniert - getestet mit einem Packetsender und einer alten Anwendung in Objektpascal.

... das habe ich als Beispiel mal probiert - und das funktioniert soweit

Code: Alles auswählen

import socket

localIP = "192.168.2.116"
localPort = 10005
bufferSize = 1024
msgFromServer = "Hello UDP Client"
bytesToSend = str.encode(msgFromServer)

# Create a datagram socket
UDPServerSocket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)

# Bind to address and ip
UDPServerSocket.bind((localIP, localPort))
print("UDP server up and listening")

# Listen for incoming datagrams
while True:
    bytesAddressPair = UDPServerSocket.recvfrom(bufferSize)
    message = bytesAddressPair[0]
    address = bytesAddressPair[1]
    clientMsg = "Message from Client:{}".format(message)
    clientIP = "Client IP Address:{}".format(address)

    print(clientMsg)
    print(clientIP)

    # Sending a reply to client
    UDPServerSocket.sendto(bytesToSend, address)
Nun möchte ich das ganze mit einer QT-Anwendung machen.
Wenn z.B. ein Datagramm vom Client kommt sollte ein event gefeuert werden.

Code: Alles auswählen

#==============================================================================
# Hauptfenster der Anwendung
#==============================================================================

# Import Anweisungen ==========================================================
from PySide6.QtWidgets import QMainWindow
from ui_frm_main import Ui_FrmMain
from PySide6.QtNetwork import QUdpSocket, QHostAddress


# Implementierungen ===========================================================
class FrmMain(QMainWindow, Ui_FrmMain):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.Btn_send.clicked.connect(self.btn_senden)  # Btn Daten senden
        self.Btn_clear_tx.clicked.connect(self.btn_clear_edit_tx) # Btn TX Edit leeren
        self.Btn_clear_rx.clicked.connect(self.btn_clear_edit_rx) # Btn RX Edit leeren
        self.show()

        self.udpSocket = QUdpSocket(self)
        self.udpSocket.bind()  <=== hier komme ich nicht weiter


    # Btn Daten senden ========================================================
    def btn_senden(self):
        self.TextEdit_rx.setText("Hallo")
        print("senden")

    # Btn TX Edit leeren ======================================================
    def btn_clear_edit_tx(self):
        print("clear edit tx")

    # Btn RX Edit leeren ======================================================
    def btn_clear_edit_rx(self):
        print("clear edit rx")

Suche ein Beispiel bzw. Hilfe

Frank
Benutzeravatar
__blackjack__
User
Beiträge: 14246
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@DL3AD: Der Python-Code sieht irgendwie nicht wirklich nach Python aus. Angefangen mit der Namensschreibweise. In Python klein_mit_unterstrichen für alles ausser Konstanten (KOMPLETT_GROSS) und Klassen (PascalCase). Es gibt Konstanten die nicht als solche zu erkennen sind, und `UDPServerSocket` wäre in Python der Name für eine Klasse.

Methoden sollte man auf dem Exemplar aufrufen, und nicht auf der Klasse. Also ``"...".encode()`` statt ``str.encode("...")``. Wobei man sich das auch sparen kann wenn die Zeichenkette nur ASCII-Zeichen enthält, denn dann kann man gleich ein `bytes`-Literal in den Quelltext schreiben.

Socket-Objekte sind Kontextmanager, das sollte man nutzen damit das geschlossen wird wenn man das Programm beispielsweise mit Strg+C abbricht.

`bytesAddressPair` ist überflüssig. Statt danach dann per magischen Indexwerten auf die beiden Elemente zuzugreifen und die einzeln an Namen zu binden, würde man das in Python einfach gleich an die beiden Namen zuweisen.

Die `format()`-Methode nimmt man seit dem es f-Zeichenketten nur noch wenn die Vorlage nicht direkt für den Aufruf verwendet wird, sondern wenn das variabel ist. Und es macht auch nicht wirklich Sinn das hier als Zwischenschritt zu machen und an Namen zu binden, die sowieso nur einmal in einem sofort darauf folgenden `print()` verwendet werden.

Zwischenstand:

Code: Alles auswählen

#!/usr/bin/env python3
import socket

LOCAL_ADDRESS = ("192.168.2.116", 1005)
BUFFER_SIZE = 1024
RESPONSE = b"Hello UDP Client"


def main():
    with socket.socket(
        family=socket.AF_INET, type=socket.SOCK_DGRAM
    ) as udp_server_socket:
        udp_server_socket.bind(LOCAL_ADDRESS)
        print("UDP server up and listening.")
        while True:
            data, address = udp_server_socket.recvfrom(BUFFER_SIZE)
            print(f"Message from Client: {data!r}")
            print(f"Client IP Address: {address!r}")
            udp_server_socket.sendto(RESPONSE, address)


if __name__ == "__main__":
    main()
Was genau ist denn das Problem beim Qt-Programm? `bind()` fehlen Argumente, die sind doch aber in der Dokumentation beschrieben und *sehr* ähnlich denen, die man beim `socket.bind()` übergibt. Sind halt zwei einzelne Werte und kein Tupel, und die IP-Adresse muss noch mal in einem `QHostAddress`-Objekt gekapselt werden.
“All tribal myths are true, for a given value of 'true'.” — Terry Pratchett, The Last Continent
DL3AD
User
Beiträge: 68
Registriert: Montag 31. August 2015, 19:03

... bin nun ein Stück weiter gekommen.
Beim bind war ich nur zu blöd :mrgreen:

Code: Alles auswählen

#==============================================================================
# Hauptfenster der Anwendung
#==============================================================================

# Import Anweisungen ==========================================================
from PySide6.QtWidgets import QMainWindow
from ui_frm_main import Ui_FrmMain
from PySide6.QtNetwork import QUdpSocket, QHostAddress


# Implementierungen ===========================================================
class FrmMain(QMainWindow, Ui_FrmMain):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.Btn_send.clicked.connect(self.btn_senden)  # Btn Daten senden
        self.Btn_clear_tx.clicked.connect(self.btn_clear_edit_tx) # Btn TX Edit leeren
        self.Btn_clear_rx.clicked.connect(self.btn_clear_edit_rx) # Btn RX Edit leeren
        self.show()

        self.udpSocket = QUdpSocket(self)
        self.udpSocket.bind(QHostAddress("192.168.2.116"), 10005)
        self.udpSocket.readyRead.connect(self.read_pending_datagrams)

    def read_pending_datagrams(self):
        while self.udpSocket.hasPendingDatagrams():
            data = self.udpSocket.receiveDatagram()
            
Daten kommen schon mal an - mit print(data) kommt die Objektaddresse.
data ist wohl ein QNetworkDatagram - wie komme ich an den Inhalt, mit der QT Beschreibung kann ich nix anfangen.
Benutzeravatar
sparrow
User
Beiträge: 4596
Registriert: Freitag 17. April 2009, 10:28

Es scheint, dass du der Dokumentation hier folgst.
Und dort ist sogar im Beispiel "QNetworkDatagram" anklickbar und führt zu dessen Dokumentation. Das hast du gesehen? Denn darin gibt es auch wieder ein Beispiel.
DL3AD
User
Beiträge: 68
Registriert: Montag 31. August 2015, 19:03

sorry das bringt mich nicht weiter.

Wie bekomme ich aus "data" die Daten raus ?
Benutzeravatar
sparrow
User
Beiträge: 4596
Registriert: Freitag 17. April 2009, 10:28

Dann zeig doch mal, was du versuchst, und erkläre, was du an der Dokumentation zu data nicht verstehst.
Benutzeravatar
__blackjack__
User
Beiträge: 14246
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@DL3AD: Das ist laut Dokumentation ein `QByteArray`-Objekt. Das ist auch dokumentiert. Spiel doch einfach mal in einer Python-Shell damit herum und probiere die einzelnen Methoden aus, um das Objekt kennen zu lernen.
“All tribal myths are true, for a given value of 'true'.” — Terry Pratchett, The Last Continent
DL3AD
User
Beiträge: 68
Registriert: Montag 31. August 2015, 19:03

... habe nun folgendes versucht

Code: Alles auswählen

    def read_pending_datagrams(self):
        while self.udpSocket.hasPendingDatagrams():
            data = self.udpSocket.receiveDatagram()
            print(data[0])
            print(data[1])

Dann kommt folgende Fehlermeldung


Traceback (most recent call last):
File "/home/elko/PyProject/UDP_Test01/Form1.py", line 28, in read_pending_datagrams
print(data[0])
~~~~^^^
TypeError: 'PySide6.QtNetwork.QNetworkDatagram' object is not subscriptable
DL3AD
User
Beiträge: 68
Registriert: Montag 31. August 2015, 19:03

... da hier funktioniert nun

Code: Alles auswählen

    def read_pending_datagrams(self):
        while self.udpSocket.hasPendingDatagrams():
            data = self.udpSocket.readDatagram(1024)
            #data = self.udpSocket.receiveDatagram()
            print(str(data[0]))
            print(data[1])
Mir ist allerdings der Unterschied zwischen readDatagram und receiveDatagram nicht klar.
Benutzeravatar
__blackjack__
User
Beiträge: 14246
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@DL3AD: Die haben halt unterschiedliche Rückgabewerte. Die eine Methode hat das Ergebnis in einem Tupel — an der Stelle ist `data` auch kein guter Name, Du solltest Dir klar machen *was* da zurück kommt, und das gleich an passende Namen binden, statt da wieder mit magischen Indexwerten zu arbeiten. Du brauchst die anderen Werte ja auch wenn das Programm das gleiche tun soll wie das Beispiel mit `socket` aus der Python-Standard-Bibliothek. Die andere andere Methode hat das Ergebnis in einem Objekt verpackt. Auch wieder: Dokumentation. Und ausprobieren. Interessant wäre beispielsweise was die Python-Anbindung machen wenn die jetzt verwendete Methode im Original -1 zurückgeben würde. Auch hier: einfach mal in einer Python-Shell herum spielen und ausprobieren.

Und es gibt einen Unterschied bei der maximalen gelesenen Grösse der Daten. Bei dem auskommentierten Aufruf wird da nichts angegeben.
“All tribal myths are true, for a given value of 'true'.” — Terry Pratchett, The Last Continent
Antworten