web-dev-qa-db-fra.com

Comment savoir si une connexion est morte dans python

Je veux que mon application python puisse savoir quand le socket de l'autre côté a été abandonné. Existe-t-il une méthode pour cela?

57
directedition

Cela dépend de ce que vous entendez par "abandonné". Pour TCP sockets, si l'autre extrémité ferme la connexion via close () ou le processus se terminant, vous le découvrirez en lisant une fin de fichier ou en obtenant une erreur de lecture, généralement le errno étant défini sur la "réinitialisation de connexion par l'homologue" de votre système d'exploitation. Pour python, vous lirez une chaîne de longueur nulle ou une socket.error sera levée lorsque vous essayez de lire ou d'écrire à partir du socket.

36
Andy V

Réponse courte:

utilisez un recv () non bloquant, ou un recv ()/select () bloquant avec un délai très court.

Longue réponse:

La façon de gérer les connexions de socket consiste à lire ou à écrire selon vos besoins et à être prêt à gérer les erreurs de connexion.

TCP distingue 3 formes de "coupure" d'une connexion: timeout, reset, close.

Parmi ceux-ci, le délai d'attente ne peut pas vraiment être détecté, TCP pourrait seulement vous dire que le temps n'est pas encore expiré. Mais même s'il vous le dit, le temps peut encore expirer juste après.

Souvenez-vous également que l'utilisation de shutdown (), vous ou votre homologue (l'autre extrémité de la connexion) peut fermer uniquement le flux d'octets entrant et garder le flux d'octets sortant en cours d'exécution, ou fermer le flux sortant et garder celui entrant en cours d'exécution.

Donc, à strictement parler, vous voulez vérifier si le flux de lecture est fermé, ou si le flux d'écriture est fermé, ou si les deux sont fermés.

Même si la connexion a été "interrompue", vous devriez toujours pouvoir lire toutes les données qui sont encore dans la mémoire tampon du réseau. Ce n'est qu'une fois le tampon vide que vous recevrez une déconnexion de recv ().

Vérifier si la connexion a été interrompue revient à demander "que vais-je recevoir après avoir lu toutes les données actuellement mises en mémoire tampon?" Pour le savoir, il vous suffit de lire toutes les données actuellement mises en mémoire tampon.

Je peux voir comment "lire toutes les données tamponnées", pour y arriver, pourrait être un problème pour certaines personnes, qui considèrent toujours recv () comme une fonction de blocage. Avec un blocage recv (), "vérifier" une lecture lorsque le tampon est déjà vide bloquera, ce qui va à l'encontre du but de "vérifier".

À mon avis, toute fonction qui est documentée pour potentiellement bloquer indéfiniment l'ensemble du processus est un défaut de conception, mais je suppose qu'elle est toujours là pour des raisons historiques, à partir du moment où utiliser un socket comme un descripteur de fichier ordinaire était une bonne idée.

Ce que vous pouvez faire, c'est:

  • définissez le socket en mode non bloquant, mais vous obtenez une erreur dépendante du système pour indiquer que le tampon de réception est vide ou que le tampon d'envoi est plein
  • s'en tenir au mode de blocage mais définir un délai d'expiration de socket très court. Cela vous permettra de "cingler" ou de "vérifier" le socket avec recv (), à peu près ce que vous voulez faire
  • utilisez l'appel select () ou le module asyncore avec un délai très court. Le rapport d'erreurs est toujours spécifique au système.

Pour la partie écriture du problème, garder les tampons de lecture vides le couvre à peu près. Vous découvrirez une connexion "abandonnée" après une tentative de lecture non bloquante, et vous pouvez choisir d'arrêter d'envoyer quoi que ce soit après qu'une lecture renvoie un canal fermé.

Je suppose que la seule façon d'être sûr que vos données envoyées ont atteint l'autre extrémité (et ne sont pas encore dans le tampon d'envoi) est soit:

  • recevoir une réponse appropriée sur la même prise pour le message exact que vous avez envoyé. Fondamentalement, vous utilisez le protocole de niveau supérieur pour fournir une confirmation.
  • effectuer un shutdow () et un close () réussis sur le socket

Le howto socket python indique que send () renverra 0 octet écrit si le canal est fermé. Vous pouvez utiliser un socket.send () non bloquant ou timeout et s'il renvoie 0, vous ne pouvez pas envoyer plus de données sur ce socket. Mais s'il retourne non nul, vous avez déjà envoyé quelque chose, bonne chance avec ça :)

Ici aussi, je n'ai pas considéré les données de socket OOB (hors bande) ici comme un moyen d'aborder votre problème, mais je pense que OOB n'était pas ce que vous vouliez dire.

44
Toughy

À partir du lien que Jweede a publié:

exception socket.timeout:

This exception is raised when a timeout occurs on a socket
which has had timeouts enabled via a prior call to settimeout().
The accompanying value is a string whose value is currently
always “timed out”.

Voici le serveur de démonstration et les programmes clients pour le module socket de la docs python

# Echo server program
import socket

Host = ''                 # Symbolic name meaning all available interfaces
PORT = 50007              # Arbitrary non-privileged port
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((Host, PORT))
s.listen(1)
conn, addr = s.accept()
print 'Connected by', addr
while 1:
    data = conn.recv(1024)
    if not data: break
    conn.send(data)
conn.close()

Et le client:

# Echo client program
import socket

Host = 'daring.cwi.nl'    # The remote Host
PORT = 50007              # The same port as used by the server
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((Host, PORT))
s.send('Hello, world')
data = s.recv(1024)
s.close()
print 'Received', repr(data)

Sur la page d'exemple de documents à partir de laquelle je les ai extraits, il existe des exemples plus complexes qui utilisent cette idée, mais voici la réponse simple:

En supposant que vous écrivez le programme client, mettez simplement tout votre code qui utilise le socket lorsqu'il risque d'être abandonné, dans un bloc try ...

try:
    s.connect((Host, PORT))
    s.send("Hello, World!")
    ...
except socket.timeout:
    # whatever you need to do when the connection is dropped
14
Jiaaro

J'ai traduit l'exemple de code de ce billet de blog en Python: Comment détecter quand le client ferme la connexion? , et cela fonctionne bien pour moi:

from ctypes import (
    CDLL, c_int, POINTER, Structure, c_void_p, c_size_t,
    c_short, c_ssize_t, c_char, ARRAY
)


__all__ = 'is_remote_alive',


class pollfd(Structure):
    _fields_ = (
        ('fd', c_int),
        ('events', c_short),
        ('revents', c_short),
    )


MSG_DONTWAIT = 0x40
MSG_PEEK = 0x02

EPOLLIN = 0x001
EPOLLPRI = 0x002
EPOLLRDNORM = 0x040

libc = CDLL(None)

recv = libc.recv
recv.restype = c_ssize_t
recv.argtypes = c_int, c_void_p, c_size_t, c_int

poll = libc.poll
poll.restype = c_int
poll.argtypes = POINTER(pollfd), c_int, c_int


class IsRemoteAlive:  # not needed, only for debugging
    def __init__(self, alive, msg):
        self.alive = alive
        self.msg = msg

    def __str__(self):
        return self.msg

    def __repr__(self):
        return 'IsRemoteClosed(%r,%r)' % (self.alive, self.msg)

    def __bool__(self):
        return self.alive


def is_remote_alive(fd):
    fileno = getattr(fd, 'fileno', None)
    if fileno is not None:
        if hasattr(fileno, '__call__'):
            fd = fileno()
        else:
            fd = fileno

    p = pollfd(fd=fd, events=EPOLLIN|EPOLLPRI|EPOLLRDNORM, revents=0)
    result = poll(p, 1, 0)
    if not result:
        return IsRemoteAlive(True, 'empty')

    buf = ARRAY(c_char, 1)()
    result = recv(fd, buf, len(buf), MSG_DONTWAIT|MSG_PEEK)
    if result > 0:
        return IsRemoteAlive(True, 'readable')
    Elif result == 0:
        return IsRemoteAlive(False, 'closed')
    else:
        return IsRemoteAlive(False, 'errored')
5
kay

Si je ne me trompe pas, cela est généralement géré via un timeout .

4
Jon W