J'ai écrit un serveur de jeux multi-thread simple en python qui crée un nouveau thread pour chaque connexion client. Je constate que le serveur plante de temps en temps à cause d'une erreur SIGPIPE/canal cassé. Je suis presque sûr que cela se produit lorsque le programme tente d'envoyer une réponse à un client qui n'est plus présent.
Quel est un bon moyen de faire face à cela? Ma résolution préférée fermait simplement la connexion côté serveur au client et passait au lieu de quitter le programme entier.
PS: Cette question/réponse traite le problème de manière générique; comment spécifiquement dois-je le résoudre?
Lisez sur l'essai: déclaration.
try:
# do something
except socket.error, e:
# A socket error
except IOError, e:
if e.errno == errno.EPIPE:
# EPIPE error
else:
# Other error
En supposant que vous utilisiez le module de socket standard, vous devriez capturer l'exception socket.error: (32, 'Broken pipe')
(et non pas IOError comme d'autres l'ont suggéré). Ceci sera soulevé dans le cas que vous avez décrit, c’est-à-dire que vous envoyez/écrivez sur un socket pour lequel le côté distant a été déconnecté.
import socket, errno, time
# setup socket to listen for incoming connections
s = socket.socket()
s.bind(('localhost', 1234))
s.listen(1)
remote, address = s.accept()
print "Got connection from: ", address
while 1:
try:
remote.send("message to peer\n")
time.sleep(1)
except socket.error, e:
if isinstance(e.args, Tuple):
print "errno is %d" % e[0]
if e[0] == errno.EPIPE:
# remote peer disconnected
print "Detected remote disconnect"
else:
# determine and handle different error
pass
else:
print "socket error ", e
remote.close()
break
except IOError, e:
# Hmmm, Can IOError actually be raised by the socket module?
print "Got IOError: ", e
break
Notez que cette exception ne sera pas toujours déclenchée lors de la première écriture dans un socket fermé - plus généralement lors de la seconde écriture (sauf si le nombre d'octets écrits dans la première écriture est supérieur à la taille de la mémoire tampon du socket). Gardez cela à l'esprit au cas où votre application pense que l'extrémité distante a reçu les données dès la première écriture alors qu'elle s'est peut-être déjà déconnectée.
Vous pouvez en réduire l'incidence (sans toutefois l'éliminer entièrement) en utilisant select.select()
(ou poll
). Vérifiez que les données sont prêtes à être lues par l'homologue avant de tenter une écriture. Si select
indique qu'il existe des données pouvant être lues à partir du socket homologue, lisez-les à l'aide de socket.recv()
. Si cela renvoie une chaîne vide, l'homologue distant a fermé la connexion. Dans la mesure où il existe toujours une condition de concurrence critique, vous devez toujours détecter et gérer l'exception.
Twisted est idéal pour ce genre de choses, cependant, on dirait que vous avez déjà écrit pas mal de code.
SIGPIPE
(même si je pense que vous voulez peut-être dire EPIPE
?) se produit sur des sockets lorsque vous fermez un socket puis que vous lui envoyez des données. La solution simple est de ne pas éteindre le socket avant d’essayer de lui envoyer des données. Cela peut également se produire sur des tuyaux, mais cela ne ressemble pas à ce que vous vivez, car il s'agit d'un serveur de réseau.
Vous pouvez également simplement appliquer la solution consistant à intercepter l'exception dans un gestionnaire de niveau supérieur dans chaque thread.
Bien sûr, si vous utilisiez Twisted plutôt que de créer un nouveau thread pour chaque connexion client, vous n'auriez probablement pas ce problème. Il est très difficile (voire impossible, en fonction de votre application) d'obtenir un ordre correct des opérations d'écriture et de fermeture si plusieurs threads traitent avec le même canal d'E/S.
Je suis confronté à la même question. Mais je soumets le même code la prochaine fois, ça marche ... .. La première fois, il a éclaté:
$ packet_write_wait: Connection to 10.. port 22: Broken pipe
La deuxième fois que cela fonctionne:
[1] Done Nohup python -u add_asc_dec.py > add2.log 2>&1
J'imagine que la raison peut être liée à l'environnement serveur actuel.