Simpler Chat (Server-Client) Multithreading

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
onur22
User
Beiträge: 19
Registriert: Freitag 28. Dezember 2018, 21:25

Hallo,

ich bin aktuell dabei ein ganz einfaches Chat Programm zu schreiben. Das hat auch alles soweit gut geklappt, nun versuche ich gerade gewisse Funktionen, konkret das senden und empfangen von Nachrichten, zu parallelisieren. Damit man quasi eine Nachricht tippen kann und gleichzeitig etwas empfangen kann(recv und send). Dazu habe ich mich nun in Threading und Multiprocessing eingelesen. Beides sollte eig. funktionieren für mein Tool. Leider kriege ich beides nicht zum laufen hin...

In den folgenden Codes könnt ihr ganz unten meine Versuche sehen, multiprocessing oder threading einzusetzen. Das hat leider so nicht geklappt.
Mein Server Code :

Code: Alles auswählen

import socket
import multiprocessing
import easygui
import time
from threading import Thread


port = 
IP
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

server_socket.bind(('IP', port))


hostname = socket.gethostname()
IPAddr = socket.gethostbyname(hostname)
print("Server connects with your IP: " + IPAddr)

print("Server running")
server_socket.listen(10)
(print("Wait for a Message"))
(client_socket, addr) = server_socket.accept()
print(addr)




def ResvMessage():
 while socket.error != None:
  time.sleep(0.2)
  print("Waiting for an answer")
  b = "stop"
  msg2 = client_socket.recv(1024)
  k = (str(msg2, "utf8"))
  print(k)
  if k == b:
   socket.close(server_socket)
   sys.exit()
   print("server close")
  else:
   print("Waiting for more messages")



def Senden():
 while socket.error != None:
  time.sleep(0.2)
  client_socket.send((bytes(input("Type in your Message"), "utf8")))
  msg2 = client_socket.recv(1024)
  k = (str(msg2, "utf8"))
  print(k)

ResvMessage()
Senden()





t = Thread(target=Senden(), args=())
t2 = Thread(target=ResvMessage(), args=())
t.start()
t2.start()


Client:
[code]import socket
import multiprocessing
import easygui
import time
from threading import Thread
 
IP =
port =
 
 
print("Clientporgramm started")
 
 
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 
 
hostname = socket.gethostname()
IPAddr = socket.gethostbyname(hostname)
print("Server Connects with your IP: " + IPAddr)
 
 
x = client_socket.connect(('IP', port))
 
 
def sendmessages():
    while socket.error != None:
        time.sleep(0.2)
        Nachricht = input("Your Message:")
        if Nachricht != "":
            b = client_socket.send(bytes(Nachricht, "utf8"))
            print(b)
        else:
            print("Type in your Message")
 
 
 
def Recvmessages():
 while socket.error != None:
    time.sleep(0.2)
    msg = client_socket.recv(1024)
    print(str(msg, "utf8"))
    print("Got a Message")
 

 
 

 
t = Thread(target=Recvmessages(), args=())
t2 = Thread(target=sendmessages(), args=())
t.start()
t2.start()
 
Danke schonmal, falls mir jemand helfen kann
alles ist als Einsteiger möglich. Es ist nur die Frage, wie lange es dauert, bis man die nötigen Vorkenntnisse erworben hat.
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

multiprocessing geht hier nicht. Ein socket zwischen Prozessen zu teilen ist nicht so ohne weiteres drin. Multithreading geht, aber ich würde dir stattdessen zu select.select oder am besten gleich dem asyncio Framework ab Python 3.5 raten.
Sirius3
User
Beiträge: 17738
Registriert: Sonntag 21. Oktober 2012, 17:20

@onur22: als erstes solltest Du Code ohne SyntaxFehler posten. `IP` ist nicht definiert und 'IP' bei bind nicht sinnvoll. Eingerückt wird immer mit 4 Leerzeichen pro Ebene, nicht einem. socket.error ist niemals gleich None, weil das eine Klasse ist. sys.exit sollte in einem sauberen Programm nicht vorkommen. Dein Socket-Code ist wie 99.999% der Beispiele, die Du im Netz findest, totaler Schrott. Das ist einfach nur kaputt, und ich kann es nicht mehr sehen. `recv` muß damit zurechtkommen, dass pro Aufruf nur 1 Byte zurückgeliefert wird, und dann ist Dein UTF8 kaputt. Und send sendet im Zweifel nur einen Teil Deiner Nachricht, der Rest ist futsch. Und wenn Du dann noch zwei Threads hast, die gleichzeitig Lesen, dann ist auch nicht klar, wer was bekommt.
Sockets sind nicht einfach und Threads auch nicht. Warum glaubt alle Welt, damit ein EINFACHES Chatprogramm schreiben zu können.
onur22
User
Beiträge: 19
Registriert: Freitag 28. Dezember 2018, 21:25

Hier der fertige Chat(Client, Server ist ähnlich aufgebaut), funktioniert nun so, wie es sein soll. Also falls mal jmd über Google die gleiche Fragen hat, wie ich...

Code: Alles auswählen

def Tkiinter():
    master = Tk()
    Label(master, text="Client").grid()


    Button(master, text='Send', command=Start,).grid(row=7, column=1, sticky=W, pady=4)
    Button(master, text='Baue Verbindung mit dem Chatserver auf', command=Startconn,).grid(row=8, column=1, sticky=W, pady=4)
    #Button(text='Beende das Program', command=exit1,).grid(row=9, column=1, sticky=W, pady=4)
    global e1
    e1 = Entry(master)

    e1.grid()
    e1.insert(index=0 ,string="Type in here your Message")
    global T
    T = Text( height=10, width=50)
    T.grid()
    T.insert(END, "Chat Verlauf")



    mainloop()







def Start():
    first = threading.Thread(target=Send, args=(), name=Send)
    first.start()

def Send():
        Message = e1.get()
        client_socket.send((bytes(" \n" "Client:  " + Message, "utf8")))
        client_socket.send((bytes(" \n" + "Client:  " + Message, "utf8")))
        time.sleep(0.2)
        T.insert(END, "\n" + "Client :" + e1.get())




def Recive():
    while True:
        Message = client_socket.recv(1024)
        print(Message)
        time.sleep(0.2)

def recvmessageprint():
    time.sleep(0.5)
    while True:
        T.insert(END, client_socket.recv(1024))


def Startconn():
    global client_socket
    client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    hostname = socket.gethostname()
    IP = "''
    try:
     x = client_socket.connect((IP, 2422))
     T.insert(END, "Using Port 1")
    except:
     client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
     b = client_socket.connect((IP, 2433))
     T.insert(END, "Using Port 2")
    second = threading.Thread(target=Recive, args=(), name=Recive)
    forth = threading.Thread(target=recvmessageprint, args=(), name=recvmessageprint)

    second.start()
    forth.start()

Tkiinter()
second = threading.Thread(target=Recive, args=(), name=Recive)
forth = threading.Thread(target=recvmessageprint, args=(), name=recvmessageprint)

second.start()
forth.start()
alles ist als Einsteiger möglich. Es ist nur die Frage, wie lange es dauert, bis man die nötigen Vorkenntnisse erworben hat.
Sirius3
User
Beiträge: 17738
Registriert: Sonntag 21. Oktober 2012, 17:20

@onur22: um ehrlich zu sein, ich hoffe, dass das niemand per Google findet.
Es fehlen die Zeilen mit den *-Importen, die man nicht benutzen sollte. Vergiß dass es »global« gibt, das macht nur Probleme, und löst keine; vor allem nicht in Zusammenhang mit Threads.

Wenn man keinen guten Namen für eine Funktion hat, dann schreibt man am besten einen mit Schreibfehler; genauso gut sind Variablennamen die nur ausgeschriebene Zahlen sind.
Sarkasmus-Ende.

GUIs dürfen nur vom Hauptthread aus verändert werden, nicht in Threads, dafür muß man über Queues kommunizieren.
Eingerückt wird immer mit 4 Leerzeichen pro Ebene, keine nackten Excepts.

Wie schon vermutet, ist der ganze Socket-Code kaputt. Nimm wenigstens eine zeilenbasiertes Protokoll. Dass es zwei Threads gibt, die sich gegenseitig die Bytes wegnehmen und zweimal gesendet wird, hab ich auch noch nie gesehen.

Wenn Du ernsthaft lernen willst, wie man richtig programmiert, kann man Dir hier helfen, bisher hast Du aber nicht den Eindruck erweckt, dass Du Hilfe willst. Falls ich falsch liege, korrigier mich bitte.
onur22
User
Beiträge: 19
Registriert: Freitag 28. Dezember 2018, 21:25

Sirius3 hat geschrieben: Samstag 19. Januar 2019, 15:44 @onur22: um ehrlich zu sein, ich hoffe, dass das niemand per Google findet.
Es fehlen die Zeilen mit den *-Importen, die man nicht benutzen sollte. Vergiß dass es »global« gibt, das macht nur Probleme, und löst keine; vor allem nicht in Zusammenhang mit Threads.

Wenn man keinen guten Namen für eine Funktion hat, dann schreibt man am besten einen mit Schreibfehler; genauso gut sind Variablennamen die nur ausgeschriebene Zahlen sind.
Sarkasmus-Ende.

GUIs dürfen nur vom Hauptthread aus verändert werden, nicht in Threads, dafür muß man über Queues kommunizieren.
Eingerückt wird immer mit 4 Leerzeichen pro Ebene, keine nackten Excepts.

Wie schon vermutet, ist der ganze Socket-Code kaputt. Nimm wenigstens eine zeilenbasiertes Protokoll. Dass es zwei Threads gibt, die sich gegenseitig die Bytes wegnehmen und zweimal gesendet wird, hab ich auch noch nie gesehen.

Wenn Du ernsthaft lernen willst, wie man richtig programmiert, kann man Dir hier helfen, bisher hast Du aber nicht den Eindruck erweckt, dass Du Hilfe willst. Falls ich falsch liege, korrigier mich bitte.
Ich versuche meine Ergebnisse nur zu teilen, vielleicht helfen sie ja jemandem. Ich meine in der Coding welt findet man ja so gut wie nie perfekte Lösungen, sondern muss selbst immer was optimieren :) Den Code habe ich ja für niemanden außer mich geschrieben(der code ist auch nur paar Zeilen lang, aber wenn jemand mal sowas sehen will, dann findet mans vielleicht ganz hilfreich. Sicher ist da viel Mist drin, kann das auch löschen, wenn du mir sagst, wie man das hier macht

"GUIs dürfen nur vom Hauptthread aus verändert werden, nicht in Threads, dafür muß man über Queues kommunizieren."
Wird doch bei mir im Hauptthread ausgeführt, oder?

"Wie schon vermutet, ist der ganze Socket-Code kaputt. Nimm wenigstens eine zeilenbasiertes Protokoll. Dass es zwei Threads gibt, die sich gegenseitig die Bytes wegnehmen und zweimal gesendet wird, hab ich auch noch nie gesehen."
Was meinst du mit kaputt? Es funktioniert nach meinem Empfinden genau so, wie es soll

"Wenn Du ernsthaft lernen willst, wie man richtig programmiert, kann man Dir hier helfen, bisher hast Du aber nicht den Eindruck erweckt, dass Du Hilfe willst. Falls ich falsch liege, korrigier mich bitte."
Ich nehme Hilfe sehr gerne an :), dafür bin ich hier. Nur wenn du mal den anderen Beitrag von dir nochmal liest, fällt dir vielleicht auf, wieso es keine Reaktion gibt...
alles ist als Einsteiger möglich. Es ist nur die Frage, wie lange es dauert, bis man die nötigen Vorkenntnisse erworben hat.
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

Noe, dein Send manipuliert aus dem Thread heraus "T" - das kann beliebig krachen.

Und kaputt ist der Socket-Code weil du faelschlicherweise annimmst, ein Sendevorgang korrespondiert mit einem Paket empfangener Daten. Dem ist so nicht. Sockets sind DatenSTROEME. Da kommen Bytes rausgetroepfelt, und ob die eine komplette Nachricht darstellen, dass musst du mit einem Protokoll sicherstellen. Zuzugebenermassen ist das bei deiner Chat-Implementierung akut noch nicht so relevant, weil jedes Byte das kommt auch immer dargestellt wird - wenn die Nachricht also in mehreren Happen kommt, dann werden die einfach hintereinander gepappt. Aber das ist ganz bestimmt keine Eigenschaft, die dir bewusst ist. Und beim naechsten mal geht das dann halt mal in die Hose.
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@onur22: Deine Ergebnisse helfen niemandem weil es nur ein weiteres kaputtes Beispiel ist, von denen man im Netz sowieso schon viel zu viele findet. Ob etwas nach Deinem ”Empfinden” funktioniert ist irrelevant wenn es grobe Fehler enthält. Es geht hier nicht um eine perfekte Lösung sondern eine die auch tatsächlich funktioniert und nicht abstürzt oder Datensalat produziert. Dein Empfinden ändert sich sicher wenn das dann mal passiert ist. Man kann auch aus dem 10 Stock springen und bei jeder Etage an der man vorbeifällt sagen ”funktioniert doch”. Bis es unten *Platsch* macht. Und das hätte man oben im 10 Stock schon sehen können, dass das keine gute Idee ist.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Daniel26
User
Beiträge: 26
Registriert: Freitag 2. Juni 2017, 09:29

Moin,

ich beschäftige mich auch gerade mit dem Thema Sockets und Multithreading.
Jetzt habt ihr dem Theradersteller zig-mal an den Kopf geworfen, dass sein Code Sch..... ist. Aber es gibt nicht einen vernünftigen Vorschlag was er besser machen könnte oder wo er nachschauen muss damit er selbst die Lösung findet.

Eigentlich wollte ich auch ein Thema mit meinem Problem eröffnen, aber das lass ich wohl besser bleiben.

Gruß
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

Sicher kein brillanter Thread. Der Frust meinerseits und der anderen über den nicht enden wollenden falschen Umgang mit sockets und Threads ist natürlich keine Schuld der Anfänger. Auf der anderen Seite ist die “aber es geht für mich”-Attitüde auch nicht gerade Ausweis eines echten Bemühens, das Problem zu verstehen.

Und ganz ohne Hinweise wie man es besser macht ist der Thread nicht. Es kam sowohl der Hinweis auf asyncio zur Vermeidung von Threads, als auch auf zeilenbasierte Protokolle als zumindest bei einem Chat geeignete Methode.
Daniel26
User
Beiträge: 26
Registriert: Freitag 2. Juni 2017, 09:29

Gibts denn irgendwo ein gutes Tutorial wie man mit Sockets arbeitet?
Ich versuche mir gerade einen "Server" zu bauen der Kommandos annimmt, verarbeitet und das Ergebnis zurück gibt. In etwa wie bei einem Telnet-Server.
Dabei sollen verbindungen von mehreren CLients möglich sein.
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich kenne keines. Wenn du Kontrolle über beide Seiten der Kommunikation hast, dann empfehle ich dir nachdrücklich, eine bestehende Lösung zu benutzen. ZeroMQ oder nanomsg. Letzteres ist mein aktueller Favorit. Die nehmen dir viel von der Komplexität ab, die dir überhaupt nicht bewusst ist.
Sirius3
User
Beiträge: 17738
Registriert: Sonntag 21. Oktober 2012, 17:20

@Daniel26: um helfen zu können, braucht man ein konkretes Problem. Es gibt etliche Varianten, wie man einen "simplen Chat" programmieren kann.
Das Beispiel (https://docs.python.org/3/library/async ... ng-streams) ist leider mit read-100Bytes statt readline zu verwenden, was bei einem Chat sinnvoller ist, aber als Einstieg zeigt es schonmal grob, wie es geht.
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

@Sirius3 was macht das denn besser jenseits der Nutzung von asyncio? Meine ich ganz ernst. Die einkommenden messages sind doch genauso potentiell gestückelt. Klar, readline würde helfen. Und das Writer.drain garantiert vollständiges versenden vor dem schließen. Also 2 von 3 Punkten.

Der Stream Reader hat einiges mehr zu bieten als normale sockets.

https://docs.python.org/3/library/asyncio-stream.html

Zb feste Größen oder bis zu beliebigen trennzeichen lesen.
Sirius3
User
Beiträge: 17738
Registriert: Sonntag 21. Oktober 2012, 17:20

@__deets__: genau, das was ich geschrieben habe, die Klasse ist die richtige, das Beispiel zur Anwendung suboptimal.
Daniel26
User
Beiträge: 26
Registriert: Freitag 2. Juni 2017, 09:29

Wenn du Kontrolle über beide Seiten der Kommunikation hast, dann empfehle ich dir nachdrücklich, eine bestehende Lösung zu benutzen. ZeroMQ oder nanomsg. Letzteres ist mein aktueller Favorit. Die nehmen dir viel von der Komplexität ab, die dir überhaupt nicht bewusst ist.
Ich gestalte nur den Server. Der "Client" kann irgendwas sein. Zum testen benutze ich nen Telnet-Client.
Es geht um ein Script, das Anfragen auf einem Port annimmt und drauf hin die passenden Werte ausliefert.

Gruß
Sirius3
User
Beiträge: 17738
Registriert: Sonntag 21. Oktober 2012, 17:20

@Daniel26: bei "Telnet" also dann ein zeilenbasiertes Protokoll. Wie schon geschrieben, ist dann der Einstieg über die Pythondokumentation zu asyncio.
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Alternativ hätten `socket`-Objekte eine Methode um sich ein Dateiobjekt geben zu lassen, wo man dann auch Zeilen lesen kann ohne sich selbst um die ganzen Details kümmern zu müssen. Eventuell möchte man sich dazu noch etwas aus dem `io`-Modul zum ”wrappen” nehmen, damit es eine Text- und keine Bytes-Datei ist.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

Daniel26 hat geschrieben: Mittwoch 6. Februar 2019, 13:54
Ich gestalte nur den Server. Der "Client" kann irgendwas sein. Zum testen benutze ich nen Telnet-Client.
Es geht um ein Script, das Anfragen auf einem Port annimmt und drauf hin die passenden Werte ausliefert.
Naja, aber du kannst ja an den client auch eine Vorgabe machen. Denn wie gesagt - automatisch passiert da nichts sinnvolles. telnet/newline ist das absolute Mininmum, und wenn das fuer dich reicht - ok. Aber wehe da kommen mal binaere Daten ins Spiel, dann hast du den Salat und musst das ganze irgendwie per base64 oder so uebertragen. Damit ja kein Zeilenende-Zeichen in die Daten kommt.
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Oder den HTTP-Weg, eine Textzeile, die sagt wie viele Bytes folgen, und dann exakt so viele Bytes senden/empfangen. Wobei dann, wenn das Protokoll auch „stateless“ sein darf, natürlich wieder gilt: Nichts selbst basteln wenn es das schon fertig gibt, und HTTP gibt es schon fertig. :-)
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten