web-dev-qa-db-fra.com

Qu'est-ce que socket.recv () de Python renvoie pour les sockets non bloquants si aucune donnée n'est reçue avant l'expiration du délai?

En gros, j’ai lu à plusieurs endroits que socket.recv() retournera tout ce qu’elle peut lire, ou une chaîne vide signalant que l’autre côté s’est éteint (la documentation officielle ne mentionne même pas ce qu’elle renvoie quand la connexion est fermée ... génial!). C’est parfait pour le blocage des sockets, car nous savons que recv() ne retourne que s’il ya quelque chose à recevoir. Ainsi, quand il renvoie une chaîne vide, il DOIT OBLIGATOIREMENT signifie que l'autre côté a fermé la connexion, non?

D'accord, d'accord, mais que se passe-t-il lorsque mon socket est non bloquant? J'ai cherché un peu (peut-être pas assez, qui sait?) Et je ne sais pas comment dire quand l'autre côté a fermé la connexion en utilisant une socket non bloquante. Il semble n'y avoir aucune méthode ou attribut qui nous le dise, et comparer la valeur de retour de recv() à la chaîne vide semble absolument inutile ... est-ce juste que j'ai ce problème?

Par exemple, disons que le délai d'attente de mon socket est fixé à 1.2342342 (quel que soit le nombre non négatif que vous aimez ici) secondes et j'appelle socket.recv(1024), mais l'autre côté n'envoie rien pendant cette seconde période de 1.2342342 . L'appel recv() retournera une chaîne vide et je ne sais pas si la connexion est toujours debout ou non ...

49

Dans le cas d'une socket non bloquante pour laquelle aucune donnée n'est disponible, recv lèvera l'exception socket.error et la valeur de l'exception aura le numéro d'erreur errno soit EAGAIN ou EWOULDBLOCK. Exemple:

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 :)

La situation est un peu différente dans le cas où vous avez activé le comportement non bloquant via un délai d'attente avec socket.settimeout(n) ou socket.setblocking(False) . Dans ce cas, un socket.error est toujours généré, mais dans le cas d'un délai d'expiration, la valeur associée à l'exception est toujours une chaîne définie sur "expiré". Donc, pour gérer ce cas, vous pouvez faire:

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 :)

Comme indiqué dans les commentaires, il s’agit également d’une solution plus portable car elle ne dépend pas des fonctionnalités propres au système d’exploitation pour placer le socket en mode non bloquant.

Voir recv (2) et socket python pour plus de détails.

66
mshildt

C'est simple: si recv() renvoie 0 octet; Vous ne recevrez plus de données sur cette connexion. Jamais. Vous pourrez peut-être encore envoyer.

Cela signifie que votre socket non bloquant doit générer une exception (cela peut dépendre du système) si aucune donnée n'est disponible mais que la connexion est toujours vivante (l'autre extrémité peut envoyer).

7
jfs

Lorsque vous utilisez recv en connexion avec select si le socket est prêt à être lu mais qu'il n'y a aucune donnée à lire, cela signifie que le client a fermé la connexion.

Voici du code qui gère cela, notez également l'exception qui est levée lorsque recv est appelé une seconde fois dans la boucle while. S'il ne reste plus rien à lire, cette exception sera levée, cela ne signifie pas que le client a fermé la connexion:

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)

Et la fonction qui reçoit les données:

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
Barış Uşaklı

Pour compléter les réponses existantes, je suggérerais en utilisant select au lieu de sockets non bloquants . Le fait est que les sockets non bloquantes compliquent les choses (sauf peut-être l'envoi), alors je dirais qu'il n'y a aucune raison de les utiliser du tout. Si vous rencontrez régulièrement le problème que votre application est bloquée en attente d'IO, je vous conseillerais également de faire IO dans un fil séparé en arrière-plan.

2
Ulrich Eckhardt