Cómo prevenir los SIGPIPE (o manejarlos adecuadamente)
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 SIGPIPE
fallo 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 SIGPIPE
con un controlador e ignorarlo?
Por lo general, desea ignorar SIGPIPE
y 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 SIGPIPE
controlador en SIG_IGN
. Esto evitará que cualquier escritura en un socket o tubería cause una SIGPIPE
señal.
Para ignorar la SIGPIPE
señal, utilice el siguiente código:
signal(SIGPIPE, SIG_IGN);
Si está utilizando la send()
llamada, otra opción es utilizar la MSG_NOSIGNAL
opción, que desactivará el SIGPIPE
comportamiento por llamada. Tenga en cuenta que no todos los sistemas operativos admiten la MSG_NOSIGNAL
bandera.
Por último, es posible que también desees considerar el SO_SIGNOPIPE
indicador de socket que se puede configurar setsockopt()
en algunos sistemas operativos. Esto evitará SIGPIPE
que sea causado por escrituras solo en los sockets en los que está configurado.
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.
Llegué muy tarde a la fiesta, pero SO_NOSIGPIPE
no 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_NOSIGPIPE
sería establecer la MSG_NOSIGNAL
bandera 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 );
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