TCP-Client-Trennung erkennen

77

Angenommen, ich verwende einen einfachen Server und habe accept()eine Verbindung von einem Client hergestellt.

Was ist der beste Weg, um festzustellen, wann der Client die Verbindung getrennt hat? Normalerweise soll ein Client einen Schließbefehl senden, aber was ist, wenn er die Verbindung manuell trennt oder die Netzwerkverbindung insgesamt verliert? Wie kann der Server dies erkennen oder handhaben?

Zxaos
quelle
Schauen Sie hier (für die Worst-Case-Szenarien): tldp.org/HOWTO/TCP-Keepalive-HOWTO/overview.html ( Suche nach toten Kollegen)
Blauohr
4
Da es so viele falsche und irreführende Antworten gibt, ist hier die richtige: Befolgen Sie die Spezifikation für das Protokoll, das Sie zusätzlich zu TCP implementieren. Es sollte angegeben werden, ob dies durch Zeitüberschreitungen, Schreibfehler oder einen anderen Mechanismus erfolgt. Wenn Sie ein Protokoll entwerfen, stellen Sie sicher, dass Sie eine Methode zum Erkennen der Client-Trennung entwerfen, falls dies erforderlich ist.
David Schwartz

Antworten:

-2

select (mit eingestellter Lesemaske) kehrt mit dem signalisierten Handle zurück. Wenn Sie jedoch ioctl * verwenden, um die Anzahl der zu lesenden Bytes zu überprüfen, ist sie Null. Dies ist ein Zeichen dafür, dass die Steckdose getrennt wurde.

Dies ist eine großartige Diskussion über die verschiedenen Methoden zum Überprüfen, ob der Client die Verbindung getrennt hat: Stephen Cleary, Erkennung von halboffenen (unterbrochenen) Verbindungen .

* Für Windows verwenden Sie ioctlsocket.

sep
quelle
78
Dies ist absolut und eindeutig KEIN "Zeichen dafür, dass die Steckdose abgezogen wurde". Dies ist ein Zeichen dafür, dass im Socket-Empfangspuffer keine Daten vorhanden sind. Zeitraum. Auf einer Landmeile ist es nicht dasselbe. Der Artikel, den Sie zitieren, um Ihre Antwort zu unterstützen, erwähnt diese Technik nicht einmal.
Marquis von Lorne
3
@ MarkKCowan Sehr schwer zu glauben. Die Daten sollten nicht einmal in den Socket-Empfangspuffer gelangen, bis sie die Prüfsummenvalidierung bestanden haben. Haben Sie eine Quelle oder ein wiederholbares Experiment für Ihren Anspruch?
Marquis von Lorne
2
@ MarkKCowan Es ist nur in dem von Ihnen genannten Fehler dokumentiert. Es ist nicht in der Spezifikation der IOCTL dokumentiert. Es können jederzeit null Bytes gelesen werden, meistens, weil der Peer nichts gesendet hat. Dies ist keine korrekte Technik.
Marquis von Lorne
2
@EJP bedeutet nicht 0 Byte gelesen EOF (dh Peer hat die Verbindung geschlossen)? Wenn sich nichts auf dem Socket befindet und Sie versuchen zu lesen, wird ein EWOULDBLOCK / EAGAIN-Fehler angezeigt, kein 0-Byte-Lesevorgang.
ustulation
1
@ Matthieu: Kannst du mich bitte auf einen hinweisen? Ich glaube nicht, dass Sie jemals einen 0-Byte-Lesevorgang in TCP auf Anwendungsebene erhalten können (ja, Sie erhalten ihn möglicherweise für ACKs usw., aber das wird nicht an den Benutzer des Sockets weitergegeben), was keine EOF bedeutet.
ustulation
122

In TCP gibt es nur eine Möglichkeit, eine ordnungsgemäße Trennung zu erkennen, nämlich Null als Rückgabewert read()/recv()/recvXXX()beim Lesen zu erhalten.

Es gibt auch nur einen zuverlässigen Weg, um eine unterbrochene Verbindung zu erkennen: indem Sie darauf schreiben. Nach genügend Schreibvorgängen in eine unterbrochene Verbindung hat TCP genügend Wiederholungsversuche und Zeitüberschreitungen durchgeführt, um zu wissen, dass die Verbindung unterbrochen wurde, und führt schließlich dazu write()/send()/sendXXX(), dass -1 mit dem errno/WSAGetLastError()Wert ECONNRESET,oder in einigen Fällen "Verbindungszeitüberschreitung" zurückgegeben wird. Beachten Sie, dass sich letzteres von dem Verbindungszeitlimit unterscheidet, das in der Verbindungsphase auftreten kann.

Sie sollten auch ein angemessenes Lesezeitlimit festlegen und Verbindungen trennen, bei denen dies fehlschlägt.

Die Antwort hier über ioctl()und FIONREADist konkurrierender Unsinn. Sie erfahren lediglich, wie viele Bytes sich derzeit im Socket-Empfangspuffer befinden und ohne Blockierung gelesen werden können. Wenn ein Client Ihnen fünf Minuten lang nichts sendet, stellt dies keine Unterbrechung dar, führt jedoch FIONREADzu einer Null. Nicht dasselbe: nicht einmal in der Nähe.

Marquis von Lorne
quelle
2
@Jay Bei der Frage geht es darum, wie TCP-Verbindungsabbrüche erkannt werden, und nicht darum, was das Zurücksetzen der Verbindung verursacht. Es gibt viele Ursachen für das Zurücksetzen der Verbindung, und ich stimme nicht zu, dass eine davon einen "normalen Betrieb" darstellt. Es ist per Definition ein abnormaler Zustand.
Marquis von Lorne
2
@ user1055568 Ein einzelner Schreibvorgang wird normalerweise nur gepuffert und asynchron über das Netzwerk gesendet, es sei denn, er ist sehr groß. Sie müssen genügend Schreibvorgänge ausgeben, damit alle internen Timer und Wiederholungsversuche beim ursprünglichen Schreibvorgang erschöpft sind, damit ein Fehler erkannt wird.
Marquis von Lorne
2
Wenn die Anwendung keine Schreibvorgänge ausgibt, gibt es keine Garantie dafür, dass nach dem Verbindungsabbruch Schreibvorgänge ausgegeben wurden. Während ein nach dem Ausfall der Verbindung ausgegebener Schreibvorgang ausreicht, kann die Verbindung jederzeit fehlschlagen. Wenn Sie jemals auf unbestimmte Zeit mit dem Schreiben aufhören, können Sie nicht wissen, dass Sie nach dem Ausfall der Verbindung auch nur einen Schreibvorgang ausgegeben haben.
David Schwartz
3
@EJP Und ich habe mehrmals gesagt, dass wenn die App auf select / epoll / kevent wartet, um bereit zum Lesen zu sein, sie benachrichtigt wird, einen Lesevorgang durchzuführen, um den Fehler zu erkennen. Sie haben dies bestritten und wiederholt darauf bestanden, dass mehr Schreibvorgänge ausgeführt werden müssen. Sie haben nichts über Lesevorgänge gesagt, und bei epoll ist tatsächlich kein Lesen oder Schreiben erforderlich, da der epoll das Timeout direkt signalisieren kann. Wahrscheinlich auch kevent.
user1055568
2
@ user1055568 Wenn Sie nur Lesevorgänge ausführen, tun Sie nichts mit dem Netzwerk, sodass keine Fehlerbedingungen auftreten, es sei denn, der Peer ist verpflichtet, einen Reset durchzuführen. Wenn Sie schreiben, tun Sie etwas mit dem Netzwerk, sodass Sie garantiert auf eine Fehlerbedingung stoßen, falls es eine gibt.
Marquis von Lorne
13

Um dies etwas näher zu erläutern:

Wenn Sie einen Server ausführen, müssen Sie entweder TCP_KEEPALIVE verwenden, um die Clientverbindungen zu überwachen, oder etwas Ähnliches selbst tun oder Kenntnisse über die Daten / Protokolle haben, die Sie über die Verbindung ausführen.

Wenn die Verbindung unterbrochen wird (dh nicht ordnungsgemäß geschlossen wird), bemerkt der Server dies erst, wenn er versucht, etwas an den Client zu schreiben, was das Keepalive für Sie erreicht. Wenn Sie das Protokoll besser kennen, können Sie alternativ auch bei einem Inaktivitäts-Timeout die Verbindung trennen.

Peter Jeffery
quelle
Der Server sollte auch ein angemessenes Lesezeitlimit festlegen und Verbindungen trennen, bei denen dies fehlschlägt.
Marquis von Lorne
Verbindung trennen, die fehlschlägt? Was ist, wenn das Timeout der empfohlenen Standardeinstellung von 200 ms entspricht? Sollte es nicht zu einem bestimmten angemessenen Zeitlimit zurückgehen? Vielleicht verursacht das zu viel Kontextwechsel für Sie? Eine Verbindung immer noch zu trennen, wenn diese Timeoutso niedrig ist, ist kein guter Rat ...
Jay
Auf Winsock2 wird Keepalive alle 5 Sekunden abgefragt und ich habe einen blockierenden Sende- oder Empfangsanruf. Funktioniert Keepalive dann ordnungsgemäß? Was sind auch die Mindestgrenzen für das Keepalive-Timeout und das Intervall?
Anurag Daware
1
@EJP, welches Betriebssystem ist das? Das Standard-Lesezeitlimit für die meisten Betriebssysteme betrug 0,5 - 5 Sekunden, als ich das letzte Mal nachgesehen habe ... rfc für tcp sagt ausdrücklich, dass tcp einen Standardwert von 0,2 Sekunden hat ....
Jay
@ Jay Ich weiß nicht wovon du redest. Der Standardwert für SO_RCVTIMEO ist auf allen Betriebssystemen unendlich. Andernfalls würde jeder ständig Zeitüberschreitungen beim Lesen bekommen. Ihre Vorschläge von 200ms usw. sind absurd.
Marquis von Lorne
2

Wenn Sie überlappende (dh asynchrone) E / A mit Abschlussroutinen oder Abschlussports verwenden, werden Sie sofort benachrichtigt (vorausgesetzt, Sie haben einen ausstehenden Lesevorgang), wenn die Clientseite die Verbindung schließt.

Graeme Perrow
quelle
Nicht ganz. Sie werden sofort benachrichtigt, wenn Sie bis zum Ende des Streams gelesen haben. Es kann eine begrenzte Zeit dauern, wenn vor dem Abschluss signifikante Daten vom Kunden im Flug vorliegen.
Marquis von Lorne
0

TCP hat "offene" und "geschlossene" Prozeduren im Protokoll. Nach dem "Öffnen" wird eine Verbindung bis zum "Schließen" gehalten. Es gibt jedoch viele Dinge, die den Datenfluss abnormal stoppen können. Abgesehen davon hängen die Techniken zur Bestimmung, ob es möglich ist, eine Verbindung zu verwenden, stark von den Softwareschichten zwischen dem Protokoll und dem Anwendungsprogramm ab. Die oben genannten konzentrieren sich auf einen Programmierer, der versucht, einen Socket nicht-invasiv zu verwenden (0 Bytes lesen oder schreiben), sind möglicherweise am häufigsten. Einige Ebenen in Bibliotheken liefern die "Abfrage" für einen Programmierer. Zum Beispiel können Win32-Asych-Aufrufe (verzögert) einen Lesevorgang starten, der fehlerfrei und mit 0 Bytes zurückgegeben wird, um einen Socket zu signalisieren, der nicht mehr gelesen werden kann (vermutlich eine TCP-FIN-Prozedur). Andere Umgebungen verwenden möglicherweise "Ereignisse" wie in ihren Wickelschichten definiert. Es gibt keine einheitliche Antwort auf diese Frage. Der Mechanismus zum Erkennen, wann ein Socket nicht verwendet werden kann und geschlossen werden sollte, hängt von den in den Bibliotheken bereitgestellten Wrappern ab. Es ist auch erwähnenswert, dass Sockets selbst von Ebenen unterhalb einer Anwendungsbibliothek wiederverwendet werden können. Es ist daher ratsam, herauszufinden, wie Ihre Umgebung mit der Berkley Sockets-Oberfläche umgeht.

jlpayton
quelle
-1
"""
tcp_disconnect.py
Echo network data test program in python. This easily translates to C & Java.

A server program might want to confirm that a tcp client is still connected 
before it sends a data. That is, detect if its connected without reading from socket.
This will demonstrate how to detect a TCP client disconnect without reading data.

The method to do this:
1) select on socket as poll (no wait)
2) if no recv data waiting, then client still connected
3) if recv data waiting, the read one char using PEEK flag 
4) if PEEK data len=0, then client has disconnected, otherwise its connected.
Note, the peek flag will read data without removing it from tcp queue.

To see it in action: 0) run this program on one computer 1) from another computer, 
connect via telnet port 12345, 2) type a line of data 3) wait to see it echo, 
4) type another line, 5) disconnect quickly, 6) watch the program will detect the 
disconnect and exit.

John Masinter, 17-Dec-2008
"""

import socket
import time
import select

HOST = ''       # all local interfaces
PORT = 12345    # port to listen

# listen for new TCP connections
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((HOST, PORT))
s.listen(1)
# accept new conneciton
conn, addr = s.accept()
print 'Connected by', addr
# loop reading/echoing, until client disconnects
try:
    conn.send("Send me data, and I will echo it back after a short delay.\n")
    while 1:
        data = conn.recv(1024)                          # recv all data queued
        if not data: break                              # client disconnected
        time.sleep(3)                                   # simulate time consuming work
        # below will detect if client disconnects during sleep
        r, w, e = select.select([conn], [], [], 0)      # more data waiting?
        print "select: r=%s w=%s e=%s" % (r,w,e)        # debug output to command line
        if r:                                           # yes, data avail to read.
            t = conn.recv(1024, socket.MSG_PEEK)        # read without remove from queue
            print "peek: len=%d, data=%s" % (len(t),t)  # debug output
            if len(t)==0:                               # length of data peeked 0?
                print "Client disconnected."            # client disconnected
                break                                   # quit program
        conn.send("-->"+data)                           # echo only if still connected
finally:
    conn.close()

quelle
Die Überprüfung, ob der Socket bereit ist, aber keine Daten enthält, funktioniert für mein Projekt sehr gut. Es ist eine einfache Lösung
luc
3
@ Luc Es funktioniert überhaupt nicht. Es ist eine einfache, falsche, ungültige Lösung. Es ist ein Test für die Datenmenge, die ohne Blockierung gelesen werden kann, kein Test für eine Unterbrechung der Verbindung. Sie müssen lesen, um das zu testen. Wenn ein Client Ihnen fünf Minuten lang nichts sendet, ist FIONREAD Null, aber er ist möglicherweise noch verbunden.
Marquis von Lorne
1
Dies ist Python, aber der Tag sagt C ++
Greg Schmit
-1

In Python können Sie eine Try-Except-Anweisung wie folgt ausführen:

try:
  conn.send("{you can send anything to check connection}")
except BrokenPipeError:
  print("Client has Disconnected")

Dies funktioniert, da Python beim Schließen des Programms durch den Client / Server einen fehlerhaften Pip-Fehler an den Server oder Client zurückgibt, je nachdem, wer die Verbindung getrennt hat.

d19mc
quelle
-3

Es ist wirklich einfach: zuverlässig und nicht chaotisch:

        Try
            Clients.Client.Send(BufferByte)
        Catch verror As Exception
            BufferString = verror.ToString
        End Try
        If BufferString <> "" Then
            EventLog.Text &= "User disconnected: " + vbNewLine
            Clients.Close()
        End If
Marquis von Lorne
quelle
Es ist nicht zuverlässig. Aufgrund des Socket-Sendepuffers wird nicht zwischen ordnungsgemäßem und ungeordnetem Schließen unterschieden, und es funktioniert erst, wenn mindestens zwei Sends aufgetreten sind.
Marquis von Lorne
-3

Ich habe mit ein paar Lösungen gespielt, aber diese scheint am besten zu funktionieren, um die Trennung von Host und / oder Client unter Windows zu erkennen. Es ist für nicht blockierende Sockets vorgesehen und aus dem Beispiel von IBM abgeleitet .

char buf;
int length=recv(socket, &buf, 0, 0);
int nError=WSAGetLastError();
if(nError!=WSAEWOULDBLOCK&&nError!=0){
    return 0;
}   
if (nError==0){
    if (length==0) return 0;
}
Galeone
quelle
Ein recv () macht nichts auf dem Draht, kann also keine Erkennung von Kabelzügen usw. auslösen. Das kann nur ein send ().
Marquis von Lorne
-3

Der Rückgabewert des Empfangs ist -1, wenn die Verbindung unterbrochen wird, andernfalls hat er die Größe des Puffers.

void ReceiveStream(void *threadid)
{
    while(true)
    {
        while(ch==0)
        {
            char buffer[1024];
            int newData;
            newData = recv(thisSocket, buffer, sizeof(buffer), 0);
            if(newData>=0)
            {
                std::cout << buffer << std::endl;
            }
            else
            {
                std::cout << "Client disconnected" << std::endl;
                if (thisSocket)
                {
                    #ifdef WIN32
                        closesocket(thisSocket);
                        WSACleanup();
                    #endif
                    #ifdef LINUX
                        close(thisSocket);
                    #endif
                }
                break;
            }
        }
        ch = 1;
        StartSocket();
    }
}
Anand Paul
quelle
2
-1 wird nur zurückgegeben, wenn ein Fehler auftritt, nicht wenn eine Unterbrechung vorliegt. Ich habe unter Windows und Linux überprüft, dass recv einfach einen Puffer voller Nullen zurückgibt, wenn ein Peer die Verbindung nicht ordnungsgemäß trennt.
TekuConcept
@TekuConcept Falsch. Es wird -1 mit zurückgeben errno == ECONNRESETund es wird überhaupt nichts mit dem Puffer tun.
Marquis von Lorne
Laut Manpage haben Sie recht! Ich habe wohl die Zeile "Zusätzliche Fehler können generiert und von den zugrunde liegenden Protokollmodulen zurückgegeben werden"
übersehen