Pregunta

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 a veces el cliente puede no tener interés en la respuesta y sale antes de tiempo, por lo que escribir en ese socket provocará un SIGPIPE y hará que mi servidor falle.¿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 capturar el SIGPIPE con un controlador e ignorarlo?

¿Fue útil?

Solución

Generalmente deseas ignorar el 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 manejador de SIG_IGN.Esto evitará que cualquier escritura en zócalo o tubería cause un SIGPIPE señal.

Para ignorar el SIGPIPE señal, utilice el siguiente código:

signal(SIGPIPE, SIG_IGN);

Si estás usando el send() llamar, otra opción es utilizar el MSG_NOSIGNAL opción, que convertirá el SIGPIPE comportamiento desactivado por llamada.Tenga en cuenta que no todos los sistemas operativos admiten la MSG_NOSIGNAL bandera.

Por último, es posible que también desee considerar la SO_SIGNOPIPE indicador de socket que se puede configurar con setsockopt() en algunos sistemas operativos.Esto evitará SIGPIPE de ser causado por escrituras solo en los sockets en los que está configurado.

Otros consejos

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.

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 BSD).

Una buena alternativa si estás, por ejemplo, en un sistema Linux sin SO_NOSIGPIPE sería establecer el MSG_NOSIGNAL marcar en su llamada send(2).

Ejemplo de reemplazo write(...) por send(...,MSG_NOSIGNAL) (ver sin barcomentario de)

char buf[888];
//write( sockfd, buf, sizeof(buf) );
send(    sockfd, buf, sizeof(buf), MSG_NOSIGNAL );

En esto correo 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.Aquí se explica cómo hacer esto:Para suprimir SIGPIPE primero comprobamos 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 en un escenario en el que un usuario malintencionado envió SIGPIPE manualmente a todo un proceso: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

Manejar SIGPIPE localmente

Generalmente es mejor manejar el error localmente en lugar de hacerlo en 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 le permite comunicarse 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 try catch para manejarlo allí.

Código:

El código para ignorar una señal SIGPIPE para poder 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 genere la señal SIGPIPE, pero obtendrá un error de lectura/escritura al intentar utilizar el socket, por lo que deberá verificarlo.

No puedes evitar que el proceso en el otro extremo de una tubería salga, y si sale antes de que hayas terminado de escribir, obtendrás una señal SIGPIPE.Si SIG_IGN la señal, su escritura regresará con un error, y deberá notar 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, lo intentarás de nuevo y todo el proceso podría continuar durante un largo tiempo y desperdician mucha energía de la CPU).

¿O debería simplemente capturar el SIGPIPE con un controlador e ignorarlo?

Creo que eso es correcto.Quieres saber cuándo el otro extremo ha cerrado su descriptor y eso es lo que te dice SIGPIPE.

Sam

El manual de Linux decía:

Epipe El extremo local se ha cerrado en una toma orientada a la conexión.En este caso, el proceso también recibirá un siglo a menos que se establezca msg_nosignal.

Pero para Ubuntu 12.04 no está bien.Escribí una prueba para ese caso y siempre recibo EPIPE sin SIGPIPE.SIGPIPE se genera si intento escribir en el mismo socket roto por segunda vez.Por lo tanto, no necesita ignorar SIGPIPE. Si ocurre esta señal, significa un error lógico en su programa.

¿Cuál es la mejor práctica para evitar el bloqueo aquí?

Deshabilite los sigpipes según todos, o detecte e ignore el error.

¿Hay alguna forma de comprobar si el otro lado de la línea todavía está leyendo?

Sí, use select().

select() no parece funcionar aquí ya que siempre dice que se puede escribir en el socket.

Debes seleccionar en el leer bits.Probablemente puedas ignorar el escribir bits.

Cuando el otro extremo cierra su identificador de archivo, select le indicará que hay datos listos para leer.Cuando vaya y lea eso, obtendrá 0 bytes, que es la forma en que el sistema operativo le indica que el identificador del archivo se ha cerrado.

El único momento en el que no puede ignorar los bits de escritura es si envía grandes volúmenes y existe el riesgo de que el otro extremo se atasque, lo que puede hacer que los buffers se llenen.Si eso sucede, intentar escribir en el identificador del archivo puede hacer que su programa/hilo se bloquee o falle.Probar select antes de escribir lo protegerá de eso, pero no garantiza que el otro extremo esté en buen estado o que sus datos vayan a llegar.

Tenga en cuenta que puede obtener un sigpipe desde close(), así como cuando escribe.

Close vacía todos los datos almacenados en el buffer.Si el otro extremo ya se ha cerrado, el cierre fallará y recibirá un sigpipe.

Si está utilizando TCPIP almacenado en búfer, entonces una escritura exitosa solo significa que sus datos se han puesto en cola para enviar, no significa que se hayan enviado.Hasta que llame al cierre con éxito, no sabrá que sus datos han sido enviados.

Sigpipe le dice que algo salió mal, no le dice qué ni qué debe hacer al respecto.

Bajo un sistema POSIX moderno (es decir,Linux), puede utilizar el sigprocmask() función.

#include <signal.h>

void block_signal(int signal_to_block /* i.e. SIGPIPE */ )
{
    sigset_t set;
    sigset_t old_state;

    // get the current state
    //
    sigprocmask(SIG_BLOCK, NULL, &old_state);

    // add signal_to_block to that existing state
    //
    set = old_state;
    sigaddset(&set, signal_to_block);

    // block that signal also
    //
    sigprocmask(SIG_BLOCK, &set, NULL);

    // ... deal with old_state if required ...
}

Si desea restaurar el estado anterior más adelante, asegúrese de guardar el old_state algún lugar seguro.Si llama a esa función varias veces, debe usar una pila o solo guardar la primera o la última old_state...o tal vez tener una función que elimine una señal bloqueada específica.

Para más información lea el página de manual.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top