cerrar vs socket de apagado?

En C, entendí que si cerramos un socket, significa que el socket se destruirá y puede reutilizarse más adelante.

¿Qué tal el cierre? La descripción dice que cierra la mitad de una conexión dúplex a ese socket. Pero ¿se destruirá ese socket como close llamada de sistema close ?

Esto se explica en la guía de red de Beej. shutdown es una forma flexible de bloquear la comunicación en una o ambas direcciones. Cuando el segundo parámetro es SHUT_RDWR , bloqueará el envío y la recepción (como close ). Sin embargo, close es la forma de destruir un socket.

Con el shutdown , aún podrá recibir los datos pendientes del interlocutor ya enviado (gracias a Joey Adams por notar esto).

Ninguna de las respuestas existentes le dice a la gente cómo el shutdown y el close funcionan a nivel del protocolo TCP, por lo que vale la pena agregar esto.

Una conexión TCP estándar finaliza mediante la finalización en 4 direcciones:

  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 finalizó 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, shutdown enví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 puede recibir datos. Una vez que esto sucedió, su Receive obtendrá 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 los datos.

Por otro lado, si llamas close mientras la conexión aún está activa (el otro lado aún está activo y es posible que también tengas 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 de DOS?), Puede cerrar el socket de inmediato.

Mi opinión sobre las reglas sería:

  1. Considere shutdown antes de close cuando sea posible
  2. Si terminó de recibir (0 datos de tamaño recibidos) antes de que decidiera apagar, cierre la conexión después de finalizar 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 la conexión. enchufe.
  4. En cualquier caso, si se produce otro error (por ejemplo, tiempo de espera), simplemente cierre el conector.

Implementaciones ideales para SHUT_RD y SHUT_WR

Los siguientes no han sido probados, confíe bajo su propio riesgo. Sin embargo, creo que esta es una forma razonable y práctica de hacer las cosas.

Si la stack TCP recibe un apagado con SHUT_RD solamente, marcará esta conexión ya que no se esperan más datos. Cualquier solicitud de read pendiente y posterior (independientemente del hilo en el que se encuentren) se devolverá con un resultado de tamaño cero. Sin embargo, la conexión aún está activa y se puede usar: aún puede recibir datos OOB, por ejemplo. Además, el sistema operativo eliminará todos los datos que reciba para esta conexión. Pero eso es todo, no se enviarán paquetes al otro lado.

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

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

close() terminará ambas direcciones en una conexión TCP. Algunas veces quiere decirle al otro extremo 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 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án bifurcando, la limpieza ocurre solo después de que el conteo de referencia cae a 0. Con shutdown() uno puede iniciar una secuencia de cierre de TCP normal ignorando el conteo de referencia.

Los parámetros son los siguientes:

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

int how puede ser:

SHUT_RD o 0 Recibe más no están permitidos

SHUT_WR o 1 envíos adicionales no están permitidos

SHUT_RDWR o 2 envíos y SHUT_RDWR adicionales no están permitidos

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 msdn donde explican sobre el apagado, las opciones persistentes, 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 al nivel de TCP y use close para liberar los recursos utilizados por las estructuras de datos de socket en su proceso. Si no ha emitido una secuencia de apagado explícito en el momento en que llama cierra, entonces se inicia uno por usted.

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

En otros sistemas operativos (al menos OSX), me pareció que llamar a close() era suficiente para que connect() fallara.

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

Cerca

Cuando haya terminado de usar un socket, puede simplemente cerrar su descriptor de archivo con close; Si todavía hay datos en espera de ser transmitidos a través de la conexión, normalmente el cierre 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 cerrar solo la recepción o transmisión en una conexión llamando al apagado.

La función de apagado apaga la conexión del socket. Su argumento cómo especifica qué acción realizar: 0 Deje de recibir datos para este socket. Si llegan más datos, rechazarlo. 1 Deja de intentar transmitir datos desde este socket. Deseche cualquier información que esté esperando ser enviada. Deja de buscar el reconocimiento de los datos ya enviados; no lo retransmita si se pierde. 2 Detenga tanto la recepción como la transmisión.

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

en mi prueba.

close enviará paquete de aleta y destruirá fd inmediatamente cuando el socket no se comparte con otros procesos

shutdown SHUT_RD , el proceso aún puede recuperar datos del socket, pero recv devolverá 0 si el buffer TCP está vacío. Después de que el par envíe más datos, recv devolverá los datos nuevamente.

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

shutdown SHUT_RDWR (igual para usar tanto SHUT_RD como SHUT_WR ) enviará el primer paquete si el par envía más datos.