Client-Server comm. in PyQt5

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Benutzeravatar
Finux
User
Beiträge: 32
Registriert: Mittwoch 18. September 2019, 13:59

wenn ich mit dem debugger durchgehe schmiert mir das window schon mit dem aufruf via show() ab
__deets__
User
Beiträge: 14543
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das ist aber die andere Richtung. Das abliefern der Ergebnisse. Es muss aber ein Slot von Communication getriggert werden. Wenn das zb Login sein soll, dass ja auch noch ein Argument will, brauchst du ein neues Signal in deiner GUI klasse, mit einem str Argument. Das connected zu dem login. Und dann emittest du das
Benutzeravatar
Finux
User
Beiträge: 32
Registriert: Mittwoch 18. September 2019, 13:59

vielen, vielen dank!

soweit bin ich successful, was mich jetzt aber noch wurmt ist die threading-geschichte:

ich möchte also in der lage sein via command_input etwas senden zu können. tue ich dies außerhalb meiner login-funktion, werde ich vom server als neuer client betrachtet, was natürlich allen zweck des login und der authentification verwirft. d.h. ich brauche (wenn das überhaupt klappen sollte..) den inhalt meiner conn - variable der Connection-Klasse und "fuddel" damit in der Window-Klasse rum, oder ich lasse conn brav in der Connection-Klasse, müsste dann aber dort einen Thread erstellen, damit ich (wie zu meinem stand vor der GUI) in der lage bin gleichzeitig receiven und senden zu können. ich würde hier ungern einen thread blockieren müssen, nur um befehle zu senden....

Code: Alles auswählen

import socket, ssl, pprint, sys
#import threading
import time
import hashlib
from PyQt5.QtWidgets import *
from PyQt5.QtGui import QPixmap, QFont
from PyQt5.QtCore import *


IP = '127.0.0.1'
PORT = 4600


class Communication(QObject):

    msg_changed = pyqtSignal(str)
    conn_var = pyqtSignal(ssl.SSLSocket) #TODO: ??? <class 'ssl.SSLSocket'>

    def __init__(self, u_name_input, u_pwd_input):
        QObject.__init__(self)
        self.u_name_input = u_name_input
        self.u_pwd_input = u_pwd_input

    def connect_to_server(self):
        context = ssl.SSLContext()
        context.verify_mode = ssl.CERT_REQUIRED
        context.check_hostname = True

        try:
            context.load_verify_locations("/home/fin/server.crt")
            print("***CHECK 1/4- Certificate loading successful")
        except (FileExistsError, FileNotFoundError) as e:
            print(e)
            print("::::: Program will be closed now! :::::")
            sys.exit()

        try:
            # with socket.create_connection((ip, port)) as s:
            self.conn = context.wrap_socket(socket.socket(socket.AF_INET, socket.SOCK_STREAM), server_hostname="127.0.0.1")
            print("***CHECK 2/4- Socket only supports ssl connection successful")
            try:
                self.conn.connect((IP, PORT))
                print("***CHECK 3/4- Connection to server successful")                
                print(type(self.conn))
                self.conn.sendall(b"Thanks for accepting the connection!")
                print("***CHECK 4/4- Bytestring sending successful")
            except:
                print("CONNECTION NOT POSSIBLE! IN 10 SECONDS TRYING TO CONNECT AGAIN..")
                time.sleep(10.0)
                return
        except ssl.CertificateError as e:
            # print("Error {}: {}".format(e.args[0], e.args[1]))
            # print("Error {}:".format(e))  #TODO: Darf Ziel-IP bekannt sein? (Fehlerprovokation bei Hackern) SICHERHEITSMANGEL!
            print("Hostname doesn't match.")
            print("::::: Program will be closed now! :::::")
            sys.exit()
        except ConnectionError as e:
            print(e)
            print("::::: Program will be closed now! :::::")
            sys.exit()


    def login(self):
        tell_auth = "username"

        self.connect_to_server()

        u_name_input = self.u_name_input.text()
        print(u_name_input)
        u_pwd_input = self.u_pwd_input.text()

        u_pwd_input = hashlib.sha512(bytes(u_pwd_input, "utf-8"))
        pwd_message = (u_pwd_input.hexdigest())

        self.conn.sendall(tell_auth.encode("utf-8"))
        self.conn.sendall(u_name_input.encode("utf-8"))
        self.conn.sendall(pwd_message.encode("utf-8"))

        try:
            while True:
                msg = self.conn.recv(4096).decode("utf-8")
                print("\n[{}]: {}".format(IP, msg))
                self.msg_changed.emit(msg)
                time.sleep(0.000001)
                if (msg == '') or (msg == "Authentication failed!"):
                    self.msg_changed.emit("LOST CONNECTION TO SERVER!")
                    self.msg_changed.emit("Please Login again!")
                    self.conn.close()
                    break

        except OSError as e:
            self.msg_changed.emit("Connection not possible. Please restart the program!")
            return
        # self.output.setText("TEST")
        # if msg == "Authentication succeeded!":
        #     # TODO: was passiert nach der succeed message?
        #     t1 = threading.Thread(target=self.msg_receive, args=(conn, IP))
        #     t1.start()
        #     t2 = threading.Thread(target=self.msg_print_send, args=(conn, IP))
        #     t2.start()
        #
        #     try:
        #         while 1:
        #             time.sleep(1)
        #     except KeyboardInterrupt as e:
        #         print(e)
        # elif msg == "Authentication failed!":
        #     self.setWindowTitle("LOGIN FAILED!" + self.u_name_input.text())


    # def msg_receive(self, conn, IP):
    #     try:
    #         while True:
    #             print("RECEIVE: ", i)
    #             msg = conn.recv(4096).decode("utf-8")
    #             print("\n[{}]: {}".format(IP, msg))
    #             time.sleep(0.000001)
    #             if msg == '':
    #                 print("LOST CONNECTION TO SERVER!")
    #                 print("TRYING TO RECONNECT... (Refresh Browser!)")  # TODO:
    #                 conn.close()
    #                 break
    #     except OSError as e:
    #         return

    # def msg_print_send(self, conn, IP):
    #     while True:
    #         print("SENDING: ", y)
    #         message = input("\nNachricht: ")
    #         conn.sendall(message.encode("utf-8"))
    #
    #         if message == 'exit':
    #             print("+++++ SOCKET CLOSED +++++")
    #             conn.close()
    #             sys.exit()  # TODO: skript beendet sich nicht
    #             break




class Window(QWidget):
    # def __init__(self):
    #     super().__init__()

    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.setWindowTitle("CSK Control - YOU NEED TO LOGIN!")
        self.setGeometry(200, 200, 1500, 700)
        self.UI()
        self.show()


    def UI(self):
        self.mainDesign()
        self.layout()


    def mainDesign(self):

        #self.login_btn()

        ######### left layout widgets #########
        self.u_name_label = QLabel("Username: ")
        self.u_name_input = QLineEdit(self)
        self.u_name_input.setPlaceholderText("Enter Username")
        self.u_pwd_label = QLabel("Password: ")
        self.u_pwd_input = QLineEdit(self)
        self.u_pwd_input.setEchoMode(QLineEdit.Password)
        self.u_pwd_input.setPlaceholderText("Enter Password")
        self.btn_login = QPushButton("LOGIN", self)
        # self.btn_login.clicked.connect(self.auth_checker.login_btn)
        # self.btn_login.clicked.connect(self.auth_checker.login)

        ######### middle layout widgets #########
        self.output = QTreeWidget(self)
        #self.output.setStyleSheet("background-color: white; font-size: 20pt")
        #self.output.setText("TEST")
            ######### middle layout bottom widgets #########
        self.command_label = QLabel(self)
        self.command_label.setText("Command: ")
        self.command_input = QLineEdit(self)
        self.command_input.setPlaceholderText("Enter server command here")

        ######### right layout widgets #########
        self.status = QLabel("STATUS")
        self.status.setStyleSheet("background-color: red; font-size: 16pt")
        #self.u_name_label.setStyleSheet('font-family:Arial Bold')

        self.login_btn()

        self.btn_login.clicked.connect(self.auth_checker.login)
        #self.command_input.returnPressed.connect(self.onClick)



    def layout(self):
        ######### creating main layout #########
        self.mainLayout = QHBoxLayout()
        self.leftLayout = QFormLayout()
        self.middleLayout = QVBoxLayout()
        self.middleLayout_bottom = QHBoxLayout()
        self.rightLayout = QVBoxLayout()

        ######### adding child layouts #########
        self.mainLayout.addLayout(self.leftLayout, 10)
        self.mainLayout.addLayout(self.middleLayout, 70)
        self.middleLayout.addLayout(self.middleLayout_bottom)
        self.mainLayout.addLayout(self.rightLayout, 20)

        ######### adding widgets #########
            ######### left layout #########
        self.leftLayout.addWidget(self.u_name_label)
        self.leftLayout.addWidget(self.u_name_input)
        self.leftLayout.addWidget(self.u_pwd_label)
        self.leftLayout.addWidget(self.u_pwd_input)
        self.leftLayout.addWidget(self.btn_login)
            ######### middle layout #########
        self.middleLayout.addWidget(self.output)
        self.middleLayout_bottom.addWidget(self.command_label)
        self.middleLayout_bottom.addWidget(self.command_input)
            ######### right layout ##########
        self.rightLayout.addWidget(self.status)
        self.rightLayout.addStretch()

        ######### selecting main layout for window #########
        self.setLayout(self.mainLayout)


    # def login_action(self):
    #     self.setWindowTitle("CSK Control - User: " + self.u_name_input.text())
    #     self.output.setText(self.msg)


    def login_btn(self):
        self.check_auth = QThread()
        self.check_auth.start()
        #self.check_auth = QThread()
        self.auth_checker = self.check_auth.work = Communication(self.u_name_input, self.u_pwd_input)
        #myLogin = Communication(self.u_name_input, self.u_pwd_input)
        self.auth_checker.moveToThread(self.check_auth)
        self.auth_checker.msg_changed.connect(self.msgChanged)

    def send_command(self):
        pass


    def msgChanged(self, messg):
        if messg == "Authentication failed!":
            self.setWindowTitle("CSK Control - LOGIN FAILED!")
        elif messg == "Authentication succeeded!":
            self.setWindowTitle("CSK Control - User: " + self.u_name_input.text())
            self.status.setStyleSheet("background-color: green; font-size: 16pt")
        parent = QTreeWidgetItem(self.output)
        self.output.addTopLevelItem(parent)
        parent.setText(0, messg)

def main():
    # -----------------GUI-----------------#
    App = QApplication(sys.argv)
    window = Window()
    sys.exit(App.exec_())
    # -------------------------------------#

if __name__ == '__main__':
    main()
Benutzeravatar
sparrow
User
Beiträge: 4195
Registriert: Freitag 17. April 2009, 10:28

Nimm Flask und bau es als Webservice.
Das was du tust ist Arbeitsbeschaffung mit einem fürchterlich schlechten Resultat.
Benutzeravatar
Finux
User
Beiträge: 32
Registriert: Mittwoch 18. September 2019, 13:59

das geht leider aus privaten gründen nicht. ich bin ja mittlerweile auch vom ziel nicht mehr weit entfernt, und klinge es noch für euch so unlogisch: "das fürchterliche ergebnis reicht mir tatsächlich". ich kann es derzeit nur leider nicht erklären und bitte daher um verständnis.
Benutzeravatar
__blackjack__
User
Beiträge: 13114
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Finux: Also für mich sieht das alles noch sehr weit von Ziel entfernt aus. Da fehlt was grundlegendes — vielleicht wurde das noch nicht erwähnt, aber TCP ist ein Datenstrom, da braucht man ein Protokoll um die einzelnen Nachrichten auseinander halten zu können und feststellen zu können wann eine Nachricht komplett ist. Zur Semantik von `recv()` könnte man auch was sagen. ``recv(4096).decode("utf-8")`` wäre auch kaputt wenn der darauf folgende Code tatsächlich mit mehr oder weniger beliebigen Teilausschnitten des Datenstroms klar kommen würde, denn dekodieren als UTF-8 tut das schon nicht.

Der Client/Server-Code funktioniert schon nicht, und Du tackerst da jetzt mit GUI-Programmierung eine weitere Komplexitätsstufe drauf.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@Finux: Sternchenimporte sind auch bei Qt schlecht. `sys.exit` sollte insbesondere bei Threads nicht benutzt werden. Was sollen denn diese u_-Präfixe bei den Variablen?
`login` sendet jetzt wirklich ohne Trennung mehrere Daten, die so niemals über eine Internetleitung als Einzelpakete ankommen dürften.
Bitte behaupte nirgends, Du hättest mit dem Hashing irgendwelche Passwörter geschützt.
Was soll diese while-True Schleife in `login`? Sowas in einer Funktion zu finden, die Login heißt, ist nur verwirrend.
Ein so kleiner sleep-Wert ist unsinnig. Die Klammern um die Bedingungen sind überflüssig. Dass `msg_changed`-Signale Freitextfelder sind, reiht sich gut in die lange Liste von DON'Ts.
In `Window.__init__` gehört kein `show`. Widgets sorgen nicht dafür, dass sie dargestellt werden, das macht die Instanz darüber.
`Window.UI` ist eine Methode, die sofort in __init__ wandern sollte und der Unterschied zwischen mainDesign und layout ist auch nicht vorhanden, so dass beides auch direkt in __init__ stehen sollte. Da auf die Layouts nie wieder zugegriffen werden muß, sollten das keine Attribute sein.
Und benutze keine Abkzng. Heißt es nun msg oder messg oder mig? Nein, es heißt einfach immer messsage.
__deets__
User
Beiträge: 14543
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du machst einen Fehler indem du die Text-Felder an Communication uebergibst. Denn dann greifst du darauf aus einem anderen Thread auf GUI-Element zu, und ob das geht, oder mit einem harten Absturz endet ist reine Glueckssache. Gib stattdessen gleich die Werte fuer Namen und Passwort ein. Threads sind schwer. Du denkst QSocketNotifier waere es, aber das stimmt nicht. Threads sehen einfach aus, doch die Teufel im Detail sind derer viele, und gemein. Weil sie sich nicht immer, aber manchmal zeigen.

Wenn du ausserdem den socket ausserhalb von communication erzeugst (zb in einer Funktion), und den dann sowohl an Communication im Konstruktor uebergibst, als ihn dir auch in deiner GUI zu speichern, dann kannst du auch spaeter etwas senden. Communication muss in diesem Fall einfach nur am Ende von login in einer endlosschleife auf Daten vom Socket warten, und die mit einem Signal weiterreichen.

Das du mit nur einem Signal arbeitest, um Daten und als Texte gehaltene Fehlermeldungen zu verschicken ist auch nicht gut. Mach ein Signal daraus, das die rohen socket-Daten verschickt, und ein zweites, das den Zustand der Verbindung kommuniziert. ZB als Enum. Und in der GUI reagierst du dann auf diese Zustandsaenderungen, zB durch enablen/disablen von widgets etc.

Und du musst DRINGEN login_btn umbenennen. Der Name war schon immer schlecht, aber jetzt passt er ueberhaupt nicht mehr. Das passiert ja immer, und sollte das auch. Nenn das zB start_communication_thread oder etwas aehnliches. Es ist auch hochgradig verwirrend, dass du darin den auth_checker anlegst, auf den du dich dann mit " self.btn_login.clicked.connect(self.auth_checker.login)" beziehst. Mach das gleich IN der Methode, denn dann sieht man, dass das zusammengehoert.
Antworten