mikrocontroller.net

Forum: PC-Programmierung TIME_WAIT state bei TCP / reconnect ohne timeout


Autor: ack (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo,

habe ein kleines Python Programm, das eine TCP Verbindung als Client 
aufbaut. Als Netzwerklib wird QtNetwork aus PyQt4 verwendet.

Funktioniert auch alles soweit.
Nun möchte ich die Verbindung neu aufbauen, wenn ein Verbindungsabbruch 
vorkommt, verursacht durch zB kurzzeitigen Stromausfall der Gegenseite.

Das Programm erkennt den Verbindungsabbruch und versucht direkt, eine 
neue Verbindung aufzubauen - jedoch versandet der erneute Versuch, wenn 
der Verbindungsaufbau unmittelbar nach dem Abbruch geschieht und die 
Gegenseite noch nicht wieder erreichbar ist - allerdings ohne Timeout 
etc.

Hier der relevante Ausschnitt:
import sys
from PyQt4 import QtCore, QtGui, QtNetwork

class Client():
    def __init__(self):
        self.tcpSocket = QtNetwork.QTcpSocket()
        self.tcpSocket.error.connect(self.displayError)
   
    def reconnect(self):
        self.tcpSocket.abort()
        self.tcpSocket.connectToHost("127.0.0.1", 8080)

    def displayError(self, socketError):
        print("The following error occurred: %s." % self.tcpSocket.errorString())
        self.reconnect()

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    c = Client()
    c.reconnect()
    sys.exit(app.exec_())

Als Gegenseite fungiert Netcat, das auf Port 8080 hört.
Beendet man Netcat nach dem ersten erfolgreichen Verbindungsaufbau 
versucht das Programm erneut, eine Verbindung aufzubauen, nachdem 
displayError aufgerufen wurde ("The remote host closed the connection.")
Statt einem erneuten Error ("Connection refused.") passiert aber leider 
gar nichts.
Netstat zeigt dann an:
tcp        0      0 127.0.0.1:8080          127.0.0.1:42268    TIME_WAIT -

Was bedeutet dieser TIME_WAIT state? Ich nehme an, dass dieser die 
Ursache für das Versanden des erneuten Verbindungsaufbaus ist, also dass 
die Pakete weder an einem listening Socket ankommen, noch abgelehnt 
werden, sondern einfach leise ignoriert werden.
Das scheint mir auch ein generelles Linux/TCP Konzept zu sein und nichts 
mit Python zu tun zu haben.

Was kann man tun, um sauber zu erkennen, ob ein Verbindungsaufbau in 
einem solchen Fall fehlschlägt?
Vielen Dank schonmal

Autor: Daniel Abrecht (daniel-a)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
ack schrieb:
> Was bedeutet dieser TIME_WAIT state? Ich nehme an, dass dieser die
> Ursache für das Versanden des erneuten Verbindungsaufbaus ist, also dass
> die Pakete weder an einem listening Socket ankommen, noch abgelehnt
> werden, sondern einfach leise ignoriert werden.
> Das scheint mir auch ein generelles Linux/TCP Konzept zu sein und nichts
> mit Python zu tun zu haben.

Mit linux hat dies nichts zutun. TCP verbindungen können sich in 
verschiedenen Zuständen befinden, im rfc 793 auf Seite 23 gibt's dazu 
ein schönes diagramm: https://tools.ietf.org/html/rfc793#page-23

Grundsätzlich müssen bem Verbindungsaufbau zuerst beide 
Verbindungspartner dem anderen ein SYN flag senden, und dessen Empfang 
mit einem ACK flag bestätigen. Es spielt keine rolle, wer damit anfängt 
oder ob beide gleichzeitig ein SYN senden, es müssen einfach nur beide 
in einem Zustand sein in welchem sie ein SYN erwarten, und die quell und 
ziel ports, etc. der Packete müssen stimmen. Es ist jedoch üblich das 
einer im listening state auf Verbindungen wartet und der andere das 
erste SYN sendet und dann im syn sent state auf ein SYN und ein ACK 
wartet. Danach ist die verbindung aufgebaut und im ESTABLISHED state. 
Falls jedoch ein Verbindungspartner kein SYN erwartet, aber eines 
erhält, wird ein RST gesendet um den verbindungsaufbau abzulehnen. Wenn 
man eine Verbindung abbricht wird ein RST gesendet, das sollte man 
vermeiden weil dann unklar ist ob alle gesendeten Daten auch angekommen 
sind. Beim schliessen der Verbindung wird ein FIN gesendet. Beide 
Verbindungspartner müssen ein FIN senden und dessen empfang mit einem 
ACK bestätigen, die Verbindung zu schliessen. Wenn man ein FIN sendet, 
muss der Verbindungspartner das nicht unbedingt auch tun, man sagt damit 
nur, dass man selbst keine Daten mehr senden wird. Der 
Verbindungspartner kann dann weiterhin Daten senden.

Der TIME_WAIT state existiert, damit zwischen zwei Rechnern in einem 
gewissen Zeitraum nach dem schliessen einer Verbindung nicht erneut eine 
Verbindung mit dem selben source und destination port, etc. geöffnet 
wird, und verspätete Packete kein RST auslösen. Dies verhindert auch, 
dass verspätete Packete der alten Verbindung zu problemen führen. Dies 
stellt hier jedoch definitiv nicht das Problem dar.

Wenn du mit netcat auf eine Verbindung wartest, dann ist die Verbindung 
im LISTENING state. Dabei wird auf ein SYN gewartet, das an den 
richtigen destination port geht, und von einem beliebigen source port 
kommt.
Wenn dein Programm die Verbindung initiiert, sendet es ein SYN und ist 
dann im syn sent state. Jeder TCP Stack wird dabei den angegebenen 
destination port verwenden, aber sofern man den Source port nicht 
explizit festlegt diesen zufällig wählen. Deshalb würde beim erneuten 
Verbindungsaufbau ein anderer source port gewählt, womit die Kombination 
aus source und destination port nicht der Verbindung im TIME_WAIT 
zustand von vorher zugeordnet werden kann, womit dies nicht das Problem 
sein kann.

Man könnte jetzt mit Wireshark nachsehen, was los ist, aber eigentlich 
macht das ganze nur sinn, wenn nicht versucht wird eine neue Verbindung 
aufzubauen.

Wenn ich die API geschrieben hätte, hätte ich nicht vorgesehen, dass 
jemand versucht eine neue Verbindung mit einem alten Verbindungsobjekt 
herzustellen. Das ist nur eine Vermutung, aber versuche mal ein neues zu 
erstellen. Ungetestet:
import sys
from PyQt4 import QtCore, QtGui, QtNetwork

class Client():
    def __init__(self):
        self.tcpSocket = None

    def reconnect(self):
        if self.tcpSocket is not None:
            self.tcpSocket.abort()
        self.connect()

    def connect(self):
        self.tcpSocket = QtNetwork.QTcpSocket()
        self.tcpSocket.error.connect(self.displayError)
        self.tcpSocket.connectToHost("127.0.0.1", 8080)

    def displayError(self, socketError):
        print("The following error occurred: %s." % self.tcpSocket.errorString())
        self.reconnect()

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    c = Client()
    c.connect()
    sys.exit(app.exec_())

Autor: ack (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Vielen Dank für die ausführliche Antwort.
Wenn ich deinen Code ausführe (mit listendem nc im Hintergrund) 
verbindet
sich das Programm wie erwartet mit nc - sobald man dann nc beendet, 
kommt es zu einem Segfault.

Man kann das Beispiel soweit reduzieren, um den Segfault auszulösen:
import sys
from PyQt4 import QtCore, QtGui, QtNetwork

tcpSocket = None

def displayError(socketError):
    global tcpSocket
    tcpSocket.abort()
    tcpSocket = QtNetwork.QTcpSocket()
    tcpSocket.connectToHost("127.0.0.1", 8080)


app = QtGui.QApplication(sys.argv)
tcpSocket = QtNetwork.QTcpSocket()
tcpSocket.error.connect(displayError)
tcpSocket.connectToHost("127.0.0.1", 8080)
sys.exit(app.exec_())

Ist das ein Bug in der Library?

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.