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?
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.
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:
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:
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.
À 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
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')
Si je ne me trompe pas, cela est généralement géré via un timeout .