12

Cuando lee un socket TCP cerrado, recibe un error regular, es decir, devuelve 0 indicando EOF o -1 y un código de error en errno que se puede imprimir con perror.¿Por qué escribir un socket TCP cerrado es peor que leer uno?

Sin embargo, cuando escribe un socket TCP cerrado, el sistema operativo envía SIGPIPE a su aplicación que terminará la aplicación si no se detecta.

¿Por qué escribir el socket TCP cerrado es peor que leerlo?

+0

Hay algo más que sucede aquí que es bastante sutil: una conexión TCP puede estar medio cerrada, lo que significa que un lado ha cerrado el socket (enviado un paquete FIN), pero el otro lado todavía tiene datos para enviar. Si va a rooting en este nivel, lea: http://superuser.com/questions/298919/what-is-tcp-half-open-connection-and-tcp-half-closed-connection – rbp

Respuesta

12

+1 a Greg Hewgill para dirigir mi proceso de pensamiento en la dirección correcta para encontrar la respuesta.

El verdadero motivo de SIGPIPE en ambos sockets y tuberías es el idioma/patrón de filtro que se aplica a las E/S típicas en los sistemas Unix.

Comenzando con las tuberías. Los programas de filtro como grep normalmente escriben en STDOUT y leen desde STDIN, que pueden ser redirigidos por el shell a una tubería. Por ejemplo:

cat someVeryBigFile | grep foo | doSomeThingErrorProne 

La cáscara cuando se bifurca y luego de estos programas exec probablemente utiliza la llamada dup2 sistema para redirigir STDIN, STDOUT y STDERR a los tubos apropiados.

Como el programa de filtro grep no sabe y no tiene forma de saber que su salida ha sido redirigida, la única forma de decirle que deje de escribir en una tubería rota si doSomeThingErrorProne falla es con una señal desde los valores de retorno de escribe a STDOUT rara vez se comprueban.

El análogo con sockets sería el servidor inetd tomando el lugar del shell.

Como ejemplo, supongo que puede convertir grep en un servicio de red que opera en sockets TCP. Por ejemplo, con inetd si usted quiere tener un servidor grep en TCP puerto 8000 y luego añadir esto a /etc/services:

grep  8000/tcp # grep server 

A continuación, añadir esto a /etc/inetd.conf:

grep stream tcp nowait root /usr/bin/grep grep foo 

Enviar SIGHUP a inetd y conectarse al puerto 8000 con telnet. Esto debería causar inetd para tenedor, duplicar el socket en STDIN, STDOUT y STDERR y luego ejecutar grep con foo como argumento. Si comienza a escribir líneas en telnet grep hará eco de esas líneas que contienen foo.

Ahora reemplace telnet con un programa llamado ticker que, por ejemplo, escribe una secuencia de cotizaciones de acciones en tiempo real en STDOUT y obtiene comandos en STDIN.Alguien hace telnets al puerto 8000 y escribe "start java" para obtener presupuestos para Sun Microsystems. Luego se levantan y van a almorzar. telnet inexplicablemente se cuelga. Si no hubiera SIGPIPE para enviar, entonces ticker seguiría enviando cotizaciones para siempre, sin saber que el proceso en el otro extremo se había estrellado, y desperdiciando innecesariamente los recursos del sistema.

10

Por lo general, si está escribiendo en un socket, esperaría que el otro extremo esté escuchando. Esto es como una llamada telefónica: si habla, no esperaría que la otra parte simplemente cuelgue la llamada.

Si está leyendo desde un socket, entonces está esperando que el otro extremo (a) le envíe algo, o (b) cierre el socket. La situación (b) ocurriría si acaba de enviar algo como un comando QUIT al otro extremo.

+0

Pero eso realmente no me dice por qué 'write' o' send' no pueden simplemente devolver un error directamente de la misma forma que 'read' o' recv' do. ¿Por qué golpear la aplicación en la cabeza con 'SIGPIPE' así? Tiene que haber una razón más profunda para una respuesta tan extrema por parte del sistema operativo. Digamos que tengo un socket que acaba de recibir un 'RST'. Si 'lo leo' obtengo -1 con ECONNRESET, ¿por qué no obtener lo mismo cuando escribo? En ambos casos, espero participar en E/S consensuadas y no obtener lo que esperaba. –

+6

@Robert: el caso de uso típico para entrada y salida de tubería en Unix fue históricamente para programas de "filtro", que leen desde una tubería de entrada y escriben a una tubería de salida (el programa 'grep' es un ejemplo). Para hacer que dicho filtro finalice inmediatamente cuando la salida ya no se escucha, el comportamiento predeterminado de la señal 'SIGPIPE' se configuró para terminar el programa. Sin esto, el filtro continuaría escribiendo en la salida hasta que se agote su entrada (lo que podría ser un tiempo). –

+2

Dime si esto suena bien: la verdadera razón de 'SIGPIPE' es que los programas de filtro como grep normalmente escriben en' STDOUT', que puede ser redirigido por el shell a un conducto. Dado que el programa de filtro no sabe y no tiene forma de saber que su salida ha sido redirigida, la única forma de decirle que deje de escribir en una tubería rota es con una señal ya que los valores de retorno de las escrituras en 'STDOUT' rara vez se verifican . El análogo con sockets sería 'inetd' aceptando una conexión, generando el servidor y' dup2' ing el socket en 'STDIN',' STDOUT', 'STDERR'! –

3

Creo que una gran parte de la respuesta es 'para que un socket se comporte de manera similar a un clásico Unix (anónimo)'. Aquellos también exhiben el mismo comportamiento: atestigua el nombre de la señal.

Entonces, entonces es razonable preguntarse por qué las tuberías se comportan de esa manera. La respuesta de Greg Hewgill da un resumen de la situación.

Otra forma de verlo es: ¿cuál es la alternativa? ¿Debería una 'lectura()' en una tubería sin escritor dar una señal SIGPIPE? El significado de SIGPIPE tendría que cambiar de 'escribir en una tubería sin que nadie lo lea', por supuesto, pero eso es trivial. No hay una razón particular para pensar que sería mejor; La indicación de EOF (cero bytes para leer, cero bytes leídos) es una descripción perfecta del estado de la tubería, por lo que el comportamiento de lectura es bueno.

¿Qué pasa con 'write()'? Bueno, una opción sería devolver la cantidad de bytes escritos: cero. Pero esa no es una buena idea; implica que el código debería volver a intentarlo y quizás se enviarían más bytes, lo que no será el caso. Otra opción sería un error: write() devuelve -1 y establece un error apropiado. No está claro que haya uno. EINVAL o EBADF son inexactos: el descriptor de archivo es correcto y abierto en este extremo (y debe cerrarse después de la escritura anómala); simplemente no hay nada para leerlo. EPIPE significa 'PIPE roto'; entonces, con una advertencia sobre "esto es un enchufe, no una tubería", sería el error apropiado. Probablemente sea el errno devuelto si ignora SIGPIPE. Sería factible hacer esto: simplemente devuelva un error apropiado cuando la tubería se rompe (y nunca envíe la señal). Sin embargo, es un hecho empírico que muchos programas no prestan tanta atención a dónde va su salida, y si canaliza un comando que leerá un archivo de varios gigabytes en un proceso que se cierra después de los primeros 20 KB, pero no está prestando atención al estado de sus escrituras, entonces llevará mucho tiempo terminar, y desperdiciará esfuerzo de la máquina mientras lo hace, mientras que al enviarle una señal que no está ignorando, se detendrá rápidamente - esto es definitivamente ventajoso Y puede obtener el error si lo desea. Entonces, el envío de señales tiene beneficios para el o/s en el contexto de las tuberías; y los enchufes emulan las tuberías bastante de cerca.

aparte interesante: mientras se comprueba el mensaje para SIGPIPE, me encontré con la opción de conector:

#define SO_NOSIGPIPE 0x1022 /* APPLE: No SIGPIPE on EPIPE */ 
+0

Entonces, básicamente, estás diciendo que 'SIGPIPE' existe porque muchos programadores ignoran los códigos de error en el caso de la escritura, lo que podría causar que el proceso acapare los recursos del sistema cuando en realidad no está logrando nada. O para decirlo de otra manera, las personas son más meticulosas a la hora de verificar su entrada que su salida y esa es la razón de la asimetría en 'lectura' y' escritura'. –

+0

@Robert: sí, básicamente. La gente tiende a escribir su código en el supuesto de que el dispositivo de salida no desaparecerá o se quedará sin espacio. Cuando la salida es una tubería y el programa receptor deja de leer antes del final de la salida, es importante asegurarse de que el programa de escritura preste atención. Y este es un mecanismo simple que hace que los programas sean más sencillos de escribir. –

+0

Entonces, ¿hubo un momento anterior a 'SIGPIPE'? Como dice que esto es en cierta medida el resultado del mal comportamiento del usuario/programador, una vez hubo una versión de Unix que devolvió un error al escribir en un conducto cerrado y luego lo cambiaron para devolver una señal, o fue 'SIGPIPE 'allí desde el principio en previsión del mal comportamiento? –

7

Piense en la toma como una tubería grande de datos entre el emisor y el proceso de recepción. Ahora imagine que la tubería tiene una válvula que está cerrada (la conexión del enchufe está cerrada).

Si está leyendo desde el zócalo (tratando de sacar algo de la tubería), no hay nada malo en tratar de leer algo que no está allí; simplemente no obtendrá ningún dato. De hecho, puedes, como dijiste, obtener un EOF, que es correcto, ya que no hay más datos para leer.

Sin embargo, escribiendo en esta conexión cerrada es otra cosa. Los datos no se procesarán, y es posible que termine dejando caer alguna comunicación importante en el piso. (No puede enviar agua por una tubería con una válvula cerrada; si lo intenta, algo probablemente explotará en algún lugar, o, como mínimo, la contrapresión rociará agua por todas partes). Es por eso que hay un sistema más potente herramienta para alertarlo sobre esta condición, a saber, la señal SIGPIPE.

Siempre puede ignorar o bloquear la señal, pero lo hace bajo su propio riesgo.

Cuestiones relacionadas