cierre vs toma de apagado?

214

En C, entendí que si cerramos un socket, significa que el socket será destruido y puede reutilizarse más tarde.

¿Qué tal el apagado? La descripción dice que cierra la mitad de una conexión dúplex a ese zócalo. ¿Pero ese zócalo será destruido comoclose llamada al sistema?

tshepang
fuente
No se puede reutilizar más tarde. Está cerrado. Terminado. Hecho.
Marqués de Lorne

Respuestas:

191

Esto se explica en la guía de redes de Beej. shutdownEs una forma flexible de bloquear la comunicación en una o ambas direcciones. Cuando el segundo parámetro es SHUT_RDWR, bloqueará tanto el envío como la recepción (como close). Sin embargo, closees la forma de destruir realmente un zócalo.

Con shutdown, aún podrá recibir datos pendientes que el igual ya envió (gracias a Joey Adams por notar esto).

Matthew Flaschen
fuente
23
Tenga en cuenta que, incluso si cierra () un socket TCP, no será necesariamente reutilizable inmediatamente de todos modos, ya que estará en un estado TIME_WAIT mientras el sistema operativo se asegura de que no haya paquetes pendientes que puedan confundirse como información nueva si debían reutilizar de inmediato ese zócalo para otra cosa.
alesplin
91
La gran diferencia entre apagar y cerrar en un socket es el comportamiento cuando el socket es compartido por otros procesos. Un apagado () afecta a todas las copias del socket mientras que close () afecta solo al descriptor de archivo en un proceso.
Zan Lynx
55
Una razón por la que puede querer usar shutdownambas direcciones pero no closees si hizo FILEreferencia al socket usando fdopen. Si closeutiliza el socket, a un archivo recién abierto se le puede asignar la misma fd, y el uso posterior de FILEeste leerá / escribirá en el lugar incorrecto, lo que podría ser muy malo. Si solo shutdown, el uso posterior de la FILEsolo dará errores hasta que fclosese llame.
R .. GitHub DEJA DE AYUDAR A ICE
25
El comentario sobre TIME_WAIT es incorrecto. Eso se aplica a los puertos, no a los enchufes. No puede reutilizar enchufes.
Marqués de Lorne
48
-1. Tanto esta publicación como el enlace omiten una razón conceptual importante para usar shutdown: para señalar EOF al igual y aún poder recibir datos pendientes que el igual envió.
Joey Adams
144

Ninguna de las respuestas existentes le dice a la gente cómo shutdowny closefunciona a nivel de protocolo TCP, por lo que vale la pena agregar esto.

Una conexión TCP estándar se termina con una finalización de 4 vías:

  1. Una vez que un participante no tiene más datos para enviar, envía un paquete FIN al otro
  2. La otra parte devuelve un ACK para el FIN.
  3. Cuando la otra parte también finaliza la transferencia de datos, envía otro paquete FIN
  4. El participante inicial devuelve un ACK y finaliza la transferencia.

Sin embargo, hay otra forma "emergente" de cerrar una conexión TCP:

  1. Un participante envía un paquete RST y abandona la conexión.
  2. El otro lado recibe un RST y luego abandona la conexión también

En mi prueba con Wireshark, con las opciones de socket predeterminadas, shutdownenvía un paquete FIN al otro extremo, pero es todo lo que hace. Hasta que la otra parte le envíe el paquete FIN, aún podrá recibir datos. Una vez que esto sucedió, Receiveobtendrá un resultado de tamaño 0. Entonces, si usted es el primero en cerrar "enviar", debe cerrar el socket una vez que haya terminado de recibir datos.

Por otro lado, si llama closemientras la conexión aún está activa (el otro lado todavía está activo y es posible que también tenga datos no enviados en el búfer del sistema), se enviará un paquete RST al otro lado. Esto es bueno para los errores. Por ejemplo, si cree que la otra parte proporcionó datos incorrectos o se negó a proporcionar datos (¿ataque DOS?), Puede cerrar el socket de inmediato.

Mi opinión sobre las reglas sería:

  1. Considere shutdownantes closecuando sea posible
  2. Si terminó de recibir (datos de tamaño 0 recibidos) antes de decidir cerrar, cierre la conexión después de que finalice el último envío (si lo hubiera).
  3. Si desea cerrar la conexión normalmente, apague la conexión (con SHUT_WR, y si no le importa recibir datos después de este punto, también con SHUT_RD), y espere hasta que reciba datos de tamaño 0, y luego cierre el enchufe.
  4. En cualquier caso, si ocurrió algún otro error (tiempo de espera, por ejemplo), simplemente cierre el zócalo.

Implementaciones ideales para SHUT_RD y SHUT_WR

Lo siguiente no ha sido probado, confíe bajo su propio riesgo. Sin embargo, creo que esta es una forma razonable y práctica de hacer las cosas.

Si la pila TCP recibe un apagado solo con SHUT_RD, marcará esta conexión como no se esperan más datos. Las solicitudes pendientes y posteriores read(independientemente de la secuencia en la que se encuentren) se devolverán con un resultado de tamaño cero. Sin embargo, la conexión sigue activa y utilizable; por ejemplo, aún puede recibir datos OOB. Además, el sistema operativo eliminará cualquier dato que reciba para esta conexión. Pero eso es todo, no se enviarán paquetes al otro lado.

Si la pila TCP recibe un apagado solo con SHUT_WR, marcará esta conexión ya que no se pueden enviar más datos. Se finalizarán todas las solicitudes de escritura pendientes, pero las solicitudes de escritura posteriores fallarán. Además, se enviará un paquete FIN a otro lado para informarles que no tenemos más datos para enviar.

Motor de tierra
fuente
2
"si llama cerca mientras la conexión aún está viva" es tautológico y no hace que se envíe un RST. (1) no es necesario. En (4), los tiempos de espera no son necesariamente fatales para la conexión y no siempre indican que puede cerrarla.
Marqués de Lorne
44
@EJP No, no es tautológico. Puede shutdown()la conexión y luego ya no está vivo. Aún tienes el descriptor de archivo. Todavía puede recv()desde el búfer receptor. Y aún necesita llamar close()para disponer del descriptor de archivo.
Pavel Šimerda
Esta es la mejor respuesta con respecto al formato de cable. Me interesarían más detalles sobre cómo cerrar con SHUT_RD. No hay señalización TCP para no esperar más datos, ¿verdad? ¿No hay solo FIN para señalar que no se envían más datos?
Pavel Šimerda
3
@ PavelŠimerda Sí TCP no señala por no esperar más datos. Esto debe considerarse en los protocolos de alto nivel. En mi opinión, esto en general no es necesario. Puede cerrar su puerta, pero no puede evitar que la gente ponga regalos delante de la puerta. Esta es SU decisión, no la tuya.
Earth Engine
1
@EJP ¿Tienes algún argumento real? De lo contrario no estoy interesado.
Pavel Šimerda
35

Hay algunas limitaciones con close()eso que se pueden evitar si se usa en su shutdown()lugar.

close()terminará ambas direcciones en una conexión TCP. A veces desea decirle al otro punto final que ha terminado de enviar datos, pero aún desea recibir datos.

close()disminuye el recuento de referencias de descriptores (mantenido en la entrada de la tabla de archivos y cuenta el número de descriptores actualmente abiertos que se refieren a un archivo / socket) y no cierra el socket / archivo si el descriptor no es 0. Esto significa que si está bifurcando, la limpieza ocurre solo después de que el recuento de referencia cae a 0. Con shutdown()uno se puede iniciar la secuencia de cierre TCP normal ignorando el recuento de referencia.

Los parámetros son los siguientes:

int shutdown(int s, int how); // s is socket descriptor

int how puede ser:

SHUT_RDo 0 Más recibidos no están permitidos

SHUT_WRo No 1 se permiten más envíos

SHUT_RDWRo No 2 se permiten más envíos y recibos

Milán
fuente
8
Las dos funciones son para propósitos completamente diferentes. El hecho de que el cierre final de un socket inicie una secuencia de apagado si aún no se ha iniciado no cambia el hecho de que close es para limpiar las estructuras de datos del socket y el apagado es para iniciar una secuencia de apagado de nivel tcp.
Len Holgate el
25
No puedes 'usar el apagado en su lugar'. Puedes usarlo también. pero debes cerrar el zócalo alguna vez.
Marqués de Lorne
15

Esto puede ser específico de la plataforma, de alguna manera lo dudo, pero de todos modos, la mejor explicación que he visto está aquí en esta página de msdn donde explican sobre el apagado, las opciones de retraso, el cierre del socket y las secuencias generales de terminación de la conexión.

En resumen, use shutdown para enviar una secuencia de apagado a nivel TCP y use close para liberar los recursos utilizados por las estructuras de datos del socket en su proceso. Si no ha emitido una secuencia de apagado explícita para el momento en que llama al cierre, se iniciará una para usted.

Len Holgate
fuente
9

También he tenido éxito con Linux usando shutdown()un pthread para forzar a otro pthread actualmente bloqueado connect()a abortar temprano.

Bajo otros sistemas operativos (OSX al menos), encontré que llamar close()era suficiente para connect()fallar.

Toby
fuente
7

"shutdown () en realidad no cierra el descriptor de archivo, solo cambia su usabilidad. Para liberar un descriptor de socket, debe usar close ()". 1


fuente
3

Cerca

Cuando haya terminado de usar un socket, simplemente puede cerrar su descriptor de archivo con close; Si todavía hay datos esperando ser transmitidos a través de la conexión, normalmente cierra intenta completar esta transmisión. Puede controlar este comportamiento utilizando la opción de socket SO_LINGER para especificar un período de tiempo de espera; ver Opciones de socket.

Apagar

También puede apagar solo la recepción o transmisión en una conexión llamando al apagado.

La función de apagado cierra la conexión del zócalo. Su argumento cómo especifica qué acción realizar: 0 Dejar de recibir datos para este socket. Si llegan más datos, rechazarlos. 1 Deje de intentar transmitir datos desde este socket. Descarte todos los datos que esperan ser enviados. Deje de buscar el reconocimiento de los datos ya enviados; no lo retransmita si se pierde. 2 Pare tanto la recepción como la transmisión.

El valor de retorno es 0 en caso de éxito y -1 en caso de error.

Neha Agrawal
fuente
2

en mi prueba

close enviará el paquete fin y destruirá fd inmediatamente cuando el socket no se comparta con otros procesos

shutdown SHUT_RD , el proceso aún puede recibir datos del socket, pero recvdevolverá 0 si el búfer TCP está vacío. Después de que el igual envíe más datos, recvdevolverá datos nuevamente.

shutdown SHUT_WR enviará un paquete de aletas para indicar que los envíos adicionales no están permitidos. el par puede recibir datos pero recibirá 0 si su búfer TCP está vacío

shutdown SHUT_RDWR (igual que usar SHUT_RD y SHUT_WR ) enviará el primer paquete si el igual envía más datos.

simpx
fuente
Acabo de probarlo y close()envié RST en Linux en lugar de FIN.
Pavel Šimerda
¿También realmente quería decir que después de cerrar el lado de lectura, el programa recibirá más datos?
Pavel Šimerda
@ PavelŠimerda, creo que puede depender del estado tcp para enviar RST o FIN? no estoy seguro.
simpx
1. 'Después de que el igual envíe más datos, recv()devolverá datos nuevamente' no es correcto. 2. El comportamiento si el igual envía más datos después SHUT_RDdepende de la plataforma.
Marqués de Lorne el
@EJP Lo intenté yo mismo, lo siento, olvidé en qué plataforma hago esta prueba, centos o Mac. y esto es lo que obtuve
simpx