client sending während client receives

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Benutzeravatar
Finux
User
Beiträge: 32
Registriert: Mittwoch 18. September 2019, 13:59

Hallo,
in meiner aktuellen client-server kommunikation fiel mir (leider gerade erst) ein problem auf..
während ich beim client daten erhalte, bin ich nicht in der lage irgendetwas an den server rauszusenden... ich dachte eigentlich, dass ich diesen umstand mit 2 eigenen threads (1 zum empfangen, 1 zum senden) gelöst hätte, aber ich bekomme dann folgende meldung:
"Unhandled exception in thread started by <function msg_print_send at 0x7f48d54219d8>
Traceback (most recent call last):
File "/home/fin/PycharmProjects/CSK/CSK_client2.py", line 41, in msg_print_send
conn.send(message.encode("utf-8"))
File "/usr/lib/python3.5/ssl.py", line 869, in send
return self._sslobj.write(data)
File "/usr/lib/python3.5/ssl.py", line 594, in write
return self._sslobj.write(data)
ssl.SSLEOFError: EOF occurred in violation of protocol (_ssl.c:1949)
"

kurze infos zu der kommunikation:
- server.py soll auf einem kleinen (mir unbekannten, bs: debian) gerät (außer landes) laufen
- server.py soll viele clients zur gleichen zeit bedienen können

- die verbindung ist ssl + sha512 verschlüsselt
(kurzinfo zur erstellung eines eigenen ssl zertifikats unter debian: openssl req -new -days 999 -newkey rsa:4096bits -sha512 -x509 -nodes -out server.crt -keyout server.key
Note: CN (Common Name) has to be the server adress (e.g. IP)

Don't forget to change rights for server.crt and server.key (chmod -v 777 server.crt and chmod -v 777 server.crt)
)


- client.py werde ich bald in eine html einbinden
- client.py soll einen dauerhaften datenstrom vom server empfangen können, diesen aber auch mit einem einfachen befehl stoppen (verbindung zum server soll erhalten bleiben) dürfen
- client.py soll während dem empfangen eines datenstroms in der lage sein zeitgleich strings an den server zu senden

ich hoffe das reicht an infos, sry wenn ich was vergessen habe. dass einiger unsinn noch im code rumgeistert ist mir klar, und wird noch behoben :)
@__blackjack__ : _thread werde ich bald durch das modul threading tauschen, ich habe deine worte noch im hinterkopf! :)

hier mein bisheriger code:

server.py

Code: Alles auswählen

#::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::#
#::::::::::::::::::::::::::::::::::::        MULTITHREAD + SSL       ::::::::::::::::::::::::::::::::::::::::::#
#::::::::::::::::::::::::::::::::::::::::::::: [ server.py ] ::::::::::::::::::::::::::::::::::::::::::::::::::#
#::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::#
#TODO: logs, try-exp

import socket as socketlib
import ssl
from _thread import *
from time import ctime
import logging
import sys
import time

host = '127.0.0.1'
port = 4500
max_connections = 2


logging.basicConfig(filename="log.log", level=logging.DEBUG,
                    format='[%(levelname)s] %(asctime)s - %(name)s :  - LINE_%(lineno)s (%(funcName)s) # PROCESS ID %(process)s >>> THREAD ID %(thread)s\n || %(message)s \n')



def manage_client(connstream, client_socket, addr):
    print("~~ Step 6/6 successful [Server started admitted request in new thread (Client {})] ~~".format(addr))
    logging.debug("~~ Step 6/6 successful [Server started admitted request in new thread (Client %s)] ~~", addr)
    connstream.send(b'Server accepted the connection!')

    usr_accepted = False
    while True:
        data = connstream.recv(4096)
        data = data.decode("utf-8")
        print("Client {}: ".format(addr), data)
        logging.debug("Client %s: %s", addr, data)
        if data == '' or data == 'exit':
            disconnect(connstream, addr)
            break
        elif data == "show status":
            if usr_accepted == True:
                show_status(connstream)
        elif data == "username":
            usr_accepted = authentification(connstream, addr)
            if usr_accepted == False:
                disconnect(connstream, addr)
                break
            continue
        elif data == "count":
            for i in range(1, 61):
                connstream.send(str(i).encode("utf-8"))
                time.sleep(1)
        try:
            connstream.send(b'Servertestmessage')
            pass
        except BrokenPipeError as e:
            print(e)
            logging.debug(e)
            logging.exception('Got exception here')
            print("MESSAGE COULD NOT BE SEND!")
            logging.debug("MESSAGE COULD NOT BE SEND!")
            connstream.close()
            break

def authentification(connstream, addr):
    auth_dict = {'fin':'finja', 'haxe':'1337', 'hell':'d404559f602eab6fd602ac7680dacbfaadd13630335e951f097af3900e9de176b6db28512f2e000b9d04fba5133e8b1c6e8df59db3a8ab9d60be4b97cc9e81db'} 
    auth_data_usr = connstream.recv(4096)
    auth_data_usr = auth_data_usr.decode("utf-8")
    for usr_data in auth_dict:
        if auth_data_usr == usr_data:
            auth_data_pwd = connstream.recv(4096)
            auth_data_pwd = auth_data_pwd.decode("utf-8")
            if auth_dict[usr_data] == auth_data_pwd:
                connstream.send(b'Authentification succeeded!')
                print("CLIENT AUTHENTIFICATION FROM {} ACCEPTED".format(addr))
                logging.debug("CLIENT AUTHENTIFICATION FROM {} ACCEPTED".format(addr))
                return True
            else:
                connstream.send(b'Authentification failed!')
                print("CLIENT AUTHENTIFICATION FROM {} NOT ACCEPTED".format(addr))
                logging.debug("CLIENT AUTHENTIFICATION FROM {} NOT ACCEPTED".format(addr))
                return False



def disconnect(connstream, addr):
    print("+++++ SOCKET CLOSED +++++   [Client {}]".format(addr))
    logging.debug("+++++ SOCKET CLOSED +++++   [Client {}]".format(addr))
    connstream.close()

def show_status(connstream):
    server_location_time = ctime()
    connstream.send(server_location_time.encode("utf-8"))
    connstream.send("Location: CHINA".encode("utf-8"))
    connstream.send("Adress: XYZ".encode("utf-8"))
    connstream.send("Last update: Oct-11-2019".encode("utf-8"))
    connstream.send("Python Version: 3".encode("utf-8"))


def main():
    try:
        context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
        context.load_cert_chain(certfile="/home/fin/server.crt", keyfile="/home/fin/server.key")
        print("~~ Step 1/6 successful [Loading ssl certification]~~")
        logging.debug("~~ Step 1/6 successful [Loading ssl certification]~~")
    except FileNotFoundError:
        print("CERTIFICATE NOT FOUND!!")
        logging.exception("Certificate could not be loaded. Program is not running anymore. Please restart the program!")
        logging.error("Certificate could not be loaded. Program is not running anymore. Please restart the program!")
        sys.exit()
    try:
        socket = socketlib.socket(socketlib.AF_INET, socketlib.SOCK_STREAM)
        socket.bind((host, port))
        print("~~ Step 2/6 successful [Socket Binding IP {} on PORT {} as IPv4] ~~".format(host, port))
        logging.debug("~~ Step 2/6 successful [Socket Binding IP {} on PORT {} as IPv4] ~~".format(host, port))
    except OSError as e:
        print("Binding ip {} on port {} failed. Address already in use.".format(host, port))
        logging.exception("Binding ip %s on port %s failed. Address already in use. Please restart the program!", host, port)
        sys.exit()
    socket.listen(max_connections)
    print("~~ Step 3/6 successful [Server is listening for requests] ~~")
    logging.debug("~~ Step 3/6 successful [Server is listening for requests] ~~")

    while True:
        try:
            client_socket, addr = socket.accept()
            print("~~ Step 4/6 successful [Server got a request from {}] ~~".format(addr))
            logging.debug("~~ Step 4/6 successful [Server got a request from %s] ~~", addr)
            try:
                connstream = context.wrap_socket(client_socket, server_side=True)
                print("~~ Step 5/6 successful [Request is ssl encrypted now (Client {})] ~~".format(addr))
                logging.debug("~~ Step 5/6 successful [Request is ssl encrypted now (Client %s)] ~~", addr)
            except ssl.SSLError as e:
                print("Error {}: {}".format(e.args[0], e.args[1]))
                logging.debug("Error {}: {}".format(e.args[0], e.args[1]))
                print("Connection to {} closed.".format(addr))
                logging.debug("Connection to {} closed.".format(addr))
                continue

            start_new_thread(manage_client, (connstream, client_socket, addr))
        except KeyboardInterrupt as e:
            logging.exception('Got exception on main handler')


if __name__ == "__main__":
    main()

client.py

Code: Alles auswählen

#::::::::::::::::::::::::::::::::::#
#::::     MULTITHREAD + SSL    ::::#
#:::::::::: [ client.py] ::::::::::#
#::::::::::::::::::::::::::::::::::#
#TODO: logs

import socket, ssl, pprint, sys
from _thread import *
import time
import hashlib

global msg
msg = ''

ip = '127.0.0.1'
port = 4500




def msg_receive(conn, ip):
    try:
        while True:
            server_answer = conn.recv(4096)
            msg = server_answer.decode("utf-8")
            if msg != '':
                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()
                return 0
    except OSError as e:
        return 0


def msg_print_send(conn, ip):
    while True:
        message = input("\nNachricht: ")
        conn.send(message.encode("utf-8"))
        if message == 'username':
            usr_message = input("\nBN: ")
            conn.send(usr_message.encode("utf-8"))
            pwd_message = hashlib.sha512(bytes(input("\nPW: "), "utf-8"))
            p_message = (pwd_message.hexdigest())
            conn.send(p_message.encode("utf-8"))

        if message == 'exit':
            print("+++++ SOCKET CLOSED +++++")
            conn.close()
            sys.exit() #TODO: skript beendet sich nicht
            break

        #if message != '':
            #conn.send(message.encode("utf-8"))
    return 0


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

    try:
        context.load_verify_locations("/home/fin/server.crt")
        print("***CHECK- 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:
        conn = context.wrap_socket(socket.socket(socket.AF_INET, socket.SOCK_STREAM), server_hostname="127.0.0.1")
        print("***CHECK- Socket only supports ssl connection successful")
        try:
            conn.connect((ip, port))
            print("***CHECK- Connection to server successful")
            conn.send(b"Thanks for accepting the connection!")
            print("***CHECK- Bytestring sending successful")
        except:
            print("CONNECTION NOT POSSIBLE! IN 10 SECONDS TRYING TO CONNECT AGAIN..")
            time.sleep(10.0)
            return 0
    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("Error {}: {}".format(e.args[0], e.args[1]))
        print(e)
        print("::::: Program will be closed now! :::::")
        sys.exit()

    start_new_thread(msg_receive, (conn, ip))
    start_new_thread(msg_print_send, (conn, ip))


    try:
        while 1:
            pass
    except KeyboardInterrupt as e:
        print(e)


if __name__ == "__main__":
    main()
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Leider ist es, wie ich befürchtet hatte, dass Du kein Protokoll implementiert hast, somit ist Deine Server-Client-Kommunikation fehlerhaft.

Aber fangen wir von oben an:
Konstanten werden nach Konvention komplett groß geschrieben: HOST, PORT, MAX_CONNECTIONS.
In ›manage_client‹: `send` ist falsch, wenigstens `sendall` solltest Du benutzen, da Du aber kein Kennzeichnung einer Nachricht hast, muß der Client exakt auf Deine Willkommensmeldung prüfen.
`recv` garantiert nur dass irgendwas zwischen einem und 4096 Bytes zurückgeliefert werden. Da utf-8 aber multibyte-Zeichen zuläßt, läuft das decode in einen potentiellen DecodingError.
Da aber `recv` mindestens ein Zeichen liefert, kann der Vergleich mit dem leeren String niemals wahr werden.
Explizit auf True oder False zu vergleichen, macht man nicht, da das eh nur wieder einen Wahrheitswert liefert.
In `authentification` funktionieren die nacheinander aufgerufenen `recv` wieder nicht, weil Du nur einen Stream hast, aber kein Nachrichtenprotokoll. Wörterbücher sind ja gerade dazu da, einen Schlüssel einfach zu finden, die for-Schleife über die Schlüssel des Wörterbuchs ist also quatsch.
In `show_status` ist `ctimes` keine gute Idee, weil das Ausgabeformat nicht definiert ist. Nimm die Formatierungsfunktionen von datetime.

In ›main‹: Pfade zu Dateien sollten nicht irgendwo im Code auftauchen, sondern zumindest als Konstanten am Anfang. `sys.exit` ohne Errorcode sollte in einem sauberen Programm nicht vorkommen, hier würde ja sogar ein ErrorCode Sinn machen.
Statt `continue` wäre hier ein else-Block zu try lesbarer.
Die Exception-Nachricht bei KeyboardInterrupt verhindert, dass man das Programm mit Strg+C beenden kann.

Zum Client-Code:
Vergiss gleich wieder, dass es sowas wie `global` gibt. So wie Du das einsetzt, ist es sowieso unwirksam. In ›msg_receive‹ ist das unendlich kleine ›sleep‹ unsinnig. `msg` kann wie oben schon geschrieben, nie leer sein. Eine Funktion, die nur 0 zurückliefern kann dessen Rückgabewert ist zumindest fraglich. Fehlermeldungen sollte man nicht einfach so ignorieren.

sha512 ist kein gutes Verfahren um Passwörter zu schützen. Nimm anerkannte Verfahren: https://de.wikipedia.org/wiki/Hashfunkt ... funktionen

Das nakte Except in `main` verhindert jede sinnvolle Reaktion auf Fehler. Zumindest ein logging.exception muß da rein. Wie auch alle anderen „Fehlerbehandlungen” nicht wirklich verraten, was der Fehler ist, sondern den Nutzer das mit sinnlosen Meldungen das Leben schwer machen.

Die Busy-Loop ist zum Heizen gut, aber hat in einem Programm nichts verloren.

Das wichtigste ist, dass Du lernst, wie man ein funktionierendes Netzwerk-Protokoll schreibt, Schau Dir bekannte Protokolle an, wie HTTP, oder SMTP. Die sind einfach genug, um das Prinzip zu verstehen.


EDIT: das Wesentliche hat Dir __blackjack__ schon letzten Monat geschrieben; aber ich gebe die Hoffnung nicht auf, dass Du es diesmal beachtest.
Benutzeravatar
__blackjack__
User
Beiträge: 13079
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Ich würde mir auch relativ gut überlegen, ob ich überhaupt ein neues, eigenes Protokoll implementiere. Es gibt ja schon viele Fertige für die verschiedensten Einsatzzwecke, die auf einer höheren Schicht ansetzen als direkt auf Sockets rumzurödeln und wofür es schon Bibliotheken gibt. HTTP für REST und zum Übertragen von Dateien/Datenströmen, diverse Message-Queues (ZMQ, nanomsg, …), …
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
Finux
User
Beiträge: 32
Registriert: Mittwoch 18. September 2019, 13:59

erstmal danke für eure mühen + tipps + hilfe! ich weiß das sehr zu schätzen!
zunächst: ich ignoriere eure tipps nicht, ich versuche nur die brennenden baustellen zuerst zu bearbeiten (ist also nicht böse gemeint).

ich frage mal so direkt: ist mein code komplett useless? bin grade echt fertig mit den nerven. ist mein ganzer ansatz falsch?
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Was willst du denn erreichen? Ist das Mittel zum Zweck, oder soll das benutzt werden? Bei letzterem: ja, dann muss das weg. Und dann solltest du mit den schon existierenden Abstraktionen (die zb auch Protokolle beinhalten) arbeiten. Da hat Python schon eingebaut viel zu bieten, und mit extra Paketen wird’s noch mehr.

Wenn ersteres: nimm dir Tipps vor und setzt sie um. Und sowohl threading als auch monolithische Riesenfunktionen sind Brandbeschleuniger. Die in den Griff zu kriegen hilft den Brand zu kontrollieren.
Benutzeravatar
Finux
User
Beiträge: 32
Registriert: Mittwoch 18. September 2019, 13:59

Wie ihr sicherlich gemerkt habt, bin ich ein waschechter Anfänger. Daher auch das Durcheinander und viele Sinnlosigkeiten. Ich habe vor wenigen Wochen mit Python angefangen. Das hier ist mein erstes Socket-Projekt. Ich bitte um Nachsicht und Geduld, sorry :roll:
Ich versuche mal Satz für Satz durchzugehen:


Zu Sirius3:

Leider ist es, wie ich befürchtet hatte, dass Du kein Protokoll implementiert hast, somit ist Deine Server-Client-Kommunikation fehlerhaft.
Ohne frech zu wirken- wie könnte denn hier die Lösung mit einem bereits existierenden Protokoll aussehen? Jegliche Fehler (auf die ich mit der Zeit gestoßen bin) habe ich versucht zu fixen/abzufangen, die Kommunikation funktioniert soweit auch. Lediglich das Ärgernis nichts senden zu können, wenn ich etwas empfange wirft mir gefühlt unüberwindbare Steine in den Weg..

..da Du aber kein Kennzeichnung einer Nachricht hast, muß der Client exakt auf Deine Willkommensmeldung prüfen.
Prüfen? Wie meinst du das? Bisher kamen zumindest alle Nachrichten bei der Gegenseite vollständig an.

`recv` garantiert nur dass irgendwas zwischen einem und 4096 Bytes zurückgeliefert werden. Da utf-8 aber multibyte-Zeichen zuläßt, läuft das decode in einen potentiellen DecodingError.
Bisher hatte ich noch keinen decoding-error... :shock:

Explizit auf True oder False zu vergleichen, macht man nicht, da das eh nur wieder einen Wahrheitswert liefert.
Ich muss doch einen authentifizierten User erkennen können? Wenn der User ne Falscheingabe macht, soll der Server direkt die Verbindung zu diesem Client abbrechen.

Wörterbücher sind ja gerade dazu da, einen Schlüssel einfach zu finden, die for-Schleife über die Schlüssel des Wörterbuchs ist also quatsch.
Um das passende Passwort zum User finden zu können ist die for-Schleife unsinnig? Das verstehe ich nicht. Wie sollte ich sonst User und zugehöriges Passwort im dictionary finden um die Clientmessage vergleichen zu können?

`sys.exit` ohne Errorcode sollte in einem sauberen Programm nicht vorkommen, hier würde ja sogar ein ErrorCode Sinn machen.
die sys.exit() habe ich eingebaut, damit das Skript sauber beendet wird. Spätestens in der Log ist die genauere Fehlerbezeichnung erkennbar.

Die Exception-Nachricht bei KeyboardInterrupt verhindert, dass man das Programm mit Strg+C beenden kann
Wie kann ich denn ein Programm "sauber" beenden, sodass ich keine Exception bekomme?

Eine Funktion, die nur 0 zurückliefern kann dessen Rückgabewert ist zumindest fraglich.
D.h. um eine Funktion zu beenden sollte hier ein return ausreichen?

Fehlermeldungen sollte man nicht einfach so ignorieren.
Irgendwann beim Schreiben und Testen des Codes stieß ich an dieser Stelle auf einen OSError, ich weiß nicht wie ich damit umgehen soll, wenn nicht mit einer Exeption..?

sha512 ist kein gutes Verfahren um Passwörter zu schützen.
Wieso?

Das nakte Except in `main` verhindert jede sinnvolle Reaktion auf Fehler.
Wie sähe eine sinvolle Reaktion (außer in einer Logdatei zu schreiben) denn an der Stelle aus?

Die Busy-Loop ist zum Heizen gut, aber hat in einem Programm nichts verloren.
Statt der Busy-Loop hätte ich eine meiner Funktionen (msg_receive oder msg_print_send) an die Stelle setzen können. Der Übersicht halber wollte ich beide in einem eigenen getrennten Thread manifestieren.

Das wichtigste ist, dass Du lernst, wie man ein funktionierendes Netzwerk-Protokoll schreibt, Schau Dir bekannte Protokolle an, wie HTTP, oder SMTP. Die sind einfach genug, um das Prinzip zu verstehen.
Hast du hier eine geeignete Quelle für mich? Ich tue mich schwer, da etwas zu finden. Das Prinzip von GET oder POST jedoch ist mir (so dachte ich zumindest) bekannt..


-----------------------------------------------------------------------------


Zu __blackjack__:

Ich würde mir auch relativ gut überlegen, ob ich überhaupt ein neues, eigenes Protokoll implementiere.
Das hatte ich auch nicht vor :)

Es gibt ja schon viele Fertige für die verschiedensten Einsatzzwecke, die auf einer höheren Schicht ansetzen als direkt auf Sockets rumzurödeln und wofür es schon Bibliotheken gibt. HTTP für REST und zum Übertragen von Dateien/Datenströmen, diverse Message-Queues (ZMQ, nanomsg, …), …
Das sagt mir ehrlich gesagt alles überhaupt nichts.. Ich habe traurigerweise auch immer noch nicht verstanden was für ein Protokoll mir fehlen soll. Die client.py werde ich in HTML einbinden. Dass ich hier noch Verknüpfen muss ist mir klar, aber ich muss erst noch mein HTML-Grundgerüst bauen..



Darüber hinaus (in der Hoffnung der Code ist nicht ein ganzer Haufen Müll, denn auch wenn es nicht danach aussehen mag, es steckt viel Arbeit darin):
Ich sehe eine mögliche Lösung meine Grundproblems in der Anwendung von async auf beiden Skripten (Kernproblem für mich war ja, dass ich beim clientseitigen receiven keine nachricht senden kann). Ist das denkbar? Allerdings finde ich auch hier kaum Material im Netz, welchem ich wirklich gut folgen kann um das Prinzip so zu verstehen, dass ich es sogar auf meine Skripte anwenden könnte.. :cry:
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Um Dir helfen zu können, wäre es gut, wenn wir Deine wirklichen Anforderungen an die Aufgabe kennen würden. Was ist Dir vorgegeben? Was ist der Lieferumfang?


Zu den Fragen:
Das Problem ist, dass lokal die Kommunikation so schnell ist, und die Datenpakete so kurz, dass die immer vollständig übertragen werden. Das ist aber nicht garantiert, und führt dazu, dass Fehler schwierig zu reproduzieren sind. Das kann man schlecht Testen und muß in der Theorie bewiesenermaßen funktionieren.
Hier ein Beispiel, das Dein Problem illustriert:

Code: Alles auswählen

import socket

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

server.bind(('0.0.0.0', 1234))
server.listen()
client.connect(('127.0.0.1', 1234))
client.sendall(b'Hallo')
client.sendall(b'Login User')
client.sendall(b'Quit')

session, _ = server.accept()
print(session.recv(4096))
Mit einem `recv` kommen alle drei "Nachrichten" gleichzeitig an.

2. Explizit prüfen ist "a == True" impliziert ist einfach nur "a". Bei Deinem Code:

Code: Alles auswählen

        elif data == "show status":
            if usr_accepted:
                show_status(connstream)
        elif data == "username":
            usr_accepted = authentification(connstream, addr)
            if not usr_accepted:
                disconnect(connstream, addr)
                break
3. Wörterbücher zu benutzen ist so grundlegend, dass das erklären warum es falsch ist, so schwierig ist.

Code: Alles auswählen

AUTHENTICATIONS = {
    'fin':'finja',
    'haxe':'1337', 
    'hell':'d404559f602eab6fd602ac7680dacbfaadd13630335e951f097af3900e9de176b6db28512f2e000b9d04fba5133e8b1c6e8df59db3a8ab9d60be4b97cc9e81db'
} 

def authentification(connstream, addr):
    auth_data_usr = "fin" # nur zum Testen. connstream.recv(4096).decode("utf-8")
    auth_data_pwd = "finja" # connstream.recv(4096).decode("utf-8")
    # if user does not exisits, stored_password is None
    stored_password = AUTHENTICATIONS.get(auth_data_usr, None)
    if stored_password == auth_data_pwd:
        connstream.send(b'Authentification succeeded!')
        print("CLIENT AUTHENTIFICATION FROM {} ACCEPTED".format(addr))
        logging.debug("CLIENT AUTHENTIFICATION FROM {} ACCEPTED".format(addr))
        return True
    else:
        connstream.send(b'Authentification failed!')
        print("CLIENT AUTHENTIFICATION FROM {} NOT ACCEPTED".format(addr))
        logging.debug("CLIENT AUTHENTIFICATION FROM {} NOT ACCEPTED".format(addr))
        return False
4. Das mit dem `sys.exit` ist eben nicht sauber, weil mitten irgendwo im Programm plötzlich etwas passiert, das verhindert, dass man z.B. sauber aufräumen kann. Es verhindert, dass man von weiter außen anders mit Fehlern umgehen kann, sondern wenn man anderes verhalten will, muß man tief verschachtelt die Stelle finden und dort Änderungen einbauen. Solche Probleme hören sich bei so kurzen Skripten immer etwas übertrieben an, bei großen Projekten, wo auch viele Programmierer zusammenarbeiten, führt aber ein sys.exit an der falschen Stelle zu großem Chaos.

5. Bei Strg+C hast Du einfach das try innerhalb der while-Schleife, das heißt, Strg+C beendet zwar das aktuelle `accept` (an anderen Stellen dürfte sich das Programm im Normalfall nicht länger aufhalten), durch die while-Schleife rufst Du es aber gleich wieder auf.

6. Statt `return 0` würde ein `return` reichen, hier aber ein `break` um die while-Schleife zu verlassen.

7. Hast Du den von mir verlinkten Artikel angeschaut und insbesondere die unter den Links zu verschiedenen Passwort-Hash-Funktionen die Abschnitte über Sicherheit?

8. Bei Exceptions mußt Du Dich immer fragen, welche Fehler auftreten können, und wie man auf den Fehler reagieren kann, so dass das Programm danach in einem benutzbaren Zustand bleibt.

9. dann benutze aber sleep.

10. die Wikipedia-Artikel dazu sind ein guter Einstieg.
Benutzeravatar
__blackjack__
User
Beiträge: 13079
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Finux: Aus den Antworten wird klar das Du ein grundlegendes Problem bei Socket-Programmierung noch nicht erfasst hast: Nur weil das Programm scheinbar funktioniert, heisst das leider überhaupt nicht, dass es nicht ganz gravierende Fehler enthalten kann. Denn die Annahme, dass man ohne Protokoll mit `send()` und `recv()` Nachrichten über TCP senden und empfangen kann, also das `send()` immer eine komplette Nachricht sendet und `recv()` immer eine komplette mit `send()` gesendete Nachricht empfängt, ist falsch. Lokal oder in einem kleinen lokalen Netz scheint das scheinbar der Fall zu sein, es stimmt aber eben nicht.

Die Senderseite lässt sich leicht durch `sendall()` fixen, das stellt mindestens mal sicher, dass alles gesendet wurde was man übergeben hat. Beim Empfänger gibt es so etwas nicht, weil da dann das Protokoll fehlt. Also irgendeine Beschreibung wie man denn beim Empfänger erkennen kann, wann eine Nachricht komplett ist. Weil, wie ja schon in vorherigen Beiträgen geschrieben wurde: `recv(n)` liefert zwischen 1 und n Bytes. Der Code muss im Extremfall damit klar kommen können das diese Methode immer nur *ein* Byte liefert. Woher weiss der Empfänger dann beispielsweise in Deinem Szenario wann er die Willkommensmeldung komplett empfangen hat und wo danach dann die Befehle jeweils anfangen und aufhören?

Die drei üblichen Varianten für Nachrichtentrennung sind a) eine feste Struktur pro Nachricht und damit eine feste Nachrichtengrösse, oder b) eine feste Struktur für einen Nachrichtenkopf in dem die Grösse der Nachricht kodiert ist, oder c) ein Endkennzeichen das garantiert nicht innerhalb einer Nachricht vorkommen kann. Um c) sicherzustellen kann man noch einen Escape-Mechanismus definieren, falls man das Endkennzeichen auch innerhalb der Daten haben könnte und das irgendwie ”entschärfen” muss.

Explizit auf `True` oder `False` vegrleichen meint ``x == True`` oder ``x == False`` zu schreiben. Das macht man nicht denn `x` ist ja bereits `True` oder `False` und da kommt nur wieder `True` oder `False` heraus. ``== True`` ist dann so ähnlich als würde man bei Rechnungen mit Zahlen immer ein ``(z + 0)`` oder ``z * 1`` überall zum `z` schreiben. Ändert nix am Ergebnis ist aber total überflüssig. Man will statt eines Vergleichs einfach ``x`` oder ``not x`` haben. Also ``if user_accepted:`` oder ``if not user_accepted:`` statt eine der vier Möglichkeiten die man mit ``==``, ``!=`` und literalen `True`, `False` hätte.

Die Frage nach dem Wörterbuchzugriff verleitet dazu dringend ein Grundlagentutorial zu empfehlen. Wörterbücher sind dazu da schnell und effizient, also gerade ohne eine Schleife über alle Schlüssel zu haben, von Schlüsseln auf Werte zuzugreifen. *Das* hier ist deren Hauptrechtfertigung warum man nicht einfach Listen mit Paaren aus Schlüssel/Wert verwendet:

Code: Alles auswählen

In [10]: d = {"hot": "red", "cold": "blue"}                                     

In [11]: d["hot"]                                                               
Out[11]: 'red'

In [12]: d["cold"]                                                              
Out[12]: 'blue'
Ein Skript das sauber endet, endet ”natürlich”, also dass der Programmfluss einfach das Ende des Programms erreicht und damit das Programm eben aufhört. Wenn man `sys.exit()` verwendet sollte man das in der Regel an genau der Stelle tun. Da das Programm dort aber sowieso endet, macht `sys.exit()` nur Sinn wenn man statt der impliziten 0 dort auch andere mögliche Werte an den Aufrufer liefern möchte.

Die Regel ist nicht ganz so hart, normalerweise ist es auch okay `sys.exit()` innerhalb der `main()`-Funktion für Rückgabecodes zu verwenden, oder in Bibliotheken bei denen man ganz sicher ist, dass der Programmierer erwartet und damit einverstanden ist, dass die so etwas *drastisches* tun. Bibliotheken die Kommandozeilenargumente auswerten beispielsweise. `argparse` oder `click` wären Beispiele dafür.

Bei den `sys.exit()` in Deinen Hauptfunktionen ist also weniger der Fehler das dort `sys.exit()` aufgerufen wird, sondern wie Sirius3 schon schrieb das da kein Rückgabcode angegeben wird, womit 0 genommen wird, was aber soviel wie „alles okay“ heisst. Nur das in den Fällen wo `sys.exit()` aufgerufen wird ja gerade nicht alles in Ordnung ist, sondern kritische Probleme aufgetreten sind. Da sollte man mindestens 1 als Rückgabcode setzen, damit der Aufrufer das auswerten kann und sehen kann ob das Programm normal beendet wurde, oder durch einen Fehler/ein Problem.

Das `sys.exit()` innerhalb eines Threads der Nachrichten verarbeitet, ist dagegen total unsauber, sofern es denn funktionieren würde. An der Stelle weiss man ja nicht ob der Hauptthread noch irgendwelche Aufräumaktionen vorgesehen hat, oder ob man andere Threads einfach so mittendrin abwürgen kann ohne das etwas kaputt geht. Sauber wäre wenn der Thread darüber informiert das Ende angesagt ist. Entweder implizit, weil irgendwo überwacht wird das der Thread endete. Also beispielsweise weil der Hauptthread auf das Ende gewartet hat, und danach dann den Shutdown koordiniert – andere Threads informieren, auf deren Ende warten, Aufräumarbeiten durchführen, und dann ”natürlich” enden. Oder der Thread informiert explizit über den Wunsch nach Ende über eine `Queue` oder ein `Event`.

Für das Abbrechen bei `KeyboardInterrupt` hast Du ganz offensichtlich die ``while True:``-Schleife und das ``try``/``except`` falsch herum verschachtelt. Ein Ctrl+C muss ja aus der Schleife ausbrechen können, was nicht passiert wenn man die Behandlung *in* der Schleife macht, aus der man damit heraus kommen möchte.

Anstelle des ``return 0`` im ``exept OSError:``-Block würde man eher ein ``pass`` schreiben. Denn die Funktion endet danach ja sowieso, auch ohne explizites ``return``.

Kannst Du den `OSError` an der Stelle denn einfach ignorieren? Wenn man Ausnahmen ignoriert ist es in der Regel eine gute Idee zu kommentieren das man das absichtlich macht, und in nicht-offensichtlichen Fällen dann auch warum das okay ist das zu tun. Hier würde ich sagen es ist nicht okay, insbesondere wenn Du nicht genau sagen kannst warum diese Ausnahme auftritt. Dann sollte man sie mindestens mal protokollieren, samt Traceback. So kann man sich später noch mal mit der Analyse von diesen Ausnahmen beschäftigen.

Wenn man `logging` verwendet, ist es an sich auch immer eine gute Idee ein ``try``/``except Exception:`` um die ”Hauptfunktionen” von allen Threads zu legen und im ``except``-Block die Ausnahme zu protokollieren, damit die Sachen mit denen man überhaupt nicht gerechnet hat, nicht einfach verloren gehen können.

Statt der „busy loop“ hätte man auch auf das Ende der Threads warten können, oder mindestens auf das Ende eines Threads wenn dessen Ende das Ende des Programms bedeuten soll. Das legt den Hauptthread dann wenigstens schlafen statt sinnlos eine CPU zu beschäftigen.

Was soll denn „die client.py“ in HTML einbinden bedeuten?
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Was heißt denn “werde ich in HTML einbinden”? Du kannst nicht einfach beliebige Programme im Browser ausführen. Falls es das sein sollte was du vorhast. Erzähl uns doch mal was das Ziel des ganzen Sein soll
Benutzeravatar
Finux
User
Beiträge: 32
Registriert: Mittwoch 18. September 2019, 13:59

Hier mein aktueller Zwischenstand der server.py

Code: Alles auswählen

#::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::#
#::::::::::::::::::::::::::::::::::::        MULTITHREAD + SSL       ::::::::::::::::::::::::::::::::::::::::::#
#::::::::::::::::::::::::::::::::::::::::::::: [ server.py ] ::::::::::::::::::::::::::::::::::::::::::::::::::#
#::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::#
#TODO: logs, try-exp

import socket as socketlib
import ssl
from threading import Thread
from time import ctime
import logging
import time


HOST = '127.0.0.1'
PORT = 4200

AUTHENTICATIONS = {'fin': 'finja',
                   'haxe': '1337',
                   'hell': 'd404559f602eab6fd602ac7680dacbfaadd13630335e951f097af3900e9de176b6db28512f2e000b9d04fba5133e8b1c6e8df59db3a8ab9d60be4b97cc9e81db'
                   }

logging.basicConfig(filename="log.log", level=logging.DEBUG,
                    format='[%(levelname)s] %(asctime)s - %(name)s :  - LINE_%(lineno)s (%(funcName)s) # PROCESS ID %(process)s >>> THREAD ID %(thread)s\n || %(message)s \n')



def manage_client(connstream, client_socket, addr):
    print("~~ Step 6/6 successful [Server started admitted request in new thread (Client {})] ~~".format(addr))
    logging.debug("~~ Step 6/6 successful [Server started admitted request in new thread (Client %s)] ~~", addr)
    connstream.sendall(b'Server accepted the connection!')

    usr_accepted = False
    while True:
        data = connstream.recv(4096).decode("utf-8")
        print("Client {}: ".format(addr), data)
        logging.debug("Client %s: %s", addr, data)
        if data == 'exit':
            disconnect(connstream, addr)
            break
        elif data == "show status":
            if usr_accepted:
                show_status(connstream)
        elif data == "username":
            usr_accepted = authentication(connstream, addr)
            if not usr_accepted:
                disconnect(connstream, addr)
                break
            continue
        elif data == "count":
            for i in range(1, 61):
                connstream.sendall(str(i).encode("utf-8"))
                time.sleep(1)
        try:
            connstream.sendall(b'Servertestmessage')
            pass
        except BrokenPipeError as e:
            print(e)
            logging.debug(e)
            logging.exception('Got exception here')
            print("MESSAGE COULD NOT BE SEND!")
            logging.debug("MESSAGE COULD NOT BE SEND!")
            connstream.close()
            break


def authentication(connstream, addr):
    usr_name = connstream.recv(4096).decode("utf-8")
    usr_pwd = connstream.recv(4096).decode("utf-8")
    if usr_name in AUTHENTICATIONS.keys():
        auth_pwd = AUTHENTICATIONS.get(usr_name, None)
        if auth_pwd == usr_pwd:
            connstream.send(b'Authentication succeeded!')
            print("CLIENT AUTHENTICATION FROM {} ACCEPTED".format(addr))
            logging.debug("CLIENT AUTHENTICATION FROM {} ACCEPTED".format(addr))
            return True
        else:
            connstream.send(b'Authentication failed!')
            print("CLIENT AUTHENTICATION FROM {} NOT ACCEPTED".format(addr))
            logging.debug("CLIENT AUTHENTICATION FROM {} NOT ACCEPTED".format(addr))
            return False
    else:
        connstream.send(b'Authentication failed!')
        print("CLIENT AUTHENTICATION FROM {} NOT ACCEPTED".format(addr))
        logging.debug("CLIENT AUTHENTICATION FROM {} NOT ACCEPTED".format(addr))
        return False



def disconnect(connstream, addr):
    print("+++++ SOCKET CLOSED +++++   [Client {}]".format(addr))
    logging.debug("+++++ SOCKET CLOSED +++++   [Client {}]".format(addr))
    connstream.close()

def show_status(connstream):
    server_location_time = ctime()
    connstream.sendall(server_location_time.encode("utf-8"))
    connstream.sendall("Location: CHINA".encode("utf-8"))
    connstream.sendall("Adress: XYZ".encode("utf-8"))
    connstream.sendall("Last update: Oct-11-2019".encode("utf-8"))
    connstream.sendall("Python Version: 3".encode("utf-8"))


def main():
    try:
        context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
        context.load_cert_chain(certfile="/home/fin/server.crt", keyfile="/home/fin/server.key")
        print("~~ Step 1/6 successful [Loading ssl certification]~~")
        logging.debug("~~ Step 1/6 successful [Loading ssl certification]~~")
    except FileNotFoundError:
        print("CERTIFICATE NOT FOUND!!")
        logging.exception("Certificate could not be loaded. Program is not running anymore. Please restart the program!")
        logging.error("Certificate could not be loaded. Program is not running anymore. Please restart the program!")

    server_socket = socketlib.socket(socketlib.AF_INET, socketlib.SOCK_STREAM)
    with server_socket:
        server_socket.bind((HOST, PORT))
        print("~~ Step 2/6 successful [Socket Binding IP {} on PORT {} as IPv4] ~~".format(HOST, PORT))
        logging.debug("~~ Step 2/6 successful [Socket Binding IP {} on PORT {} as IPv4] ~~".format(HOST, PORT))
        server_socket.listen()
        print("~~ Step 3/6 successful [Server is listening for requests] ~~")
        logging.debug("~~ Step 3/6 successful [Server is listening for requests] ~~")
        try:
            while True:
                    client_socket, addr = server_socket.accept()
                    print("~~ Step 4/6 successful [Server got a request from {}] ~~".format(addr))
                    logging.debug("~~ Step 4/6 successful [Server got a request from %s] ~~", addr)
                    client_socket = context.wrap_socket(client_socket, server_side=True)
                    print("~~ Step 5/6 successful [Request is ssl encrypted now (Client {})] ~~".format(addr))
                    logging.debug("~~ Step 5/6 successful [Request is ssl encrypted now (Client %s)] ~~", addr)
                    
                    Thread(target=manage_client, args=(client_socket, server_socket, addr), daemon=True).start()
                
        except KeyboardInterrupt as e:
            print(e)                        # wieso printet er mir hier nichts aus? nicht mal den traceback!?
            logging.exception(e)
    



if __name__ == "__main__":
    main()
Als nächstes ist die client.py dran. Ich habe versucht ein paar Dinge zu berücksichtigen (u.a. glaube ich ist sha512 aktuell mein kleinstes Problem), bin aber noch nicht fertig.
Benutzeravatar
Finux
User
Beiträge: 32
Registriert: Mittwoch 18. September 2019, 13:59

__deets__ hat geschrieben: Donnerstag 17. Oktober 2019, 13:29 Was heißt denn “werde ich in HTML einbinden”? Du kannst nicht einfach beliebige Programme im Browser ausführen. Falls es das sein sollte was du vorhast. Erzähl uns doch mal was das Ziel des ganzen Sein soll
Da die Frage recht kurz ist, direkt eine Antwort dazu:
Ich habe vor einigen Wochen via Pycharm/Django schon Python-Logik hinter einer Homepage gesehen. Es sollte also eigentlich machbar sein. Genau weiß ich es leider nicht mehr, aber die Verknüpfung wurde mit GET und POST-Befehlen realisiert und funktionierte soweit ohne Probleme.. HTML soll ja nur die "Ein- und Ausgabemaske" bilden, ggfs. 1-2 Buttons (z.B. für die serverseitige "show_status"-Funktion), aber das ist letztlich Spielerei...
Benutzeravatar
sparrow
User
Beiträge: 4187
Registriert: Freitag 17. April 2009, 10:28

In dem Thread wurde dir wirklich ausführlich erklärt, dass das Problem ein fehlendes definiertes Protokoll ist. Ohne ist es falsch. Hast du die entsprechenden Beiträge verstanden? Denn umgesetzt hast du das gar nicht.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@Finux: dass der gesamte Socketcode Schrott ist, wurde Dir jetzt oft genug gesagt.

Andere Sachen hast Du aber auch verschlimmbessert. Aus irgend einem Grund hast Du nicht meine `authentication`-Funktion übernommen, sondern Veränderungen gemacht, die die Funktion um doppelten Code anschwellen lassen und zeigen, dass Du Wörterbücher noch nicht ganz verstanden hast.

Der `in`-Operator wird auf das Wörterbuch angewendet, nicht auf die keys: `if usr_name in AUTHENTICATIONS:`.
Wenn Du diese Prüfung aber schon gemacht hast, dann ist `get` unnötig kompliziert, weil der Zugriff über [] nicht mehr fehlschlagen kann: `AUTHENTICATIONS[usr_name]`.
Das ganze wird also zu:

Code: Alles auswählen

def authentication(connstream, addr):
    usr_name = connstream.recv(4096).decode("utf-8")
    usr_pwd = connstream.recv(4096).decode("utf-8")
    if usr_name in AUTHENTICATIONS:
        auth_pwd = AUTHENTICATIONS[usr_name]
        if auth_pwd == usr_pwd:
            connstream.send(b'Authentication succeeded!')
            logging.debug("CLIENT AUTHENTICATION FROM {} ACCEPTED".format(addr))
            return True
    connstream.send(b'Authentication failed!')
    logging.debug("CLIENT AUTHENTICATION FROM {} NOT ACCEPTED".format(addr))
    return False
`print` und `logging` ist irgendwie doppelt.

In `main` hast Du dann wirkliche Fehler eingebaut. Dadurch dass Du einfach alle sys.exit herausgelöscht hast, obwohl __blackjack__ nochmal explizit geschrieben hatte, was daran gestört hat, hast Du jetzt Code, der nach der „Fehlerbehandlung” weiterläuft, obwohl das `context`-Objekt nicht richtig initialisiert wurde, es also später zu einem Fehler kommt, den man nicht mehr einfach mit dem ursprünglichen Fehler in Verbindung bringen kann. Das ist die Hölle bei der Fehlersuche.

Das sind zwar aktuell auch Deine kleinsten Probleme, aber an die großen willst Du ja nicht ran.
Benutzeravatar
Finux
User
Beiträge: 32
Registriert: Mittwoch 18. September 2019, 13:59

die die Funktion um doppelten Code anschwellen lassen
Der redundante Code ist mir aufgefallen, allerdings bin ich einfach nicht auf diese Schreibweise gekommen. Ich kam einfach nicht drauf.

Zum Restinhalt aller Fragenden:
Ich gebs auf.
Ich kann schlecht Dinge vermitteln von denen ich fast nichts verstehe zu einer Situation dessen Umfang mir einfach selbst nicht ganz klar ist.
Ich bin ganz klar auf die Hilfe meines Kollegen angewiesen. Ob die daraus resultierende Lösung (also nach Rücksprache mit ihm) dann zu einem brauchbaren technischen Einsatz wird, steht auf einem anderen Blatt..
Sorry, dass ich eure Zeit + Nerven so beansprucht habe. Mir sind einfach ganz viele Zusammenhänge überhaupt nicht klar, somit verstehe ich das Unverständnis nicht, und letztlich führt es nur noch zur Frustration aller Seiten.
Bildlich: Stellt euch vor ihr müsst eine Zinsrechenaufgabe lösen, habt aber das 2.-6 Schuljahr nicht mitbekommen. Mir fehlt einfach eine vernünftige Schulung von der 1. Sekunde an.

Ich danke euch allen für die Geduld + Mühen.
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Wenn das, was du da probierst, den Anspruch hat, später produktiv verwendet zu werden, statt “nur” eine Übung zur socket Kommunikation zu sein - dann gilt erst recht, was hier schon mehrfach vorgetragen wurde: das macht man nicht selbst. Benutz eine der vielen verfügbaren Middlewares zur Kommunikation. Welche dabei die geeignetste ist, hängt von den konkreten Anforderungen ab. Wenn du die mal darlegst, kann man dazu was sagen.
Benutzeravatar
Finux
User
Beiträge: 32
Registriert: Mittwoch 18. September 2019, 13:59

Hallo,

hoffe es sorgt nicht für Unmut, aber wollte euch ja auf dem Laufenden halten :)

Vom Kollegen wurden mir heute 2 wichtige Fragen zum Code gestellt:
1. Kann der Client denn etwas senden wenn er gerade Daten empfängt? --> ja, das kann er!
2. Wann arbeitet der Server mit receive? --> und da wurde mir klar, warum der Server einen evtl. nachgelieferten Befehl erst nach seiner "aktuellen Aufgabe" sendet. :]

Damit wäre meine Ursprungsfrage "Wieso kann ich während dem Empfangen auf der Client-Seite nichts senden?" endlich geklärt. Ich habe die Struktur demnach auf der server.py etwas umgebaut. Hier steht das receive in seinem eigenen "client-thread" wie gewohnt ganz oben, sobald aber eine Funktion aufgerufen+gesendet werden soll mache ich hier einen eigenen Thread dafür auf. Es funktioniert (im localhost) ohne Probleme.

Eine Kontrollmöglichkeit zur Prüfung ob eine Verbindung noch besteht werde ich ebenso noch einbauen.

Das Thema "Passworthashing" werde ich mir in Ruhe nochmal anschauen, hat derzeit allerdings keine Priorität.

Nochmal zu dem mir unangenehmen Thema "Protokoll":
Ich werde (wenn es mir die Zeit erlaubt) vom Sender vor der eigentlichen Message eine feste Bytelänge an den Empfänger übertragen. Erst dann sende ich, was ich eigentlich senden wollte. Stimmt beim Empfänger die Länge der emfangenen Pakete z.B. mit der Bytelänge überein, weiß ich dass das Paket nun weiterverarbeitet werden darf, da es komplett ist. Ist es das nicht, teile ich dem Sender mit, dass er alles noch einmal schicken soll. Usw. usw.

Seid nicht stressed, ich glaube ich habe ein paar sehr gute Dinge daraus gelernt, auch wenn ich noch weit entfernt von einem soliden Programm bin, aber dafür muss ich eben Fehler machen und Fragen stellen :)
Nochmals Danke, dass ihr euch die Zeit genommen habt.

LG
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@Finux: nochmal die Frage, warum Du alles selbst machen willst, statt gut getestete bewährte Bibliotheken mit Standardprotokollen zu verwenden?

Dass Dein ›nochmal schicken‹ nicht gerade die intelligenteste Lösung ist, weißt Du hoffentlich selbst.
Statt dessen einfach so lange lesen, bis Du genug Bytes zusammen hast:

Code: Alles auswählen

def recv_bytes(socket, length):
    data = b''
    while len(data) < length:
        data += socket.recv(length - len(data))
    return data

def recv_packet(socket):
    length = int.from_bytes(recv_bytes(socket, 8), 'little'))
    return recv_bytes(socket, length)

def send_packet(socket, data):
    socket.sendall(len(data).to_bytes(8, 'little'))
    socket.sendall(data)
Benutzeravatar
Finux
User
Beiträge: 32
Registriert: Mittwoch 18. September 2019, 13:59

@Sirius3: Die Kollegen sind nicht sehr überzeugt von Standardlibrarys, nativ ist zwar aufwendiger, aber man weiß "recht genau" was man hier tut ohne sich auf "Fremdwerke" verlassen zu müssen. Ein Kollege ist allerdings auch der Meinung, dass man mit Standardbibliotheken gut zurecht käme... daher der ganze Aufwand.
Danke für den Code! Den saug ich mir morgen in aller Ruhe mal in den Kopf! :)
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Sich gegen mitgelieferte Standards stellen, aber dann mit threading und mangelndem know-how was zusammenzimmern - 🤦‍♂️

Wieso ist das mitgelieferte socket-Modul denn koscher, aber das mitgelieferte urllib und HTTPServer nicht? Das muss man nicht verstehen, oder?

Ich weiss das es nicht deine Schuld ist. Aber sowas ist wirklich hirnverbrannt. Man muss es ja nicht treiben wie die JS-Community, bei der leicht hunderte Abhaengigkeiten fuer ein triviales Projekt zusammenkommen. Aber jeden Morgen den Feuerstein aus dem Steinbruch brechen, und sich ein Rad zimmern muss es ja nun auch nicht sein. Vor allem nicht in Python, das schon so viel mitbringt.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Du weißt eben nicht recht genau, was Du hier tust. Mit diesem Argument darfst Du Dich auch nicht auf dieses sockets und den darunterliegenden C-Routinen verlassen. Da weiß man ja nicht, was passiert. An Deiner Stelle würde ich auch auf das völlig undurchsichtige Betriebssystem verzichten und den Maschinencode direkt im Hex-Editor schreiben, wer weiß schon, was da ein sogenannter Compiler aus meinem schönen Code macht.
Antworten