Timeout nach Datenübertragung

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
graceflotte
User
Beiträge: 25
Registriert: Samstag 8. März 2014, 12:17

Moin,

probiere mich aktuell an TCP-Verbindungen. Leider bekomme ich immer ein Timeout auf Client-Seite. Offenbar wartet der noch auf eine Antwort vom Server nur wie kann ich das beheben?
Danke schon mal. Verbesserungsvorschläge nehme ich ebenfalls gerne entgegen.

Klasse und Client:

Code: Alles auswählen

__author__ = 'lukas'
from socket import socket, AF_INET, SOCK_STREAM


class tcp_client():

    def __init__(self, target, port, timeout=5, buffer_size=1024):
        self.tcpdata = {'target': target, 'port': int(port), 'buffer_size': int(buffer_size), 'timeout': int(timeout)}
        self.sock = socket(AF_INET, SOCK_STREAM)
        self.sock.settimeout(self.tcpdata['timeout'])
        self.init_connection()

    def init_connection(self):
        self.sock.connect((self.tcpdata['target'], self.tcpdata['port']))

    def set_target(self, target):
        self.tcpdata['target'] = target
        self.init_connection()

    def set_port(self, port):
        self.tcpdata['port'] = port
        self.init_connection()

    def send(self, msg):
        try:
            if msg == 'request: database':
                self.sock.send(msg.encode())
                with open('./temp/dbdump', 'wb') as file:
                    data = self.sock.recv(self.tcpdata['buffer_size'])
                    while True:
                        file.write(data)
                        data = self.sock.recv(self.tcpdata['buffer_size'])
                return './temp/dbdump'
            else:
                self.sock.send(msg.encode())
                data = self.sock.recv(self.tcpdata['buffer_size'])
                return data
        except Exception as e:
            exit(e)

    def close(self):
        self.sock.close()


class tcp_server():

    def __init__(self, database, port, buffer_size=1024):
        self.tcpdata = {'database': database, 'port': port, 'buffer_size': buffer_size}

    def wait(self):
        try:
            sock = socket(AF_INET, SOCK_STREAM)
            sock.bind(('127.0.0.1', self.tcpdata['port']))
            sock.listen(5)
            conn, addr = sock.accept()  # accept incoming connections

            self.tcpdata['database'].load()  # reload database

            if addr[0] in [s.get_ip() for s in self.tcpdata['database'].get_server()]:  # ip in server-list
                while True:
                    data = conn.recv(self.tcpdata['buffer_size']).decode("utf-8")
                    print('incoming data: ' + data)
                    if not data:
                        print('--CLOSE--')
                        sock.close()
                        conn.close()
                        break
                    else:
                        msg = 'unknown message'

                        if data == 'request: database':
                            with open(self.tcpdata['database'].confpath, 'rb') as dbfile:
                                buf = dbfile.read(self.tcpdata['buffer_size'])
                                while buf:
                                    conn.send(buf)
                                    buf = dbfile.read(self.tcpdata['buffer_size'])


                        elif data.startswith('request'):
                            msg = 'reply: unknown request'

                        conn.send(msg.encode())
            else:
                conn.send(b'unauthorized connection')
                sock.close()
                conn.close()

        except Exception as e:
            try:
                sock.close()
                conn.close()
            except:
                pass
            exit(e)

if __name__ == '__main__':
    c = tcp_client('127.0.0.1', 7777)
    print(c.send('request: database'))
    c.close()
Server:

Code: Alles auswählen

import dbfunctions
import tcp_communication

db = dbfunctions.db('meshpi.json.conf')
srv = tcp_communication.tcp_server(db, 7777)

while 1:
	srv.wait()
Edit:
Die Datei wird komplett übertragen, danach erst bricht die Verbindung ab.
BlackJack

@graceflotte: Du verwendest kein Protokoll welches das Ende der Daten kennzeichnen würde und der Server schliesst die Verbindung nicht wenn der Client erlaubt ist, also woher soll dann der Client wissen wann er aufhören kann auf Daten zu warten?

Der Server ist fehlerhaft denn es ist nicht garantiert wie viele Daten bei *einem* `recv()`-Aufruf geliefert werden. Da muss nicht alles geliefert werden was mit einem `send()`-Aufruf beim Client gesendet wurde. Man müsste also auch in diese Richtung ein Protokoll verwenden oder die Verbindung in diese Richtung schliessen, damit der Server weiss ob/wann eine Anfrage vom Client komplett ist.

Warum steckt der Inhalt von `tcpdata` in einem Wörterbuch und nicht als Attribute auf dem jeweiligen Objekt?

Klassennamen werden konventionell nicht kleingeschrieben sondern in ”CamelCase”.
graceflotte
User
Beiträge: 25
Registriert: Samstag 8. März 2014, 12:17

BlackJack hat geschrieben: Der Server ist fehlerhaft denn es ist nicht garantiert wie viele Daten bei *einem* `recv()`-Aufruf geliefert werden. Da muss nicht alles geliefert werden was mit einem `send()`-Aufruf beim Client gesendet wurde. Man müsste also auch in diese Richtung ein Protokoll verwenden oder die Verbindung in diese Richtung schliessen, damit der Server weiss ob/wann eine Anfrage vom Client komplett ist.
Danke erstmal.
Wie würdest du das Problem denn lösen?
Ein ENDE-Paket schicken, damit der Client weiß, das nichts mehr kommt?

Und wieso geht das mit allen anderen TCP-Pakete, die ich losschicke? Müsste es da nicht genau so aussehen?
BlackJack

@graceflotte: Wie ich das lösen würde habe ich doch geschrieben: Protokoll entwerfen und implementieren oder die Senderichtung schliessen.

Das mit den Paketen scheint immer das grundlegende Missverständniss von TCP zu sein: Bei TCP gibt es keine Pakete, das ist ein Datenstrom. Wenn Du da ”Pakete” verschicken möchtest musst Du Dir ein Protokoll ausdenken damit der Empfänger die ”Paket”-Grenzen erkennen kann. Und dann Code schreiben der das auch tut und alle möglichen Fälle behandeln kann die auftreten können. Also das sowohl weniger als ein ganzes als auch mehr als ein Paket mit einem `recv()` gelesen werden können. Oder Bruchteile von zwei Aufeinanderfolgenden.

Und das muss grundsätzlich so gemacht werden. Auch wenn das bei Dir bisher immer ”funktioniert” hat. Das ”funktioniert” solange bis es das dann irgendwann nicht mehr tut.
graceflotte
User
Beiträge: 25
Registriert: Samstag 8. März 2014, 12:17

OK. Danke und entschuldige die späte Antwort.

Ich habe die Verbindung jetzt via conn.shutdown(socket.SHUT_RDWR) beendet und die komplette Klasse einmal
neu geschrieben. Vorab ein paar verschändnis Fragen:

Worin besteht genau der unterschied zwischen send() und sendall()? Aus der Doku werd ich nicht sonderlich schlau. sendall() gibt Fehler zurück, send() sendet einfach?

Den Datenstrom stell ich mir so vor:
Client sendet "Hey" an den Server. Sofern der Server kein recv() ausführt, bleiben die Daten im Port (?!) liegen.
Client sendet "123". Im Port am Server liegt nun "Hey123" als Datenstrom vor, welches sich der Server via recv() holen kann?

Im aktuellen Fall geht es um das senden einer Datei (vom Server), auf Anfrage vom Client.
Das eigentliche Problem: Voher weiß der Client, wann die Datei vollständig ist? In diversen Beispielen wird hierfür anfangs immer erst die kommende länge der Bytes übermittelt. Geht das wirklich nicht anders/schöner?
BlackJack

@graceflotte: Statt `shutdown()` mit beiden Richtungen hättest Du auch einfach `close()` aufrufen können. `shutdown()` macht nur Sinn wenn man die TCP-Verbindung geziehlt in eine Richtung abbauen möchte und nicht beide Richtungen wie bei `close()`.

`send()` sendet vielleicht nur einen Teil der Daten. Wieviel tatsächlich gesendet wurde kann man am Rückgabewert ablesen, das ist nämlich die Anzahl der tatsächlich gesendeten Bytes. Das steht aber so auch in der Dokumentation. `sendall()` sendet wie der Name schon sagt wirklich alle Daten. Eigentlich will man immer `sendall()` verwenden.

Wenn die eine Seite 'Hey' und dann '123' sendet kann es sein dass ein `recv()` 'Hey123' liefert. Es kann aber auch sein das es nur 'H' liefert. Oder 'He'. Oder 'Hey'. Oder 'Hey1'. Oder… — letztendlich muss ein korrektes Programm damit klar kommen können das `recv()` bei jedem Aufruf höchstens ein Byte liefert. Denn es gibt keine Garantien in welcher Stückelung diese Aufrufe die Daten liefern. Einzig die Reihenfolge ist sicher. Wo die Daten liegen? Es gibt beim Sender Puffer, auf dem Weg durchs Netz in Geräten wie Routern gibt es Puffer, beim Empfänger gibt es Puffer. Irgendwo da liegen die Daten die gesendet aber noch nicht empfangen wurden. Die Puffer sind in der Grösse beschränkt, dass heisst man kann nicht beliebig viel senden ohne das der Empfänger das auch ausliest. Wenn die diversen Puffer voll sind, blockiert die Übertragung an der jeweiligen Stelle.

Es gibt verschiedene Möglichkeiten Nachrichten in Datenströmen zu kodieren. Die Länge einmal am Anfang und dann die Daten senden. Oder ein Endkennzeichen was garantiert niemals *in* den Daten vorkommt. Wobei man da dafür *sorgen* kann, dass das niemals vorkommt in dem man sich einen Escape-Mechanismus implementiert. Oder man teilt die Daten auf mehrere Blöcke auf bei denen vor jedem Block die Länge gesendet wird. Eine Länge von 0 könnte dann bedeuten dass die Daten zuende sind. Da gibt's ziemlich viele Möglichkeiten. Wenn nur eine Datei über eine Verbindung übertragen werden soll wäre das einfachste aber wohl einfach am Ende die Verbindung zu schliessen als Zeichen dass da nichts mehr kommt.
graceflotte
User
Beiträge: 25
Registriert: Samstag 8. März 2014, 12:17

Danke für die ausführliche und schnelle Antwort ;)
Mit nem close() kann ich die Daten nicht übertragen, da später ein ganzes Verzeichnis übertragen werden soll.

Hier mal mein Code, der auch in meinen Tests wunderbar funktioniert hat. Hoffe, dass da keine Denkfehler drin sind.

Code: Alles auswählen

def receive_file(receiver):
    data = receiver.recv(32)
    if not data.startswith('%FINSYNC%'):
        print 'get file info', data

        s_data = data.split('%')
        expected_name_length = int(s_data[2])
        expected_file_length = int(s_data[3])

        data = receiver.recv(1024)
        print 'get file data...'

        file_path = data[:expected_name_length]
        print 'filepath', file_path
        file_data = data[expected_name_length:expected_file_length]

        received_file_length = len(file_data)

        while received_file_length < expected_file_length:
            data = receiver.recv(1024)
            file_data += data[:expected_file_length-received_file_length]
            received_file_length = len(file_data)

        with open(file_path, 'wb') as f:
            print 'write file', file_path
            f.write(file_data)

        return data[expected_file_length-received_file_length:]
    else:
        return data


def send_file(sender, file_path):
    print 'sending file', file_path
    with open(file_path, 'rb') as f:
        data = f.read()
    sender.sendall('%STRFTRF%' + str(len(file_path)).rjust(12, '0') + '%'+str(len(data)).rjust(9, '0') + '%')
    sender.sendall(file_path)
    sender.sendall(data)
    print 'fin sending file'
BlackJack

@graceflotte: Für die Länge des Dateinamens sind 12 Dezimalziffern vorgesehen, für die Länge der Datei aber nur 9? Interessante Verteilung. :-)

Das Empfangen geht so nicht. Ein ``recv(32)`` liefert nicht garantiert 32 Bytes sondern nur eines ist tatsächlich garantiert. Wenn man 32 Bytes lesen will muss man das in einer Schleife tun bis man ganz bestimmt 32 Bytes beisammen hat. Das selbe gilt dann für das Empfangen des Dateinamens, da kann man auch nicht sicher sein dass der komplett in dem ``recv(1024)``-Aufruf geliefert wird. Es würde sich anbieten eine `receive_n_bytes()`-Funktion zu schreiben der man die gewünschte Anzahl von Bytes übergeben kann die man gelesen haben möchte. Und die Datei sollte man beim Empfangen vielleicht nicht im Speicher sammeln, und schon gar nicht mit ``+=``, sondern gleich in eine Datei ”streamen”.
graceflotte
User
Beiträge: 25
Registriert: Samstag 8. März 2014, 12:17

Die Zwei stellen werden bei mir unterschiedlich genutzt. Beim Syncrequest wird (hier nicht zu sehen) ein 12 stelliger Zeitstempel mitgeschickt, daher die 12 Stellen.

TCP is doof :D

Also etwa sowas hier? Hier gleich die Frage, muss es immer eine 2er Potenz sein, die ich von recv() bekommen möchte?

Code: Alles auswählen

def receive_n_bytes(receiver, num_bytes):
    data = receiver.recv(num_bytes)
    while len(data) < num_bytes:
        data += receiver.recv(num_bytes-len(data))
    return data
BlackJack

@graceflotte: Wenn TCP doof ist, dann ist ein ziemlich grosser Teil vom Internet doof. ;-)

Nein das muss natürlich keine 2er-Potenz sein. Wie kommst Du da drauf?
graceflotte
User
Beiträge: 25
Registriert: Samstag 8. März 2014, 12:17

Socket-Doku
socket.recv(bufsize[, flags])
[...]
Note: For best match with hardware and network realities, the value of bufsize should be a relatively small power of 2, for example, 4096.
Evtl. versteh ich den Text auch falsch und/oder meine Englisch-Kentnisse lassen mich im Stich.

Ggf. war meine Frage auch falsch gestellt, da ich gefragt hatte, ob es sein "muss".
Anders gefragt, wie sinnvoll is es eine 2er-Potenz zu nehmen?

Das Script bzgl. receive_n_bytes() geht soweit klar?
BlackJack

@graceflotte: Die Puffer sind halt auf solche grössen ausgelegt, aber wenn man ein Paket empfangen will was meinetwegen 42 Bytes gross ist, dann kann man natürlich auch 42 als Argument übergeben.

Bei der Funktion würde ich entweder die Grösse auf etwas sinnvolles begrenzen oder für `data` eine Liste verwenden. Wiederholtes ``+=`` ist bei Zeichenketten keine gute Idee. Und ich würde `data` vor der Schleife einfach leer initialisieren damit nur ein `recv()`-Aufruf in der Funktion steht.
graceflotte
User
Beiträge: 25
Registriert: Samstag 8. März 2014, 12:17

Wie genau meinst du das mit der Liste?

Etwa list.append(conn.recv(bytes)) und am ende dann ein ''.join(list) ?
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Für sowas wie in Zeile 37 haben die Python-Macher extra format erfunden :twisted:

Code: Alles auswählen

sender.sendall('%STRFTRF%{0:012d}%{1:09d}%'.format(len(file_path), len(data)))
Die Ausrede mit den 12 Bytes verstehe ich nicht. Ist die Gegenseite in Fortran geschrieben?
Wenn Du die Datei munter in 1024-Byte Päckchen liest, wirst Du zum Schluß auch ein bißchen des nächsten Headers mitlesen.
BlackJack

@graceflotte: Ja so meinte ich das mit der Liste. Wie gesagt, wenn die Anzahl der Bytes auch gross sein kann.
graceflotte
User
Beiträge: 25
Registriert: Samstag 8. März 2014, 12:17

Ich werde das erstmal alles soweit umsetzten. Danke ;)
Antworten