socket Verständnisprobleme

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
fsociety
User
Beiträge: 4
Registriert: Donnerstag 12. Januar 2023, 10:23

Grüßt euch,

ich bin neu hier im Forum und hoffe hier etwas Hilfe zu bekommen. Vorher gesagt, ich beschäftige mich schon recht lange mit Python aber ich bin alles andere als ein Profi. Man fängt ja immer irgendwo an.

Ich brauche ein bisschen Hilfe bezüglich sockets, angepasst auf mein Programm, was ich gerade schreibe. Kurze Erklärung zum Programm: Ich möchte von überall aus meine Computer Zuhause steuern. Ich habe zwei Computer und dort befindet sich dann ein Python Script mit der Klasse Clients. Dann habe ich ein Server im Internet mit nem Script, welche die Klasse Server enthält. Der Server steuert alle Verbindungen. Dann habe ich noch einen anderen Client, der einfach als Benutzeroberfläche für die Steuerung dient. Um es anschaulicher zu machen:

Ich schicke über meinen Steuerclient die Nachricht "hello computer1" an den Server. Der Server erkennt, dass es der Steuerclient ist und schickt die Nachricht an den Client auf mein Computer1 zuhause weiter. Computer1 schickt an den Server "Hello World" an den Server. Server erkennt, dass es sich um den Client von Zuhause handelt und schickt die Nachricht an meinen Steuerclient weiter.
Ich denke das Prinzip ist klar.

Leider habe ich scheinbar das Prinzip von socket im Kombination mit Multithreading nicht ganz verstanden. Ich bekomme immer ein BrokenPipeError. Kann mir jemand erklären, was ich falsch mache?

Ich habe schon etwas an Code. Ich denke es ist klar, was mein Ziel ist. Danke im Vorraus!

Server:

Code: Alles auswählen

from http import client
from threading import Thread
from time import sleep
import ctypes, socket, sys
from typing import Tuple

class UserClient():
    username = ""
    password = ""
    connection = ""

    def __init__(self, username, password, permissions ,conn: socket) -> None:
        self.username = username
        self.password = password
        self.conn = conn
        self.perms = permissions
    
    def take_commands(self):
        while True:
            self.conn.send("cmd".encode())
            cmd = self.conn.recv(1024).decode()
            if(cmd == "help"):
                self.conn.send("help".encode())



class Server():

    def __init__(self, ip: str, port:  int, hostname: str) -> None:
        self.hostname = hostname
        self.bots = []
        self.clients = []
        self.dead_bots = []
        self.connection = (ip, port)
        self.running = True
        if self.bind(self.connection):
            print("Started")

    def bind(self, connection: Tuple[str, int]) -> bool:
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock.bind(connection)
        self.sock.listen()

        print("Listening to Clients...")
        Thread(target=self.collect).start()

        return True

    def collect(self):
        while self.running:

            try:
                conn, addr = self.sock.accept()
                if addr not in self.bots:
                    recv = self.recv(conn)
                    if recv.startswith("c"):
                        creds = recv.split("#")
                        user, passw, permissions = self.check_login(creds[1], creds[2])
                        if permissions != None:
                            self.send(conn, "accepted " + self.hostname)
                            self.broadcast_clients(f"New Client: {conn.getpeername()}")
                            user = UserClient(user, passw, permissions, conn)
                            self.clients.append(user)
                            Thread(target=user.take_commands).start()
                        else:
                            self.broadcast_clients(f"Invalid Login: {conn.getpeername()}")
                            self.send(conn, "invalid creds")
                    elif recv.startswith("b"):
                        pass
            except socket.timeout:
                continue
            except socket.error:
                continue

        pass

    def broadcast_clients(self, message: str):
        send = f"[{self.hostname}] » " + message
        print(send)

        for client in self.clients:
            self.send_and_recv(client, send)

    def send_and_recv(self, client, data: str) -> str:
        client.send(data.encode())
        return client.recv(1024).decode()
    
    def send(self, client, data: str):
        client.send(data.encode())

    def recv(self, client) -> str:
        return client.recv(1024).decode()

    def check_login(self, username: str, password: str):
        with open("./creds.txt") as f:
            logins = [x.strip() for x in f.readlines()]

            for login in logins:
                cred_types = login.split(":")
                user = cred_types[0]
                passw = cred_types[1]
                permissions = cred_types[2].split(".")
                if user == username and passw == password:
                    return user, passw, permissions
                else:
                    return None

        

def main():
    Server("localhost", 1337, "fsocietyNET")

if __name__ == "__main__":
    main()
Client:

Code: Alles auswählen

import socket
from threading import Thread
from typing import Tuple

class Client():

    def __init__(self, conn: Tuple[str, int], username: str, password: str) -> None:
        self.hostname = ""
        self.username = username
        self.password = password

        self.prefix = ""
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock.settimeout(5)

        if self.bind(conn):
            if self.login():
                Thread(target=self.command_terminal).start()
            else:
                print("invalid creds")
        else:
            print("err")

    def bind(self, conn: Tuple[str, int]) -> bool:
        try:
            self.sock.connect(conn)

            return True
        except socket.timeout or socket.error:
            return False

    def login(self) -> bool:
        data = f"c#{self.username}#{self.password}"
        recv = self.send_and_recv(data)
        if recv.startswith("accepted"):
            self.hostname = recv.split(" ")[1]
            self.prefix = f"{self.username}@{self.hostname}$ "
            return True
        else:
            return False


    def send_and_recv(self, data) -> str:
        self.sock.send(data.encode())
        return self.sock.recv(1024).decode()

    def send(self, data):
        self.sock.send(data.encode())

    def recv(self) -> str:
        return self.sock.recv(1024).decode()


    def command_terminal(self):
        while True:
            command = input(self.prefix)
            recv = self.send_and_recv(command)
            if(recv) == "what":
                print("[!] Unknown command.")
            else:
                print(recv)
def main():
    print("Welcome to my Homenetwork!")
    user = input("Username: ")
    password = input("Password: ")

    conn = ("127.0.0.1", 1337)
    client = Client(conn, user, password)

if __name__ == "__main__":
    main()


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

@fsociety: Es wird auf jeden Fall schon mal der typische Fehler gemacht TCP so zu behandeln als wären `send()` und `recv()` nachrichtenbasiert. TCP ist ein Datenstrom . Wenn man darüber Nachrichten übermitteln will, dann muss man selbst dafür sorgen, dass es ein Protokoll gibt wo der Anfang und das Ende von einzelnen Nachrichten erkannt wird.

`send()` sendet nicht garantiert alles, sondern gibt als Rückgabewert wie viele Bytes tatsächlich gesendet wurde. Man muss das also so oft machen bis tatsächlich alles gesendet wurde. Oder `send_all()` verwenden.

Auf der anderen Seite gibt es so eine Methode nicht fertig, denn wo eine Nachricht zuende ist, lässt sich nicht allgemein sagen. `recv()` liefert wirklich garantiert nur 1 Byte. Der Code muss also so geschrieben sein, dass er auch damit klar käme, dass `recv()` wirklich immer nur 1 Byte liefert pro Aufruf.

An der Stelle muss man dann überlegen ob man wirklich ein Protokoll selbst basteln will, oder nicht doch lieber ein bestehendes verwendet, wo es schon eine fertige Bibliothek für gibt.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das ganze auf HTTP umzustellen, waere deutlich leichter. Und es sei mir die Frage gestattet: warum nicht sowas wie TeamViewer benutzen?
fsociety
User
Beiträge: 4
Registriert: Donnerstag 12. Januar 2023, 10:23

__deets__ hat geschrieben: Donnerstag 12. Januar 2023, 11:56 Das ganze auf HTTP umzustellen, waere deutlich leichter. Und es sei mir die Frage gestattet: warum nicht sowas wie TeamViewer benutzen?
Kannst du das genauer erklären? Und klar, ich könnte sowas nutzen. Aber es geht mir darum etwas eigenes zu programmieren, damit ich was lerne :D
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Netzwerkprogramierung ist schwer. Sehr schwer. Du glaubst, du hast etwas, das funktioniert. Das stimmt nicht. Du gehst nicht robust mit Fehlern um, du hast kein Protokoll, um selbst die "normalen" Verhaltensweisen der Datenstrom-basierten Sockets zu behandeln. Es ist viel einfacher, ein bestehendes, robustes und etabliertes Protokoll wie HTTP zu benutzen. ZB mit einem Webserver auf deinem PC, und einem client (urllib oder requests).
fsociety
User
Beiträge: 4
Registriert: Donnerstag 12. Januar 2023, 10:23

__blackjack__ hat geschrieben: Donnerstag 12. Januar 2023, 11:36 @fsociety: Es wird auf jeden Fall schon mal der typische Fehler gemacht TCP so zu behandeln als wären `send()` und `recv()` nachrichtenbasiert. TCP ist ein Datenstrom . Wenn man darüber Nachrichten übermitteln will, dann muss man selbst dafür sorgen, dass es ein Protokoll gibt wo der Anfang und das Ende von einzelnen Nachrichten erkannt wird.

`send()` sendet nicht garantiert alles, sondern gibt als Rückgabewert wie viele Bytes tatsächlich gesendet wurde. Man muss das also so oft machen bis tatsächlich alles gesendet wurde. Oder `send_all()` verwenden.

Auf der anderen Seite gibt es so eine Methode nicht fertig, denn wo eine Nachricht zuende ist, lässt sich nicht allgemein sagen. `recv()` liefert wirklich garantiert nur 1 Byte. Der Code muss also so geschrieben sein, dass er auch damit klar käme, dass `recv()` wirklich immer nur 1 Byte liefert pro Aufruf.

An der Stelle muss man dann überlegen ob man wirklich ein Protokoll selbst basteln will, oder nicht doch lieber ein bestehendes verwendet, wo es schon eine fertige Bibliothek für gibt.
Okay ich verstehe. Zumindest glaub ich das. Also kann man das so sagen, dass zu jeder recv() Funktion eine send() Funktion gehört, oder hab ich das falsch verstanden?
Im Prinzip möchte ich einfach ein Serverside Script haben, welches andere Clients die sich verbinden klassifizieren kann und und an die einzelnen Clients Nachrichten senden und Empfangen kann. Gibt es dafür schon Code Quellen oder Libaries, die was ähnliches behandeln?
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du hast das falsch verstanden. Ein send sendet nicht notwendigerweise alle Daten. Und ein recv empfaengt nicht notwendigerweise alle Daten. Es ist also genau *nicht* so wie du sagst, das die paarweise passen. Ganz im Gegenteil.

Und die Frage nach Libraries, die etwas aehnlich behandeln, wurde doch schon lange beantwortet: zB HTTP server und clients. Gibt's einen Grund, die nicht zu nehmen?
fsociety
User
Beiträge: 4
Registriert: Donnerstag 12. Januar 2023, 10:23

__deets__ hat geschrieben: Donnerstag 12. Januar 2023, 14:24 Und die Frage nach Libraries, die etwas aehnlich behandeln, wurde doch schon lange beantwortet: zB HTTP server und clients. Gibt's einen Grund, die nicht zu nehmen?
Sorry, wie gesagt ich bin wirklich noch am Anfang, was Netzwerkprogrammierung angeht.
Kannst du mir ein kleines Server Client Codebeispiel welches HTTP nutzt zeigen? Im Prinzip soll der Server von Clients Nachrichten empfangen und an andere Clients weiterleiten.
Benutzeravatar
sparrow
User
Beiträge: 4164
Registriert: Freitag 17. April 2009, 10:28

Bekannte Frameworks für HTTP in Python sind Django (all inklusive) und Flask (schmal und man muss sich ggf. Module suchen).
Ja, das sind Frameworks mit denen man Webseiten bauen kann. Aber die Grundlage dafür ist nun einmal HTTP als Kommunikationsprotokoll.
Benutzeravatar
grubenfox
User
Beiträge: 412
Registriert: Freitag 2. Dezember 2022, 15:49

Ich habe mich bisher ein wenig mit Django beschäftigt (mit kleinen Webanwendungen experimentiert), weil da alles inklusive ist und ich mich um den ganzen LowLevel-Kram nicht kümmern muss. Also für die eigentliche Anforderung aus dem ersten Posting könnte es passen. Der Client mit der Benutzeroberfläche ist ja eine Webanwendung. Für HTTP an sich, fürchte ich, ist da zuviel "all inklusive" drum herum.

Mit irgendeinem Projekt das mir jetzt nicht einfällt wollte ich mich in diesem Jahr vielleicht mal mit Flask vertraut machen. Wegen "schmal" ist da eben nicht soviel drumherum. Aber mangels Vertrautheit kann ich da nicht mehr zu sagen.

Als ich mal vor Jahren mit Netzwerkkommunikation rumspielen wollte [ich wollte wohl icmp-Pakete austauschen], da hatte ich mir kurz "Twisted" angeschaut, keinen Plan gehabt und mich dann wieder davon abgewendet. Ist twisted heutzutage noch aktiv und relevant? Das dürfte für rein für HTTP (und für andere Protokolle) auf einem guten Low-Level sein. Aber irgendwelche (Web-)anwendungen möchte ich damit nicht erstellen. Da fehlt ja noch alles... an Funktionalität oberhalb der Protokoll-Ebene. Also lieber Flask oder Django für die Anforderung aus dem ersten Posting.

P.S: für meine icmp-Pakete hatte ich damals dann wohl einfach das ping-Programm vom jeweiligen Betriebssystem aufgerufen.... keine Ahnung ob ich das jemals fertig programmiert hatte, aber an diesen Lösungsansatz kann ich mich noch erinnern. Führte dann zur Problematik "Auswertung der Rück- und Ausgaben von Ping"...
Benutzeravatar
noisefloor
User
Beiträge: 3843
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

Ergänzung: mit HTTP und den genannten Webframeworks kannst du dir auch einfache REST-APIs bauen und dann die Daten z.B. per JSON hin- und her senden. Das ist ein megagängier Standard und dazu gibt es reichlich Codebeispiele.

Alternative ginge auch noch RPC (Remote Procedure Call), sass unterstützt Pyton auch OOTB. Keine Ahnung, ob das heute noch irgendwer macht bzw. wie relevant das noch ist.

Gruß, noisefloor
Antworten