Tengo un pequeño programa de servidor que acepta conexiones en un socket TCP o local de UNIX, lee un comando simple y, dependiendo del comando, envía una respuesta. El problema es que el cliente puede no tener interés en la respuesta a veces y sale temprano, por lo que escribir en ese socket provocará un SIGPIPE y hará que mi servidor se bloquee. ¿Cuál es la mejor práctica para evitar el accidente 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 el socket es grabable). ¿O debería atrapar el SIGPIPE con un controlador e ignorarlo?
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 ...), (suponiendo que esté usando C / C ++), puede hacer esto con:
Con esto en efecto, en lugar de que se genere la señal SIGPIPE, se devolverá EPIPE.
fuente
Llego súper 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á en, digamos, un sistema Linux sin
SO_NOSIGPIPE
sería establecer elMSG_NOSIGNAL
indicador en su llamada send (2).Ejemplo reemplazado
write(...)
porsend(...,MSG_NOSIGNAL)
(ver el comentario de nobar )fuente
QTcpSocket
objeto Qt para envolver el uso de sockets, tuve que reemplazar lawrite
llamada al método por un sistema operativosend
(usando elsocketDescriptor
método). ¿Alguien sabe una opción más limpia para establecer esta opción en unaQTcpSocket
clase?En esto publicación describí una posible solución para el caso de Solaris cuando ni SO_NOSIGPIPE ni MSG_NOSIGNAL están disponibles.
Código de ejemplo en https://github.com/kroki/XProbes/blob/1447f3d93b6dbf273919af15e59f35cca58fcc23/src/libxprobes.c#L156
fuente
Manejar SIGPIPE localmente
Por lo general, es mejor manejar el error localmente en lugar de 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 permite que mi aplicación se comunique con un accesorio externo. Cuando se produce un error de escritura, lanzo una excepción en la capa de comunicación y dejo que se forme un bloque de prueba para manejarlo allí.
Código:
El código para ignorar una señal SIGPIPE para que pueda manejarlo localmente es:
Este código evitará que se active la señal SIGPIPE, pero recibirá un error de lectura / escritura cuando intente usar el socket, por lo que deberá verificarlo.
fuente
No puede evitar que el proceso en el otro extremo de una tubería salga, y si sale antes de que termine de escribir, obtendrá una señal SIGPIPE. Si SIG_IGN la señal, entonces su escritura volverá con un error, y debe anotar y reaccionar ante ese error. Simplemente atrapar e ignorar la señal en un controlador no es una buena idea: debe tener en cuenta que la tubería ahora está desactivada 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á nuevamente, y lo intentará nuevamente, y todo el proceso podría continuar durante mucho tiempo y desperdiciar mucha energía de la CPU).
fuente
Creo que eso es correcto. Desea saber cuándo el otro extremo ha cerrado su descriptor y eso es lo que SIGPIPE le dice.
Sam
fuente
El manual de Linux dijo:
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 se produce esta señal, significa un error lógico en su programa.
fuente
O deshabilite los sigpipes según todos, o atrape e ignore el error.
Sí, use select ().
Debe seleccionar en los bits de lectura . Probablemente pueda ignorar los bits de escritura .
Cuando el extremo lejano cierra su identificador de archivo, select le indicará que hay datos listos para leer. Cuando vaya y lea eso, obtendrá 0 bytes, que es cómo el sistema operativo le dice que el identificador de archivo se ha cerrado.
El único momento en que no puede ignorar los bits de escritura es si está enviando grandes volúmenes, y existe el riesgo de que el otro extremo se acumule, lo que puede hacer que se llenen sus memorias intermedias. 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é sano o que sus datos lleguen.
Tenga en cuenta que puede obtener un sigpipe de close (), así como cuando escribe.
Cerrar elimina todos los datos almacenados en el búfer. Si el otro extremo ya se ha cerrado, entonces cerrará fallará y recibirá un letrero.
Si está utilizando TCPIP almacenado, entonces una escritura exitosa solo significa que sus datos se han puesto en cola para enviar, no significa que se hayan enviado. Hasta que cierre correctamente, no sabe que sus datos han sido enviados.
Sigpipe te dice que algo salió mal, no te dice qué o qué debes hacer al respecto.
fuente
En un sistema POSIX moderno (es decir, Linux), puede usar la
sigprocmask()
función.Si desea restaurar el estado anterior más tarde, asegúrese de guardar el
old_state
lugar seguro. Si llama a esa función varias veces, debe usar una pila o guardar solo la primera o la últimaold_state
... o tal vez tener una función que elimine una señal bloqueada específica.Para más información lea la página del manual .
fuente
sigprocmask
se agrega al conjunto de señales bloqueadas, por lo que puede hacer todo esto con una sola llamada.old_state
para poder restaurarlo más tarde, si así lo decido. Si sabe que no necesitará restaurar el estado, no hay necesidad de leerlo y almacenarlo de esta manera.sigprocmask()
leer el estado anterior, la llamada parasigaddset
modificarlo, la segunda llamada parasigprocmask()
escribirlo. Puede eliminar la primera llamada, inicializarsigemptyset(&set)
y cambiar la segunda llamada asigprocmask(SIG_BLOCK, &set, &old_state)
.