Python eigener Chatclient mit Threading

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
Yuzuke
User
Beiträge: 10
Registriert: Dienstag 6. Juli 2010, 12:38

Hi,

nach langer Zeit versuche ich mal wieder was neues und arbeite zum allererstenmal mit Netzwerkcode + Threading. Soweit bin ich derzeit:

Code: Alles auswählen

import socket
import select
import json

class Server:    
    def __init__(self):
        self.startServer()
        self.Connections = [self.Server]
        print self.Connections;
        self.run()
      
    def startServer(self):
        # Set up the server
        self.Server = socket.socket()
        host = socket.gethostname()
        self.Server.bind((host, 12344))
        self.Server.listen(5) # Wait for client

    def run(self):
        # The 'main' function
        print 'Running ... '
        Running = True
        while Running:
            InList,OutList,ExceptList = select.select(self.Connections,[],[])
            print 'Waiting ...'
            for Connection in InList:
                if Connection == self.Server:
                    # Server got a new connecting Client
                    UserSocket, Adress = self.Server.accept() # New User Connection
                    self.Connections.append(UserSocket) # Store the new User Connection
                    print 'User ', Adress, ' connected'
                    print 'All Connections now: ', self.Connections
                else:
                    # Some other Socket got data for the Server
                    Data = Connection.recv(1024)
                    if not Data:
                        print 'No new Data!'
                        break
                    print Data
                    
            for Connection in OutList:
                pass
Das ist mein Server, beim Client hänge ich derzeit noch.

Code: Alles auswählen

import socket
import json
import select
import threading

class Client:
    def __init__(self):
        self.Socket = socket.socket()
        Host = socket.gethostname()
        self.Socket.connect((Host,12344))
        self.Connections = [self.Socket]
        self.run()

    def checkIncoming(self):
        # Check for incoming data
        InList,OutList,ExceptList = select.select(self.Connections,[],[])
        for Connection in InList:
                # Handle incoming connection (Which however is only the Connection Server -> Client)
                Data = Connection.recv(1024)
                if not Data:
                    break
                print Data
                
    def run(self):
        # Main function
        test = threading.Thread(target=self.checkIncoming())
        
        test.start()
        
                          
            
    def send(self,Data):
        Data = json.dumps(Data) # Encode Data
        
        try:
            self.Socket.send(Data)
        except socket.error, e:
            print 'Error sending data: %s' % e

    def kill(self):
        self.Socket.close()
Was ich versuche ist folgendes:

Den Client "dreiteilen". Ein Thread soll beim Client eingehende Nachrichten / Requests bearbeiten, einer ausgehende und einer Nutzerrequests, also wenn der Nutzer z.B. instance.send() anfordert. Vom dem was ich bisher verstanden habe müsste ich so vorgehen:

1. Eine Mainfunktion haben, die bei mir run() heißt und die 3 Threads startet.
2. Jeder der drei Threads muss eine Endlosschleife sein, da ja immer wieder neu nach neuen Requests geschaut werden muss.

Ist das so richtig? Ich habe das ganze vorher ohne Threads versucht und konnte dann zwar z.B. instance.send() vom Client wunderbar benutzen und beim Server kam alles an, allerdings hing sich alles in einer Endlosschleife auf, wenn ich dann noch hinzugefügt habe, dass der Clients permanent nach einkommenden Requests schaut. Lösung wäre ja das angesprochene Threading hierfür.

Und jetzt sagt mir bitte nicht, dass es fertige Lösungen dafür gibt. Ich mache das um was zu lernen.

Also wichtig für mich wäre:
1. Ist mein genereller Gedankengang richtig?
2. Wo liegen meine Fehler?
3. Wieso funktioniert der obige Code nicht?

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

Hi Yuzuke,
Du rufst »checkIncoming« auf, statt die Funktion als »target« dem »Thread« zu übergeben. Es macht wenig Sinn im Client select für eine einzige Verbindung zu verwenden, weil außer Lesen von einem Socket sowieso nichts anderes gemacht wird. Wenn die Gegenstelle geschlossen wird, darauf mit einem break zu reagieren, ist wohl die falsche Strategie, was soll den da ge»break«t werden??
BlackJack

@Yuzuke: Vorweg: Die Namensschreibweise entspricht nicht den Konventionen aus dem
Style Guide for Python Code.

In der `__init__()` sollte ein Objekt vollständig initialisiert werden. Das heisst dort sollten alle Attribute erstellt werden, die den Zustand des Objekts ausmachen. Wenn das *sehr* lang wird für eine Methode, dann kann man das auch auf mehrere Methoden aufteilen, aber das was in die `startServer()` ausgelagtert wurde ist IMHO nicht wirklich genug.

Letztendlich könnte man sogar die Klasse in Frage stellen, denn man kann mit dem Objekt was dort erstellt wird, überhaupt nichts machen weil die Initialisierung auch gleich eine Endlosschleife startet. Das Objekt bekommt der aufrufende Code also nie in einem irgendwie brauchbaren Zustand in die Finger. Das könnte man also auch in einer oder mehrere Funktionen stecken und damit den Code konzeptionell vereinfachen.

Ein Semikolon um Zeilen abzuschliessen ist in Python nicht nötig.

`Running` in der `Server.run()`-Methode wird nie benutzt. Da kann man auch einfach ``while True:`` schreiben und hat einen Namen eingespart.

Das 'Waiting...' wird zu spät ausgegeben, nämlich *nach* der Zeile die mit dem `select()`-Aufruf wartet.

„Address” schreibt sich mit zwei „d”.

Das ``recv(1024)`` wird deutlich komplizierter werden müssen, denn TCP ist ein Datenstrom und es wird nicht garantiert wieviel bei einem Aufruf dort von dem Strom gelesen wird (ausser dass mindestens 1 Byte gelesen wird). Das kann also sowohl ein Teil von dem sein was mit einem `send()` auf der Gegenseite geschrieben wurde, es kann aber auch Daten von zwei Verschiedenen `send()`-Aufrufen sein. Du musst also ein Protokoll definieren das es Dir erlaubt einzelne Nachrichten voneinander Unterscheiden zu können, und Code der Teile von Nachrichten puffert bis eine komplette Nachricht beisammen ist. Dabei darf ein Teil einer eventuell folgenden Nachricht nicht verloren gehen. Oder auch ganze Nachrichten.

Das ``break`` bricht die Schleife ab die über `InList` geht — da sind doch aber noch Sockets von denen gelesen werden kann. Ein Fehler würde ich sagen.

Was Du beim Client mit den ganzen Threads *und* `select()` anstellen möchtest ist mir nicht ganz klar. Die Kommunikation mit dem Server kann man entweder mit `select()` für beide Richtungen machen, oder man je einen Thread für das senden und einen zum Empfangen. *Dann* braucht man aber kein `select()`.

Wofür man eher einen eigenen Thread braucht ist um die Eingaben vom Benutzer parallel zur Kommunikation mit dem Server zu verarbeiten. Es sei denn man benutzt nicht `raw_input()`, sondern schreibt sich das Lesen der Benutzereingabe von `sys.stdin` selber und benutzt `select()`. Dann kann man `sys.stdin` mit in das `select()` aufnehmen.

Dein `Thread` funktioniert nicht weil Du `self.checkIncoming()` *aufrufst* und damit dessen Rückgabewert als `target` übergibst und nicht die Methode selbst. Aber auch das würde nicht viel nützen, denn dann würde der Code in der Methode *einmal* nebenläufig ausgeführt und danach ist der Thread abgelaufen und zuende.

Beim `send()` ist nicht garantiert dass das `socket.send()` auch tatsächlich alle Bytes in `Data` sendet. Da musst Du entweder eine Schleife schreiben solange bis das ganz sicher passiert ist (`send()` hat einen Rückgabewert!), oder `sendall()` verwenden.
Antworten