2008-10-07 11 views
43

He escrito un simple servidor de juegos de subprocesos múltiples en python que crea un nuevo hilo para cada conexión de cliente. Me doy cuenta de que de vez en cuando, el servidor se bloquea debido a un error de tubería rota/SIGPIPE. Estoy bastante seguro de que está sucediendo cuando el programa intenta enviar una respuesta a un cliente que ya no está presente.¿Cómo manejar una tubería rota (SIGPIPE) en python?

¿Cuál es una buena manera de lidiar con esto? Mi resolución preferida simplemente cerraría la conexión del lado del servidor con el cliente y continuaría, en lugar de salir de todo el programa.

PS: This pregunta/respuesta se ocupa del problema de una manera genérica; ¿Qué tan específicamente debería resolverlo?

Respuesta

36

Lectura en la declaración 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 hago una prueba: # algo, excepto: # cualquier cosa, ¿captará algo, y no solo IOErrors? –

+5

La manta excepto es una mala política. Sin embargo, detectará cualquier tipo de excepción. Usted sabe que es un IOError. Maneja eso. Si surge algo más, descubra por qué y trátelo apropiadamente. No desea enmascarar errores como la división por cero o la falta de memoria. –

+1

Si está utilizando el módulo de socket de Python, no obtendrá una excepción IOError: obtendrá una excepción socket.error. – mhawke

3

SIGPIPE (aunque creo que tal vez quiere decir EPIPE?) Se produce en sockets cuando cierra un socket y luego envía los datos a la misma. La solución más simple es no cerrar el zócalo antes de intentar enviar datos. Esto también puede ocurrir en las tuberías, pero no parece que eso sea lo que estás experimentando, ya que es un servidor de red.

También puede aplicar la tirita de captura de la excepción en algún controlador de nivel superior en cada hilo.

Por supuesto, si utilizó Twisted en lugar de generar un nuevo hilo para cada conexión de cliente, probablemente no tenga este problema. Es realmente difícil (quizás imposible, dependiendo de su aplicación) hacer que el orden de las operaciones de cierre y escritura sea correcto si varios hilos están tratando con el mismo canal de E/S.

+0

* La solución simple es no cerrar el zócalo antes de intentar enviar datos. * Supongamos aquí que el zócalo se cerró localmente (en el lado del servidor) mientras que en [este] (http://stackoverflow.com/ a/11866962/95735) respondemos que leemos * Esto generalmente ocurre cuando escribe en un socket totalmente cerrado en el otro lado (cliente). * ¿Omitió este caso por propósito o no está de acuerdo con esta afirmación? –

-3

Mi respuesta está muy cerca de S. Lott de, excepto que sería aún más especial:

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 

donde "23" es el número de error que se obtiene de EPIPE. De esta forma, no intentará manejar un error de permisos o cualquier otra cosa para la que no esté equipado.

+2

El errno sería 32, no 23. – mhawke

+2

Debería haber aclarado que quería decir "23" como marcador de posición. De Verdad? 32? Estaba más cerca de lo que hubiera adivinado. :-) –

47

Suponiendo que está utilizando el módulo de socket estándar, debería detectar la excepción socket.error: (32, 'Broken pipe') (no IOError como otros han sugerido). Esto se generará en el caso que haya descrito, es decir, que esté enviando/escribiendo a un socket para el cual el lado remoto se ha desconectado.

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 

Tenga en cuenta que esta excepción no siempre se planteó en la primera escritura a una toma cerrada - más generalmente la segunda escritura (a menos que el número de bytes escritos en la primera escritura es más grande que el tamaño del búfer de la toma). Debe tener esto en cuenta en caso de que su aplicación considere que el extremo remoto recibió los datos de la primera escritura cuando ya se haya desconectado.

Puede reducir la incidencia (pero no eliminar completamente) de esto utilizando select.select() (o poll). Verifique que haya datos listos para leer por el par antes de intentar escribir. Si select informa que hay datos disponibles para leer desde el socket par, léalo usando socket.recv(). Si esto devuelve una cadena vacía, el par remoto ha cerrado la conexión. Debido a que todavía hay una condición de carrera aquí, igual tendrá que atrapar y manejar la excepción.

Twisted es ideal para este tipo de cosas, sin embargo, parece que ya ha escrito un poco de código.

+0

Esto parece extraño 'if isinstance (e.args, tuple):'. ¿Alguien puede explicar esto? – guettli

+0

Eso significa, "¿es e.args una tupla?" – mjz19910

0

Me enfrento con la misma pregunta. Pero presento el mismo código la próxima vez, simplemente funciona. La primera vez que se rompió:

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

La segunda vez funciona:

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

supongo que la razón puede ser sobre el entorno de servidor actual.

Cuestiones relacionadas