it-swarm.com.de

Was gibt Pythons socket.recv () für nicht blockierende Sockets zurück, wenn bis zu einem Timeout keine Daten empfangen werden?

Grundsätzlich habe ich an mehreren Stellen gelesen, dass socket.recv() alles zurückgibt, was es lesen kann, oder eine leere Zeichenfolge, die signalisiert, dass die andere Seite heruntergefahren wurde (die offiziellen Dokumente erwähnen nicht einmal, was es zurückgibt, wenn die Verbindung ist geschlossen ... super!). Dies ist alles in Ordnung, um Sockets zu blockieren, da wir wissen, dass recv() nur zurückgibt, wenn es tatsächlich etwas zu empfangen gibt. Wenn es also einen leeren String zurückgibt, muss es ) meine die andere Seite hat die Verbindung geschlossen, oder?

Okay, gut, aber was passiert, wenn meine Steckdose nicht blockiert? Ich habe ein wenig gesucht (vielleicht nicht genug, wer weiß?) Und kann nicht feststellen, wann die andere Seite die Verbindung über eine nicht blockierende Buchse geschlossen hat. Es scheint keine Methode oder kein Attribut zu geben, die dies verraten, und der Vergleich des Rückgabewerts von recv() mit der leeren Zeichenfolge scheint absolut nutzlos ... habe ich nur dieses Problem?

Als einfaches Beispiel nehmen wir an, dass das Timeout meines Sockets auf 1,2342342 Sekunden eingestellt ist (egal, welche nicht negative Zahl Sie hier mögen), und ich rufe socket.recv(1024) auf, aber die andere Seite sendet während dieses Zeitraums von 1,2342342 Sekunden nichts . Der recv() -Aufruf gibt eine leere Zeichenfolge zurück und ich habe keine Ahnung, ob die Verbindung noch besteht oder nicht ...

49

Im Fall eines nicht blockierenden Sockets, für den keine Daten verfügbar sind, löst recv die Ausnahme socket.error aus, und der Wert der Ausnahme hat die Fehlernummer EAGAIN oder EWOULDBLOCK. Beispiel:

import sys
import socket
import fcntl, os
import errno
from time import sleep

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1',9999))
fcntl.fcntl(s, fcntl.F_SETFL, os.O_NONBLOCK)

while True:
    try:
        msg = s.recv(4096)
    except socket.error, e:
        err = e.args[0]
        if err == errno.EAGAIN or err == errno.EWOULDBLOCK:
            sleep(1)
            print 'No data available'
            continue
        else:
            # a "real" error occurred
            print e
            sys.exit(1)
    else:
        # got a message, do something :)

Die Situation ist etwas anders, wenn Sie das blockierungsfreie Verhalten über eine Zeitüberschreitung mit socket.settimeout(n) oder socket.setblocking(False) . In diesem Fall wird noch ein socket.error ausgelöst, aber im Falle einer Zeitüberschreitung ist der Begleitwert der Ausnahme immer eine Zeichenfolge, die auf "Zeitüberschreitung" gesetzt ist. Um diesen Fall zu behandeln, können Sie Folgendes tun:

import sys
import socket
from time import sleep

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1',9999))
s.settimeout(2)

while True:
    try:
        msg = s.recv(4096)
    except socket.timeout, e:
        err = e.args[0]
        # this next if/else is a bit redundant, but illustrates how the
        # timeout exception is setup
        if err == 'timed out':
            sleep(1)
            print 'recv timed out, retry later'
            continue
        else:
            print e
            sys.exit(1)
    except socket.error, e:
        # Something else happened, handle error, exit, etc.
        print e
        sys.exit(1)
    else:
        if len(msg) == 0:
            print 'orderly shutdown on server end'
            sys.exit(0)
        else:
            # got a message do something :)

Wie in den Kommentaren angegeben, ist dies auch eine portablere Lösung, da es nicht von der betriebssystemspezifischen Funktionalität abhängt, den Socket in den blockierungsfreien Modus zu versetzen.

Weitere Informationen finden Sie unter recv (2) und Python-Socket .

66
mshildt

Es ist einfach: wenn recv() 0 Bytes zurückgibt; Über diese Verbindung werden Sie nie mehr Daten erhalten. Möglicherweise können Sie noch senden.

Dies bedeutet, dass Ihr nicht blockierender Socket eine Ausnahme auslösen muss (dies kann systemabhängig sein), wenn keine Daten verfügbar sind, die Verbindung jedoch noch besteht (das andere Ende sendet möglicherweise).

7
jfs

Wenn Sie recv in Verbindung mit select verwenden, wenn der Socket zum Lesen bereit ist, aber keine Daten zum Lesen vorhanden sind, bedeutet dies, dass der Client die Verbindung geschlossen hat.

Hier ist ein Code, der dies behandelt. Beachten Sie auch die Ausnahme, die ausgelöst wird, wenn recv in der while-Schleife ein zweites Mal aufgerufen wird. Wenn nichts mehr zu lesen ist, wird diese Ausnahme ausgelöst. Dies bedeutet nicht, dass der Client die Verbindung geschlossen hat:

def listenToSockets(self):

    while True:

        changed_sockets = self.currentSockets

        ready_to_read, ready_to_write, in_error = select.select(changed_sockets, [], [], 0.1)

        for s in ready_to_read:

            if s == self.serverSocket:
                self.acceptNewConnection(s)
            else:
                self.readDataFromSocket(s)

Und die Funktion, die die Daten empfängt:

def readDataFromSocket(self, socket):

    data = ''
    buffer = ''
    try:

        while True:
            data = socket.recv(4096)

            if not data: 
                break

            buffer += data

    except error, (errorCode,message): 
        # error 10035 is no data available, it is non-fatal
        if errorCode != 10035:
            print 'socket.error - ('+str(errorCode)+') ' + message


    if data:
        print 'received '+ buffer
    else:
        print 'disconnected'
5

Um die vorhandenen Antworten zu vervollständigen, würde ich vorschlagen mit select anstelle von nonblocking Sockets . Der Punkt ist, dass nicht blockierende Sockets Dinge komplizieren (außer vielleicht das Senden), also würde ich sagen, dass es keinen Grund gibt, sie überhaupt zu benutzen. Wenn Sie regelmäßig das Problem haben, dass Ihre App blockiert ist und auf IO wartet, würde ich auch in Betracht ziehen, das IO in einem separaten Thread im Hintergrund auszuführen.

2
Ulrich Eckhardt