sockets networking tcp setsockopt so-linger

sockets - Opción TCP SO_LINGER(cero): cuando es necesario



networking setsockopt (4)

Cuando el retardo está activado pero el tiempo de espera es cero, la pila TCP no espera a que se envíen los datos pendientes antes de cerrar la conexión. Es posible que se pierdan datos debido a esto, pero al establecer la demora de esta manera usted acepta esto y le pide que la conexión se restablezca inmediatamente en lugar de cerrarse con gracia. Esto hace que se envíe un RST en lugar de la FIN habitual.

Gracias a EJP por su comentario, mira here para más detalles.

Creo que entiendo el significado formal de la opción. En algún código heredado que estoy manejando ahora, se usa la opción. El cliente se queja de RST como respuesta a FIN desde su lado en la conexión cercana a su lado.

No estoy seguro de poder eliminarlo de manera segura, ya que no entiendo cuándo debería usarse.

¿Podría por favor dar un ejemplo de cuándo se requeriría la opción?


Para mi sugerencia, lea la última sección: "Cuándo usar SO_LINGER con tiempo de espera 0" .

Antes de llegar a eso, una pequeña conferencia sobre:

  • Terminación TCP normal
  • TIME_WAIT
  • FIN , ACK y RST

Terminación TCP normal

La secuencia de terminación TCP normal se ve así (simplificada):

Tenemos dos pares: A y B

  1. A llamadas close()
    • A envía FIN a B
    • A entra en el estado FIN_WAIT_1
  2. B recibe FIN
    • B envía ACK a A
    • B pasa al estado CLOSE_WAIT
  3. A recibe ACK
    • A entra en estado FIN_WAIT_2
  4. B llama close()
    • B envía FIN a A
    • B entra en estado LAST_ACK
  5. A recibe FIN
    • A envía ACK a B
    • A entra en el estado TIME_WAIT
  6. B recibe ACK
    • B pasa al estado CLOSED , es decir, se elimina de las tablas de socket

TIEMPO DE ESPERA

Entonces, el par que inicia la terminación, es decir, llama close() primero, terminará en el estado TIME_WAIT .

Para comprender por qué el estado TIME_WAIT es nuestro amigo, lea la sección 2.7 de la tercera edición de "Programación de red UNIX" de Stevens et al (página 43).

Sin embargo, puede ser un problema con muchos sockets en el estado TIME_WAIT en un servidor, ya que podría evitar que se acepten nuevas conexiones.

Para evitar este problema, he visto a muchos sugiriendo establecer la opción de socket SO_LINGER con timeout 0 antes de llamar a close() . Sin embargo, esta es una mala solución ya que hace que la conexión TCP finalice con un error.

En su lugar, diseñe su protocolo de aplicación para que la terminación de la conexión siempre se inicie desde el lado del cliente. Si el cliente siempre sabe cuándo ha leído todos los datos restantes, puede iniciar la secuencia de finalización. Como ejemplo, un navegador conoce desde el encabezado HTTP Content-Length cuando ha leído todos los datos y puede iniciar el cierre. (Sé que en HTTP 1.1 lo mantendrá abierto por un tiempo para una posible reutilización y luego lo cierra).

Si el servidor necesita cerrar la conexión, diseñe el protocolo de la aplicación para que el servidor solicite al cliente que llame a close() .

Cuándo usar SO_LINGER con timeout 0

De nuevo, de acuerdo con la "tercera edición de la página de programación de la red UNIX" 202-203, configurar SO_LINGER con timeout 0 antes de llamar a close() hará que no se inicie la secuencia de finalización normal.

En cambio, el par que establece esta opción y llama a close() enviará un RST (reset de conexión) que indica una condición de error y así es como se percibirá en el otro extremo. Por lo general, verá errores como "Restablecimiento de conexión por pares".

Por lo tanto, en la situación normal, es una mala idea establecer SO_LINGER con timeout 0 antes de llamar a close() - a partir de ahora llamado abortive close - en una aplicación de servidor.

Sin embargo, cierta situación garantiza hacerlo de todos modos:

  • Si un cliente de su aplicación de servidor se comporta mal (agota el tiempo, devuelve datos no válidos, etc.) un cierre CLOSE_WAIT tiene sentido para evitar estar atascado en CLOSE_WAIT o terminar en el estado TIME_WAIT .
  • Si debe reiniciar su aplicación de servidor que actualmente tiene miles de conexiones de cliente, puede considerar configurar esta opción de socket para evitar miles de sockets en TIME_WAIT (al llamar a close() desde el extremo del servidor) ya que esto podría evitar que el servidor obtenga puertos disponibles para nuevas conexiones de clientes después de reiniciarse.
  • En la página 202 del libro mencionado anteriormente, dice específicamente: "Hay ciertas circunstancias que justifican el uso de esta función para enviar un cierre fallido. Un ejemplo es un servidor de terminal RS-232, que puede colgar para siempre en CLOSE_WAIT tratando de entregar datos a un puerto de terminal, pero restablecería correctamente el puerto atascado si obtuviera un RST para descartar los datos pendientes ".

Recomendaría this artículo extenso, que creo que da una muy buena respuesta a su pregunta.


Si puede eliminar el retardo de su código de forma segura o no depende del tipo de su aplicación: ¿es un "cliente" (abriendo conexiones TCP y cerrándolo activamente primero) o es un "servidor" (escuchando un TCP abierto y cerrándolo después de que el otro lado inició el cierre)?

Si su aplicación tiene el sabor de un "cliente" (cerrándose primero) Y usted inicia y cierra una gran cantidad de conexiones a diferentes servidores (por ejemplo, cuando su aplicación es una aplicación de supervisión que supervisa la accesibilidad de una gran cantidad de servidores diferentes) su aplicación tiene el problema de que todas las conexiones de sus clientes están bloqueadas en el estado TIME_WAIT. Luego, recomendaría acortar el tiempo de espera a un valor más pequeño que el valor predeterminado para apagar aún con gracia, pero liberar los recursos de conexiones del cliente antes. No establecería el tiempo de espera en 0, ya que 0 no se cierra correctamente con FIN pero aborta con RST.

Si su aplicación tiene el sabor de un "cliente" y tiene que buscar una gran cantidad de archivos pequeños del mismo servidor, no debe iniciar una nueva conexión TCP por archivo y terminar en una gran cantidad de conexiones de clientes en TIME_WAIT, pero mantenga la conexión abierta y busque todos los datos en la misma conexión. La opción Linger puede y debe eliminarse.

Si su aplicación es un "servidor" (se cierra en segundo lugar como reacción al cierre de un compañero), al cerrar () su conexión se cierra correctamente y los recursos se liberan al no ingresar el estado TIME_WAIT. Linger no debe ser usado. Pero si su aplicación de servidor tiene un proceso de supervisión que detecta conexiones inactivas inactivas durante mucho tiempo (debe definirse "larga"), puede cerrar esta conexión inactiva desde su costado, véanlo como un tipo de manejo de errores, con un apagado fallido. Esto se hace estableciendo el tiempo de espera de permanencia en 0. close () enviará un RST al cliente, diciéndole que estás enojado :-)


La razón típica para establecer un tiempo de espera de SO_LINGER de cero es evitar un gran número de conexiones en el estado TIME_WAIT , bloqueando todos los recursos disponibles en un servidor.

Cuando una conexión TCP se cierra limpiamente, el final que inició el cierre ("cierre activo") termina con la conexión en TIME_WAIT durante varios minutos. Entonces, si su protocolo es uno en el que el servidor inicia el cierre de la conexión e involucra un gran número de conexiones efímeras, entonces podría ser susceptible a este problema.

Aunque esta no es una buena idea, TIME_WAIT existe por una razón (para garantizar que los paquetes perdidos de las conexiones antiguas no interfieran con las conexiones nuevas). Es una mejor idea rediseñar su protocolo a uno donde el cliente inicia la conexión cercana, si es posible.