2008-10-07 88 views
43

J'ai écrit un serveur de jeu multi-thread simple en python qui crée un nouveau thread pour chaque connexion client. Je trouve que de temps en temps, le serveur va planter à cause d'une erreur broken-pipe/SIGPIPE. Je suis à peu près certain que cela se produit lorsque le programme tente d'envoyer une réponse à un client qui n'est plus présent.Comment gérer un tuyau cassé (SIGPIPE) en python?

Quelle est une bonne façon 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: This question/réponse traite le problème d'une manière générique; comment devrais-je le résoudre?

Répondre

36

Lire sur l'instruction try:.

try: 
    # do something 
except socket.error, e: 
    # A socket error 
except IOError, e: 
    if e.errno == errno.EPIPE: 
     # EPIPE error 
    else: 
     # Other error 
+0

Si je fais un essai: #quelque chose sauf: # quoi que ce soit, est-ce que ça va juste attraper n'importe quoi, et pas seulement IOErrors? –

+5

La couverture sauf est une mauvaise politique. Mais encore, il va attraper n'importe quelle sorte d'exception. Vous savez que c'est un IOError. Gérer ce. Si quelque chose d'autre surgit, comprendre pourquoi et gérer de manière appropriée. Vous ne voulez pas masquer les bogues comme la division par zéro ou la mémoire. –

+1

Si vous utilisez le module socket de Python, vous ne recevrez pas d'exception IOError: vous obtiendrez une exception socket.error. – mhawke

3

SIGPIPE (bien que je pense que peut-être vous dire EPIPE?) Se produit sur les sockets lorsque vous arrêtez une prise, puis d'envoyer des données. La solution simple n'est pas de fermer le socket avant d'essayer de lui envoyer des données. Cela peut également se produire sur les canaux, mais cela ne semble pas être le cas, car il s'agit d'un serveur réseau.

Vous pouvez également appliquer le pansement de capture de l'exception dans un gestionnaire de niveau supérieur dans chaque thread.

Bien sûr, si vous avez utilisé Twisted plutôt que de générer un nouveau thread pour chaque connexion client, vous n'auriez probablement pas ce problème. Il est vraiment difficile (peut-être impossible, selon votre application) de corriger l'ordre des opérations de fermeture et d'écriture si plusieurs threads traitent le même canal d'E/S.

+0

* La solution simple n'est pas de fermer le socket avant d'essayer de lui envoyer des données. * Vous supposez ici que le socket a été arrêté localement (côté serveur) alors que dans [this] (http://stackoverflow.com/ a/11866962/95735) répondez que nous lisons cela * Cela se produit généralement lorsque vous écrivez sur un socket complètement fermé de l'autre côté (client). * Avez-vous omis ce cas par exprès ou vous n'êtes pas d'accord avec cette affirmation? –

-3

Ma réponse est très proche de S. Lott de, sauf que je serais encore plus particulier:

try: 
    # do something 
except IOError, e: 
    # ooops, check the attributes of e to see precisely what happened. 
    if e.errno != 23: 
     # I don't know how to handle this 
     raise 

où « 23 » est le numéro d'erreur que vous obtenez de EPIPE. De cette façon, vous ne tenterez pas de gérer une erreur d'autorisation ou toute autre chose pour laquelle vous n'êtes pas équipé.

+2

L'errno serait 32, pas 23. – mhawke

+2

J'aurais dû préciser que je voulais dire "23" comme un espace réservé. Vraiment? 32? J'étais plus proche que je ne l'aurais deviné. :-) –

47

En supposant que vous utilisez le module de socket standard, vous devriez attraper l'exception socket.error: (32, 'Broken pipe') (pas IOError comme d'autres l'ont suggéré). Cela sera déclenché dans le cas que vous avez décrit, c'est-à-dire en envoyant/en écrivant sur une socket pour laquelle le côté distant s'est 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 élevé sur la première écriture à une prise fermée - plus généralement la seconde écriture (à moins que le nombre d'octets écrits dans la première écriture est plus grande que la taille de la mémoire tampon de la prise). Vous devez garder cela à l'esprit au cas où votre application pense que l'extrémité distante a reçu les données de la première écriture alors qu'elle était déjà déconnectée.

Vous pouvez réduire l'incidence (mais pas 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 d'essayer d'écrire. Si select signale qu'il y a des données disponibles à lire à partir du socket homologue, lisez-le en utilisant socket.recv(). Si cela renvoie une chaîne vide, l'homologue distant a fermé la connexion. Étant donné qu'il existe toujours une condition de concurrence, vous devrez toujours capturer et gérer l'exception. Twisted est idéal pour ce genre de chose, cependant, il semble que vous avez déjà écrit un peu de code.

+0

Cela semble étrange 'if isinstance (e.args, tuple):'. Quelqu'un peut-il expliquer cela? – guettli

+0

Cela signifie, "est-ce que e.args un tuple?" – mjz19910

0

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 cassé:

$ packet_write_wait: Connection to 10.. port 22: Broken pipe 

La deuxième fois cela fonctionne:

[1] Done     nohup python -u add_asc_dec.py > add2.log 2>&1 

Je suppose que la raison peut être de l'environnement du serveur actuel.