¿Cuándo se requiere la opción TCP SO_LINGER (0)?
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 sobre la conexión cerrada desde su lado.
No estoy seguro de poder eliminarlo de forma segura, ya que no entiendo cuándo se debe utilizar.
¿Puede darnos un ejemplo de cuándo sería necesaria 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
yRST
Terminación TCP normal
La secuencia de terminación TCP normal se ve así (simplificada):
Tenemos dos pares: A y B.
- una llamada
close()
- A envía
FIN
a B - A entra en
FIN_WAIT_1
estado
- B recibe
FIN
- B envía
ACK
a A - B entra en
CLOSE_WAIT
estado
- un recibe
ACK
- A entra en
FIN_WAIT_2
estado
- B llama
close()
- B envía
FIN
a A - B entra en
LAST_ACK
estado
- un recibe
FIN
- A envía
ACK
a B - A entra en
TIME_WAIT
estado
- B recibe
ACK
- B pasa al
CLOSED
estado, es decir, se elimina de las tablas de sockets.
TIEMPO DE ESPERA
Por lo tanto, el par que inicia la terminación (es decir, que llama close()
primero) terminará en ese TIME_WAIT
estado.
Para comprender por qué el TIME_WAIT
estado es nuestro amigo, lea la sección 2.7 de la tercera edición de "Programación de redes UNIX" de Stevens et al (página 43).
Sin embargo, puede ser un problema con muchos sockets en TIME_WAIT
estado en un servidor, ya que eventualmente podría impedir que se acepten nuevas conexiones.
Para solucionar este problema, he visto a muchos sugerir configurar la opción de socket SO_LINGER con tiempo de espera 0 antes de llamar 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 el protocolo de su aplicación de modo 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 terminación. Por ejemplo, un navegador sabe por el Content-Length
encabezado HTTP cuándo 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 cerrará).
Si el servidor necesita cerrar la conexión, diseñe el protocolo de aplicación para que el servidor le pida al cliente que llame close()
.
Cuándo usar SO_LINGER con tiempo de espera 0
Nuevamente, de acuerdo con la tercera edición de "Programación de red UNIX", páginas 202-203, la configuración SO_LINGER
con tiempo de espera 0 antes de la llamada close()
provocará que no se inicie la secuencia de terminación normal.
En cambio, el par que configura esta opción y llama close()
enviará un RST
(restablecimiento de conexión) que indica una condición de error y así es como se percibirá en el otro extremo. Normalmente verá errores como "Conexión restablecida por igual".
Por lo tanto, en una situación normal es realmente una mala idea establecer SO_LINGER
un tiempo de espera de 0 antes de llamar close()
(de ahora en adelante llamado cierre abortivo ) en una aplicación de servidor.
Sin embargo, ciertas situaciones justifican hacerlo de todos modos:
- Si un cliente de su aplicación de servidor se porta mal (se agota el tiempo de espera, devuelve datos no válidos, etc.), tiene sentido realizar un cierre abortivo para evitar quedarse atrapado
CLOSE_WAIT
o terminar en eseTIME_WAIT
estado. - Si debe reiniciar la aplicación de su servidor, que actualmente tiene miles de conexiones de clientes, podría considerar configurar esta opción de socket para evitar miles de sockets de servidor
TIME_WAIT
(al llamarclose()
desde el extremo del servidor), ya que esto podría impedir que el servidor obtenga puertos disponibles para nuevas conexiones de clientes. después de ser reiniciado. - En la página 202 del libro antes mencionado dice específicamente: "Existen ciertas circunstancias que justifican el uso de esta función para enviar un cierre abortivo. Un ejemplo es un servidor de terminal RS-232, que podría bloquearse para siempre al
CLOSE_WAIT
intentar entregar datos a un terminal atascado. puerto, pero restablecería correctamente el puerto atascado si consiguieraRST
descartar los datos pendientes".
Recomendaría este extenso artículo que creo que da una muy buena respuesta a su pregunta.
La razón típica para establecer un SO_LINGER
tiempo de espera de cero es evitar una gran cantidad de conexiones en el TIME_WAIT
estado, ocupando todos los recursos disponibles en un servidor.
Cuando una conexión TCP se cierra limpiamente, el extremo que inició el cierre ("cierre activo") termina con la conexión inactiva TIME_WAIT
durante varios minutos. Entonces, si su protocolo es uno en el que el servidor inicia la conexión e involucra una gran cantidad de conexiones de corta duración, entonces podría ser susceptible a este problema.
Sin embargo, esto no es una buena idea: TIME_WAIT
existe por una razón (para garantizar que los paquetes perdidos de conexiones antiguas no interfieran con las nuevas). Es una mejor idea rediseñar su protocolo a uno en el que el cliente inicie la conexión, si es posible.
Cuando la persistencia está activada 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. Se podrían perder datos debido a esto, pero al configurar la permanencia de esta manera, lo acepta y solicita que la conexión se restablezca de inmediato en lugar de cerrarse correctamente. Esto hace que se envíe un RST en lugar del FIN habitual.
Gracias a EJP por su comentario, consulte aquí para obtener más detalles.
El hecho de que pueda eliminar los restos de su código de forma segura o no depende del tipo de su aplicación: ¿es un "cliente" (que abre conexiones TCP y las cierra activamente primero) o es un "servidor" (que escucha un TCP abierto y cerrarlo después de que la otra parte inició el cierre)?
Si su aplicación tiene el tipo de "cliente" (se cierra 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 monitoreo 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 menor que el predeterminado para seguir apagándose correctamente 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 tipo de "cliente" y tiene que recuperar 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 cliente en TIME_WAIT, sino mantenga la conexión abierta y obtenga todos los datos a través de la misma conexión. La opción persistente puede y debe eliminarse.
Si su aplicación es un "servidor" (segundo cercano como reacción al cierre del par), al cerrar() su conexión se cierra correctamente y los recursos se liberan ya que no ingresa al estado TIME_WAIT. No se debe utilizar Linger. Pero si su aplicación de servidor tiene un proceso de supervisión que detecta conexiones abiertas inactivas inactivas durante un tiempo prolongado ("largo" debe definirse), puede cerrar esta conexión inactiva desde su lado (considérelo como una especie de manejo de errores) con un cierre abortivo. Esto se hace estableciendo el tiempo de espera persistente en 0. close() luego enviará un RST al cliente, diciéndole que estás enojado :-)
Acabo de ver que en el RFC de websockets (RFC 6455) , se indica explícitamente que el servidor debe llamar close()
primero al socket TCP (!)
Me quedé asombrado, ya que considero que las respuestas/publicaciones de @mgd en este hilo son de facto, y el RFC claramente va en contra de eso. Pero quizás este sería un caso en el que sería aceptable establecer un tiempo de permanencia en 0.
La conexión TCP subyacente, en la mayoría de los casos normales, DEBE ser cerrada primero por el servidor, de modo que mantenga el estado TIME_WAIT y no el cliente.
Estoy muy interesado en escuchar cualquier opinión o idea sobre esto.