Cómo prevenir los SIGPIPE (o manejarlos adecuadamente)

Resuelto jkramer asked hace 16 años • 10 respuestas

Tengo un pequeño programa de servidor que acepta conexiones en un socket TCP o UNIX local, lee un comando simple y (según el comando) envía una respuesta.

El problema es que el cliente puede no tener interés en la respuesta y, a veces, sale antes de tiempo. Entonces, escribir en ese socket provocará un SIGPIPEfallo y mi servidor fallará.

¿Cuál es la mejor práctica para evitar el bloqueo aquí? ¿Hay alguna forma de comprobar si el otro lado de la línea todavía está leyendo? ( select()no parece funcionar aquí, ya que siempre dice que se puede escribir en el socket). ¿O debería simplemente detectarlo SIGPIPEcon un controlador e ignorarlo?

jkramer avatar Sep 20 '08 20:09 jkramer
Aceptado

Por lo general, desea ignorar SIGPIPEy manejar el error directamente en su código. Esto se debe a que los manejadores de señales en C tienen muchas restricciones sobre lo que pueden hacer.

La forma más portátil de hacer esto es configurar el SIGPIPEcontrolador en SIG_IGN. Esto evitará que cualquier escritura en un socket o tubería cause una SIGPIPEseñal.

Para ignorar la SIGPIPEseñal, utilice el siguiente código:

signal(SIGPIPE, SIG_IGN);

Si está utilizando la send()llamada, otra opción es utilizar la MSG_NOSIGNALopción, que desactivará el SIGPIPEcomportamiento por llamada. Tenga en cuenta que no todos los sistemas operativos admiten la MSG_NOSIGNALbandera.

Por último, es posible que también desees considerar el SO_SIGNOPIPEindicador de socket que se puede configurar setsockopt()en algunos sistemas operativos. Esto evitará SIGPIPEque sea causado por escrituras solo en los sockets en los que está configurado.

dvorak avatar Sep 20 '2008 13:09 dvorak

Otro método es cambiar el socket para que nunca genere SIGPIPE al escribir(). Esto es más conveniente en bibliotecas, donde es posible que no desee un controlador de señal global para SIGPIPE.

En la mayoría de los sistemas basados ​​en BSD (MacOS, FreeBSD...), (suponiendo que esté utilizando C/C++), puede hacer esto con:

int set = 1;
setsockopt(sd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int));

Con esto en vigor, en lugar de generarse la señal SIGPIPE, se devolverá EPIPE.

user55807 avatar Jan 16 '2009 11:01 user55807

Llegué muy tarde a la fiesta, pero SO_NOSIGPIPEno es portátil y es posible que no funcione en su sistema (parece ser algo de BSD).

Una buena alternativa si estás, por ejemplo, en un sistema Linux sin él SO_NOSIGPIPEsería establecer la MSG_NOSIGNALbandera en tu llamada send(2).

Ejemplo de sustitución write(...)por send(...,MSG_NOSIGNAL)(ver el comentario de nobar )

char buf[888];
//write( sockfd, buf, sizeof(buf) );
send(    sockfd, buf, sizeof(buf), MSG_NOSIGNAL );
sklnd avatar Nov 10 '2009 04:11 sklnd

En esta publicación describí una posible solución para el caso de Solaris cuando ni SO_NOSIGPIPE ni MSG_NOSIGNAL están disponibles.

En cambio, tenemos que suprimir temporalmente SIGPIPE en el hilo actual que ejecuta el código de la biblioteca. Así es como se hace: para suprimir SIGPIPE, primero verificamos si está pendiente. Si es así, significa que está bloqueado en este hilo y no tenemos que hacer nada. Si la biblioteca genera SIGPIPE adicional, se fusionará con el pendiente, y eso no es operativo. Si SIGPIPE no está pendiente, lo bloquearemos en este hilo y también verificaremos si ya estaba bloqueado. Entonces somos libres de ejecutar nuestras escrituras. Cuando vamos a restaurar SIGPIPE a su estado original, hacemos lo siguiente: si SIGPIPE estaba pendiente originalmente, no hacemos nada. En caso contrario comprobamos si está pendiente ahora. Si es así (lo que significa que nuestras acciones han generado uno o más SIGPIPE), entonces lo esperamos en este hilo, borrando así su estado pendiente (para hacer esto usamos sigtimedwait() con tiempo de espera cero; esto es para evitar el bloqueo). un escenario en el que un usuario malintencionado envió SIGPIPE manualmente a un proceso completo: en este caso lo veremos pendiente, pero es posible que otro hilo lo maneje antes de que tuviéramos un cambio para esperarlo). Después de borrar el estado pendiente, desbloqueamos SIGPIPE en este hilo, pero solo si no estaba bloqueado originalmente.

Código de ejemplo en https://github.com/kroki/XProbes/blob/1447f3d93b6dbf273919af15e59f35cca58fcc23/src/libxprobes.c#L156

kroki avatar Feb 27 '2010 15:02 kroki