
jkramer
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 temprano. Así que escribir en ese socket causará un SIGPIPE
y hacer que mi servidor se bloquee.
¿Cuál es la mejor práctica para evitar el bloqueo aquí? ¿Hay alguna manera de verificar 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 atrapar el SIGPIPE
con un controlador e ignorarlo?

dvorak
Por lo general, desea ignorar el SIGPIPE
y maneje el error directamente en su código. Esto se debe a que los controladores 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 de SIG_IGN
. Esto evitará que cualquier escritura de socket o tubería cause una SIGPIPE
señal.
para ignorar el SIGPIPE
señal, utilice el siguiente código:
signal(SIGPIPE, SIG_IGN);
Si estás usando el send()
llamada, otra opción es usar el MSG_NOSIGNAL
opción, que convertirá la SIGPIPE
comportamiento desactivado por llamada. Tenga en cuenta que no todos los sistemas operativos admiten el MSG_NOSIGNAL
bandera.
Por último, es posible que también desee considerar la SO_SIGNOPIPE
bandera de socket que se puede establecer con setsockopt()
en algunos sistemas operativos. Esto evitará SIGPIPE
de ser causado por escrituras solo en los sockets en los que está configurado.

usuario55807
Otro método es cambiar el socket para que nunca genere SIGPIPE en write(). Esto es más conveniente en las 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…), (asumiendo que está usando C/C++), puede hacer esto con:
int set = 1;
setsockopt(sd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int));
Con esto en efecto, en lugar de generar la señal SIGPIPE, se devolverá EPIPE.

sklnd
Llego 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 tiene, digamos, un sistema Linux sin SO_NOSIGPIPE
sería establecer el MSG_NOSIGNAL
marcar en su llamada de envío (2).
Ejemplo reemplazando 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 );

kroki
En esto correo Describí una posible solución para el caso de Solaris cuando ni SO_NOSIGPIPE ni MSG_NOSIGNAL están disponibles.
En su lugar, tenemos que suprimir temporalmente SIGPIPE en el subproceso actual que ejecuta el código de la biblioteca. Así es como se hace esto: para suprimir SIGPIPE, primero verificamos si está pendiente. Si lo hace, 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 funciona. Si SIGPIPE no está pendiente, lo bloqueamos en este hilo y también verificamos 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. De lo contrario, comprobamos si está pendiente ahora. Si lo hace (lo que significa que las 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 bloqueos en un escenario en el que un usuario malicioso envió SIGPIPE manualmente a todo un proceso: en este caso lo veremos pendiente, pero otro subproceso puede manejarlo 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
Manejar SIGPIPE localmente
Por lo general, es mejor manejar el error localmente en lugar de un controlador de eventos de señal global, ya que localmente tendrá más contexto sobre lo que está sucediendo y qué recurso tomar.
Tengo una capa de comunicación en una de mis aplicaciones que permite que mi aplicación se comunique con un accesorio externo. Cuando ocurre un error de escritura, lanzo una excepción en la capa de comunicación y dejo que suba a un bloque de captura de prueba para manejarlo allí.
Código:
El código para ignorar una señal SIGPIPE para que pueda manejarla localmente es:
// We expect write failures to occur but we want to handle them where
// the error occurs rather than in a SIGPIPE handler.
signal(SIGPIPE, SIG_IGN);
Este código evitará que se eleve la señal SIGPIPE, pero obtendrá un error de lectura/escritura cuando intente usar el zócalo, por lo que deberá verificarlo.

jonathan leffler
No puede evitar que el proceso en el otro extremo de una tubería salga, y si sale antes de que haya terminado de escribir, obtendrá una señal SIGPIPE. Si SIG_IGN la señal, entonces su escritura regresará con un error, y debe anotar y reaccionar ante ese error. Simplemente capturar e ignorar la señal en un controlador no es una buena idea; debe tener en cuenta que la tubería ahora está inactiva y modificar el comportamiento del programa para que no vuelva a escribir en la tubería (porque la señal se generará nuevamente y se ignorará). de nuevo, y lo intentará de nuevo, y todo el proceso podría continuar por un largo tiempo y desperdiciar mucha potencia de CPU).

sam reynolds
¿O debería simplemente atrapar el SIGPIPE con un controlador e ignorarlo?
Creo que está bien. Quiere saber cuándo el otro extremo ha cerrado su descriptor y eso es lo que le dice SIGPIPE.
Sam