GNU/Linux >> LINUX-Kenntnisse >  >> Linux

Socket-Aufhängung ohne Senden oder Empfangen erkennen?

Ich hatte ein wiederkehrendes Problem bei der Kommunikation mit Geräten, die separate TCP-Verbindungen zum Senden und Empfangen hatten. Das grundlegende Problem besteht darin, dass der TCP-Stack Ihnen im Allgemeinen nicht mitteilt, dass ein Socket geschlossen ist, wenn Sie nur versuchen zu lesen. Sie müssen versuchen, zu schreiben, um zu erfahren, dass das andere Ende der Verbindung unterbrochen wurde. Teilweise wurde TCP genau so entworfen (Lesen ist passiv).

Ich vermute, Blairs Antwort funktioniert in den Fällen, in denen der Socket am anderen Ende ordnungsgemäß heruntergefahren wurde (dh sie haben die richtigen Trennungsnachrichten gesendet), aber nicht in dem Fall, in dem das andere Ende unhöflicherweise einfach aufgehört hat zuzuhören. P>

Gibt es am Anfang Ihrer Nachricht einen Header mit ziemlich festem Format, den Sie mit dem Senden beginnen können, bevor die gesamte Antwort fertig ist? z.B. ein XML-Dokumenttyp? Sind Sie auch in der Lage, einige zusätzliche Leerzeichen an einigen Stellen in der Nachricht zu senden - nur einige Nulldaten, die Sie ausgeben können, um sicherzustellen, dass der Socket noch offen ist?


Das ausgewählte Modul enthält, was Sie brauchen. Wenn Sie nur Linux-Unterstützung benötigen und einen ausreichend aktuellen Kernel haben, select.epoll() sollte Ihnen die Informationen geben, die Sie benötigen. Die meisten Unix-Systeme unterstützen select.poll() .

Wenn Sie plattformübergreifende Unterstützung benötigen, verwenden Sie standardmäßig select.select() um zu überprüfen, ob der Socket so markiert ist, dass Daten zum Lesen verfügbar sind. Wenn ja, aber recv() gibt Null Bytes zurück, das andere Ende hat aufgelegt.

Ich fand Beejs Leitfaden zur Netzwerkprogrammierung immer gut (beachten Sie, dass er für C geschrieben wurde, aber allgemein auf Standard-Socket-Operationen anwendbar ist), während das How-To zur Socket-Programmierung einen anständigen Überblick über Python bietet.

Bearbeiten :Das Folgende ist ein Beispiel dafür, wie ein einfacher Server so geschrieben werden könnte, dass er eingehende Befehle in eine Warteschlange stellt, aber die Verarbeitung beendet, sobald er feststellt, dass die Verbindung am entfernten Ende geschlossen wurde.

import select
import socket
import time

# Create the server.
serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serversocket.bind((socket.gethostname(), 7557))
serversocket.listen(1)

# Wait for an incoming connection.
clientsocket, address = serversocket.accept()
print 'Connection from', address[0]

# Control variables.
queue = []
cancelled = False

while True:
    # If nothing queued, wait for incoming request.
    if not queue:
        queue.append(clientsocket.recv(1024))

    # Receive data of length zero ==> connection closed.
    if len(queue[0]) == 0:
        break

    # Get the next request and remove the trailing newline.
    request = queue.pop(0)[:-1]
    print 'Starting request', request

    # Main processing loop.
    for i in xrange(15):
        # Do some of the processing.
        time.sleep(1.0)

        # See if the socket is marked as having data ready.
        r, w, e = select.select((clientsocket,), (), (), 0)
        if r:
            data = clientsocket.recv(1024)

            # Length of zero ==> connection closed.
            if len(data) == 0:
                cancelled = True
                break

            # Add this request to the queue.
            queue.append(data)
            print 'Queueing request', data[:-1]

    # Request was cancelled.
    if cancelled:
        print 'Request cancelled.'
        break

    # Done with this request.
    print 'Request finished.'

# If we got here, the connection was closed.
print 'Connection closed.'
serversocket.close()

Um es zu verwenden, führen Sie das Skript aus und in einem anderen Terminal telnet zu localhost, Port 7557. Die Ausgabe eines von mir durchgeführten Beispiellaufs, bei dem drei Anfragen in die Warteschlange gestellt wurden, aber die Verbindung während der Verarbeitung der dritten geschlossen wurde:

Connection from 127.0.0.1
Starting request 1
Queueing request 2
Queueing request 3
Request finished.
Starting request 2
Request finished.
Starting request 3
Request cancelled.
Connection closed.

epoll-Alternative

Noch eine Bearbeitung: Ich habe ein weiteres Beispiel mit select.epoll ausgearbeitet Ereignisse zu überwachen. Ich glaube nicht, dass es viel mehr bietet als das ursprüngliche Beispiel, da ich keine Möglichkeit sehe, ein Ereignis zu empfangen, wenn das entfernte Ende auflegt. Sie müssen immer noch das Data Received-Ereignis überwachen und nach Nachrichten der Länge Null suchen (wiederum würde ich gerne bewiesen werden, dass ich mich bei dieser Aussage geirrt habe).

import select
import socket
import time

port = 7557

# Create the server.
serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serversocket.bind((socket.gethostname(), port))
serversocket.listen(1)
serverfd = serversocket.fileno()
print "Listening on", socket.gethostname(), "port", port

# Make the socket non-blocking.
serversocket.setblocking(0)

# Initialise the list of clients.
clients = {}

# Create an epoll object and register our interest in read events on the server
# socket.
ep = select.epoll()
ep.register(serverfd, select.EPOLLIN)

while True:
    # Check for events.
    events = ep.poll(0)
    for fd, event in events:
        # New connection to server.
        if fd == serverfd and event & select.EPOLLIN:
            # Accept the connection.
            connection, address = serversocket.accept()
            connection.setblocking(0)

            # We want input notifications.
            ep.register(connection.fileno(), select.EPOLLIN)

            # Store some information about this client.
            clients[connection.fileno()] = {
                'delay': 0.0,
                'input': "",
                'response': "",
                'connection': connection,
                'address': address,
            }

            # Done.
            print "Accepted connection from", address

        # A socket was closed on our end.
        elif event & select.EPOLLHUP:
            print "Closed connection to", clients[fd]['address']
            ep.unregister(fd)
            del clients[fd]

        # Error on a connection.
        elif event & select.EPOLLERR:
            print "Error on connection to", clients[fd]['address']
            ep.modify(fd, 0)
            clients[fd]['connection'].shutdown(socket.SHUT_RDWR)

        # Incoming data.
        elif event & select.EPOLLIN:
            print "Incoming data from", clients[fd]['address']
            data = clients[fd]['connection'].recv(1024)

            # Zero length = remote closure.
            if not data:
                print "Remote close on ", clients[fd]['address']
                ep.modify(fd, 0)
                clients[fd]['connection'].shutdown(socket.SHUT_RDWR)

            # Store the input.
            else:
                print data
                clients[fd]['input'] += data

        # Run when the client is ready to accept some output. The processing
        # loop registers for this event when the response is complete.
        elif event & select.EPOLLOUT:
            print "Sending output to", clients[fd]['address']

            # Write as much as we can.
            written = clients[fd]['connection'].send(clients[fd]['response'])

            # Delete what we have already written from the complete response.
            clients[fd]['response'] = clients[fd]['response'][written:]

            # When all the the response is written, shut the connection.
            if not clients[fd]['response']:
                ep.modify(fd, 0)
                clients[fd]['connection'].shutdown(socket.SHUT_RDWR)

    # Processing loop.
    for client in clients.keys():
        clients[client]['delay'] += 0.1

        # When the 'processing' has finished.
        if clients[client]['delay'] >= 15.0:
            # Reverse the input to form the response.
            clients[client]['response'] = clients[client]['input'][::-1]

            # Register for the ready-to-send event. The network loop uses this
            # as the signal to send the response.
            ep.modify(client, select.EPOLLOUT)

        # Processing delay.
        time.sleep(0.1)

Hinweis :Dies erkennt nur ordnungsgemäße Abschaltungen. Wenn das entfernte Ende einfach aufhört zuzuhören, ohne die richtigen Nachrichten zu senden, werden Sie es nicht wissen, bis Sie versuchen zu schreiben und einen Fehler erhalten. Dies zu überprüfen, bleibt dem Leser als Übung überlassen. Außerdem möchten Sie wahrscheinlich eine Fehlerprüfung der Gesamtschleife durchführen, damit der Server selbst ordnungsgemäß heruntergefahren wird, wenn etwas in ihm kaputt geht.


Die KEEPALIVE-Option des Sockets ermöglicht es, diese Art von Szenarios zu erkennen, bei denen die Verbindung unterbrochen wird, ohne das andere Ende zu informieren.

Sie sollten die SO_KEEPALIVE-Option auf SOL_SOCKET-Ebene festlegen. Unter Linux können Sie die Timeouts pro Socket mit TCP_KEEPIDLE (Sekunden vor dem Senden von Keepalive-Probes), TCP_KEEPCNT (fehlgeschlagene Keepalive-Probes bevor das andere Ende als tot erklärt wird) und TCP_KEEPINTVL (Intervall in Sekunden zwischen Keepalive-Probes) ändern.

In Python:

import socket
...
s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
s.setsockopt(socket.SOL_TCP, socket.TCP_KEEPIDLE, 1)
s.setsockopt(socket.SOL_TCP, socket.TCP_KEEPINTVL, 1)
s.setsockopt(socket.SOL_TCP, socket.TCP_KEEPCNT, 5)

netstat -tanop zeigt an, dass sich der Socket im Keepalive-Modus befindet:

tcp        0      0 127.0.0.1:6666          127.0.0.1:43746         ESTABLISHED 15242/python2.6     keepalive (0.76/0/0)

während tcpdump zeigt die Keepalive-Sonden:

01:07:08.143052 IP localhost.6666 > localhost.43746: . ack 1 win 2048 <nop,nop,timestamp 848683438 848683188>
01:07:08.143084 IP localhost.43746 > localhost.6666: . ack 1 win 2050 <nop,nop,timestamp 848683438 848682438>
01:07:09.143050 IP localhost.6666 > localhost.43746: . ack 1 win 2048 <nop,nop,timestamp 848683688 848683438>
01:07:09.143083 IP localhost.43746 > localhost.6666: . ack 1 win 2050 <nop,nop,timestamp 848683688 848682438>

Linux
  1. Pwd ohne Symlinks?

  2. Dateien ohne Trennzeichen einfügen?

  3. Tmux-Socket-API?

  4. Erkennen Sie Prozess, der CPU frisst, ohne:Top, Htop, Ps?

  5. Gewusst wie:Socket-Programmierung in Python

Wie erkennt man Bash>=4.0?

cp -r ohne versteckte Dateien

Raw-Socket in Python ohne Root-Rechte erstellen

Datei entfernen ohne zu fragen

Bash:ls * ohne Ordnergruppierung

Wie erkennt man eine neue angeschlossene Festplatte ohne Neustart?