Socket + Makefile

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
TagiruAkashi
User
Beiträge: 12
Registriert: Dienstag 31. Januar 2012, 18:58

Hi

Ich versuche mir gerade einen kleinen Client + Server zu schreiben zwischen dennen ich ein Objekt einer selbstgeschriebenen Klasse hin und herschiebe (ersteinmal ohne tieferen Sinn). Das funktioniert soweit auch schon ganz gut, allerdings sobald ich viele Anfragen an den Client sende, bleiben manche davon unbeantwortet...ohne Fehlermeldung bzw. Exception.

Anbei mal der Quellcode - hoffe mir kann jemand helfen....vielleicht habe ich auch einfach etwas grundlegendes Falsch gemacht - habe bisher in Python noch nicht mit Sockets programmiert.

Der Server schickt 5 Sachen an den Client...davon kommen aber meistens nur 1 und 2 zurück - manchmal auch 1,2 und 4

Gruß Tagiru

# Client

Code: Alles auswählen

class Worker(Thread):

    def __init__ (self):
        # Threading
        Thread.__init__(self)
        self.__event = Event()
                
        # Connect to Server (Sending)
        self.__scon = socket.socket(socket.AF_INET, socket.SOCK_STREAM)        
        self.__scon.connect(('127.0.0.1', 55555))
        self.__scon.send('WILL SEND')
        
        # Connect to Server (Receiving)
        self.__rcon = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.__rcon.connect(('127.0.0.1', 55555))
        self.__rcon.send('WILL RECV') #

        # Something
        self.__jobs = list()
        self.__runstate = True
        
        # Start Job Listener
        listener = Thread(target=self.__receive)
        listener.start()
        

    def run(self):
        time.sleep(5)
        while self.__runstate:
            if len(self.__jobs) == 0:
                self.__event.wait()
                continue
        
            self.__send({'type' : self.__jobs[0]['type'], self.__jobs[0]['obj']})            
            self.__jobs.remove(self.__jobs[0])
        
    
    def __send(self, tmdb_obj):
        obj = self.__scon.makefile('wb')
        pickle.dump(tmdb_obj, obj, pickle.HIGHEST_PROTOCOL)
        obj.close()


    def __receive(self):
        while self.__runstate:
            obj = self.__rcon.makefile('rb')
            data = pickle.load(obj)
            obj.close()
            self.__add_job(data)
    
    
    def __add_job(self, job):        
        print "JOB: " + str(job['type'])        
        self.__jobs.append(job)

        self.__event.set()
        self.__event.clear()

w = Worker()
w.start()

# Server (leider momentan etwas unstrukturiert, da Teile aus einem Tut stammen + ich noch am probieren bin)

Code: Alles auswählen

soc = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
soc.bind(('127.0.0.1',55555))
soc.listen(5)

class ABC(threading.Thread):
    def __init__(self,c):
        threading.Thread.__init__(self)
        self.conn = c
        self.stopIt=False


    def mrecv(self):
        f = self.conn.makefile('rb')
        data = pickle.load(f)
        f.close()
        return data
        

    def run(self):
        while not self.stopIt:
            msg = self.mrecv()
            print 'recieved-> ',msg

def setConn(con1,con2):
    dict={}
    state = con1.recv(9)
    con2.recv(9)
    if state =='WILL RECV':
        dict['send'] = con1 # server will send data to reciever
        dict['recv'] = con2
    else:
        dict['recv'] = con1 # server will recieve data from sender
        dict['send'] = con2
    return dict

def msend(conn,msg):        
    test_entry = obj()
    
    tmdb_obj = {'type' : 'tmdb_country', 'obj' : test_entry}    
    obj = conn.makefile('wb')
    pickle.dump(tmdb_obj, obj, pickle.HIGHEST_PROTOCOL)
    obj.close()
    
    tmdb_obj2 = {'type' : 'tmdb_release', 'obj' : test_entry}    
    obj2 = conn.makefile('wb')
    pickle.dump(tmdb_obj2, obj2, pickle.HIGHEST_PROTOCOL)
    obj2.close()

    tmdb_obj3 = {'type' : 'tmdb_alternatetitle', 'obj' : test_entry}    
    obj3 = conn.makefile('wb')
    pickle.dump(tmdb_obj3, obj3, pickle.HIGHEST_PROTOCOL)
    obj3.close()

    tmdb_obj4 = {'type' : 'tmdb_language', 'obj' : test_entry}    
    obj4 = conn.makefile('wb')
    pickle.dump(tmdb_obj4, obj4, pickle.HIGHEST_PROTOCOL)
    obj4.close()

    tmdb_obj5 = {'type' : 'tmdb_studio', 'obj' : test_entry}    
    obj5 = conn.makefile('wb')
    pickle.dump(tmdb_obj5, obj5, pickle.HIGHEST_PROTOCOL)
    obj4.close()


(c1,a1) = soc.accept()
(c2,a2) = soc.accept()
dict = setConn(c1,c2)
thr = CThread(dict['recv'])
thr.start()
try:
    while 1:
        msend(dict['send'], raw_input()) # Damit das Teil nicht beim Testen in der Dauerschleife rennt wartet es auf irgendeine Eingabe - raw_input()
except:
    print 'closing'
thr.stopIt=True
thr.conn.close()
soc.close()
deets

Bitte gewoehn dir mal die unnoetigen __ vor allem moeglichen ab. Dieses Feature ist nicht fuer "private"-Deklarationen gemacht, sondern um Namenskollisionen zu vermeiden. Und so viel Privatheit ist eh komisch - wenn ich will, komm ich eh ran, als Nutzer deiner Library.

Zu deinem Problem: ich vermute mal, dass du darueber stolperst das sockets keine Messages kennen. Sondern nur Byte-Stroeme abstrahieren. Das heisst, dass man ein Protokoll definieren muss (wie zB HTTP das macht), um die Laenge einer versandten Nachricht zu kommunizieren.

Oft genug geht's erstmal scheinbar ohne, weil eben nur ein Paket verschickt wird mit der Anzahl der richtigen Bytes. Aber wenn dann mal mehr Daten kommen, dann werden die eben nur teilweise oder ueberlappend gelesen - und dann kracht's.

Du solltest zB ein Anfangs-token definieren, mit einer Laengenangabe in Bytes danach gefolgt zB von einem newline (das ist das, was der HTTP header auch macht), und dann eben Laenge-an-Daten schicken. Auf der client-Seite wartest du auf den Header, dekodierst die zu lesende Byte-Zahl, und liest so lange, bis du genug Bytes hast. UU mit timeout, aber das ist Sahnehaube fuer spaeter.
TagiruAkashi
User
Beiträge: 12
Registriert: Dienstag 31. Januar 2012, 18:58

Bitte gewoehn dir mal die unnoetigen __ vor allem moeglichen ab. Dieses Feature ist nicht fuer "private"-Deklarationen gemacht, sondern um Namenskollisionen zu vermeiden. Und so viel Privatheit ist eh komisch - wenn ich will, komm ich eh ran, als Nutzer deiner Library.
Ok, da war ich wohl falsch Informiert - das man als Nutzer der Library trotzdem dran kommt ist mir klar - ich dachte aber trotzdem das man __ verwendet um (anderen) zu zeigen das die entsprechende Methode/Variable private ist und damit nicht von außen verwendet werden sollte, da es gegebenenfalls zu Problemen kommen könnte.
Zu deinem Problem: ich vermute mal, dass du darueber stolperst das sockets keine Messages kennen. Sondern nur Byte-Stroeme abstrahieren. Das heisst, dass man ein Protokoll definieren muss (wie zB HTTP das macht), um die Laenge einer versandten Nachricht zu kommunizieren.
(...)
Ok verstehe - soetwas hatte ich mir schon Gedacht. Also sollte ich vorher der Gegenstelle immer erst mitteilen wie lang das "Paket" ist das als nächstes kommt. Aber mir ist gerade nicht klar wie ich das am geschicktesten Anstelle - soweit ich weiß serialisiert pickle doch mein Objekt und gibt es gleich an den Socket weiter... um die korrekte Bytelänge zu bekommen müsste ich es also erst sealisieren, dann die Länge von dem Resultat schicken und anschließend das Teil selber - wäre das so richtig?

Danke soweit ;)

Gruß Tagiru
deets

Privatheit bzw. Implementierungsdetails kann man kennzeichnen, dann aber mit einem einzelnen Unterstrich. Und generell neigen Leute die aus Bondage-Sprachen kommen (wie Java/C++) dazu, davon zu viel zu nuzten.

Was das Problem angeht: ja, du musst halt zB in einen StringIO-Objekt pickeln. Alternativ kannst du auch machen, was MIME macht: einen Header mit einem garantiert in den Daten nicht vorkommenden GUID schreiben, dann die Daten, und dann wieder die GUID. Damit liest der Client erst die GUID, und danach wieder bis zum 2ten auftreten. Das ist bei deinen Datengroessen Geschmackssache, bei Gigabytes von Daten uU etwas praktikabler.
TagiruAkashi
User
Beiträge: 12
Registriert: Dienstag 31. Januar 2012, 18:58

ok danke - dann werde ich das mal probieren, werde aber vor Donnerstag leider nicht dazu kommen - melde mich dann hier auf jeden Fall nochmal ;)

Gruß Tagiru
TagiruAkashi
User
Beiträge: 12
Registriert: Dienstag 31. Januar 2012, 18:58

Hi

Hab das jetzt einmal auf eine simple Art umgesetzt und es scheint zu funktionieren - Danke nochmal ;)

Gruß Tagiru

P.s.: Das mit den __ muss ich noch machen :)

Senden

Code: Alles auswählen

    tmdb_obj = {'type' : 'tmdb_country', 'obj' : test_entry}
    msg = pickle.dumps(tmdb_obj, pickle.HIGHEST_PROTOCOL)
    conn.send(str(len(msg)))
    if conn.recv(2) == 'OK':
        conn.send(msg)
Empfangen

Code: Alles auswählen

        while self.__runstate:
            # length
            data = self.__rcon.recv(4)
            self.__rcon.send('OK')
            
            # obj
            obj = self.__rcon.recv(int(data))
            data = pickle.loads(obj)
            
            # store
            self.__add_job(data)
deets

Ist immer noch zu kompliziert finde ich - wozu der handshake? Und du bist auch nicht wirklich robust as wiederaufsetzen angeht: dadurch, dass du immer zwei Bytes einliest um "OK" zu bekommen - was passiert denn, wenn du *ein* Byte verpasst, und dann immer 'KO', 'KO' liest? Zwar ist TCP da recht sicher weil es starke garantien gibt, aber ich wuerde immer versuchen zB mittels newlines zeilen zu schicken, die du dann als ganzes analysierst. Also lesen bis zur naechsten newline, und dann schauen, ob's ein valides Kommando mit "OK<bytezahl>" ist. So macht HTTP das auch.
TagiruAkashi
User
Beiträge: 12
Registriert: Dienstag 31. Januar 2012, 18:58

Ich werde mich noch einmal dran setzen und es mir noch einmal in Ruhe anschauen - danke für dein Feedback - werde mich sicher die Tage noch einmal melden.
lunar

@deets: Bei einer TCP-Verbindung kann man keine Bytes „verpassen“. ".recv(2)" gibt nach dem Öffnen der Verbindung immer die ersten beiden Bytes zurück, die die Gegenstelle gesendet hat. Das System reicht immer nur vollständigen Payload an den Prozess weiter.
BlackJack

@lunar: Es kann nicht passieren das `recv(2)` nur *ein* Byte liefert weil die zwei Bytes gerade auf einer Paketgrenze waren und das zweite der beiden Pakete noch nicht angekommen ist? Soweit ich das verstanden habe gibt `recv(x)` nur die Garantie das höchstens x Bytes zurückgegeben werden, aber nicht dass es *genau* x sind. Auch wenn da noch Daten von der Gegenseite kommen.
lunar

@BlackJack: Stimmt, daran habe ich nicht gedacht. Man muss ".recv(2, socket.MSG_WAITALL)" verwenden, um sicher zu gehen, dass man auch tatsächlich zwei Bytes erhält.

Ich hatte deets' Beitrag so verstanden, dass Bytes im Netzwerk verloren gegangene Pakete auf Python-Seite nicht ankommen würden, sprich das ".recv()" beispielsweise das erste und das dritte Byte zurückgeben könne, weil das zweite Byte verloren gegangen ist. Das kann nicht passieren.
TagiruAkashi
User
Beiträge: 12
Registriert: Dienstag 31. Januar 2012, 18:58

Hi

Kann es sein das es MSG_WAITALL nur unter Linux gibt?

Gruß Tagiru
lunar

@TagiruAkashi Diese Option ist POSIX-Standard, mithin sollte sie auf jedem Unix-System verfügbar sein.
TagiruAkashi
User
Beiträge: 12
Registriert: Dienstag 31. Januar 2012, 18:58

ok danke ;) - Ich hab meine Frage wohl schlecht formuliert - kann es sein das es diese Funktion nicht unter Windows gibt? - Ich arbeite mit beiden Systemen.

Gruß Tagiru
deets

Wenn sie unter Windows nicht verfuegbar ist, dann spielt das auch keine Rolle - denn du liest halt Daten ein, bufferst sie, und schaust in dem buffer nach, ob deine start-bedingung erfuellt ist.
Antworten