fermer vs socket d'arrêt?


214

En C, j'ai compris que si nous fermons un socket, cela signifie que le socket sera détruit et pourra être réutilisé plus tard.

Et la fermeture? La description indique qu'elle ferme la moitié d'une connexion duplex à cette prise. Mais ce socket sera-t-il détruit comme closeun appel système?


Il ne peut pas être réutilisé ultérieurement. C'est fermé. Fini. Terminé.
Marquis of Lorne

Réponses:


191

Ceci est expliqué dans le guide de mise en réseau de Beej. shutdownest un moyen flexible de bloquer la communication dans une ou les deux directions. Lorsque le deuxième paramètre est SHUT_RDWR, il bloquera à la fois l'envoi et la réception (comme close). Cependant, closec'est le moyen de détruire réellement une socket.

Avec shutdown, vous pourrez toujours recevoir les données en attente que le pair a déjà envoyées (merci à Joey Adams de l'avoir noté).


23
Gardez à l'esprit, même si vous fermez () un socket TCP, il ne sera pas nécessairement réutilisable de toute façon, car il sera dans un état TIME_WAIT tandis que le système d'exploitation s'assurera qu'il n'y a pas de paquets en suspens qui pourraient être confondus en tant que nouvelles informations si vous devaient réutiliser immédiatement cette prise pour autre chose.
alesplin

91
La grande différence entre l'arrêt et la fermeture sur un socket est le comportement lorsque le socket est partagé par d'autres processus. Un shutdown () affecte toutes les copies du socket tandis que close () affecte uniquement le descripteur de fichier dans un processus.
Zan Lynx

5
Une raison pour laquelle vous souhaiterez peut-être utiliser les shutdowndeux directions, mais pas closesi vous avez fait FILEréférence au socket à l'aide fdopen. Si vous closeutilisez le socket, un nouveau fichier ouvert pourrait se voir attribuer le même fd, et l'utilisation ultérieure de FILElira / écrira au mauvais endroit, ce qui pourrait être très mauvais. Si vous venez shutdown, l'utilisation subséquente de la FILEdonnera juste des erreurs jusqu'à ce que fclosesoit appelé.
R .. GitHub STOP HELPING ICE

25
Le commentaire sur TIME_WAIT est incorrect. Cela s'applique aux ports, pas aux sockets. Vous ne pouvez pas réutiliser les sockets.
Marquis de Lorne

48
-1. Ce message et le lien omettent une raison conceptuelle importante à vouloir utiliser shutdown: pour signaler EOF au pair et toujours pouvoir recevoir les données en attente envoyées par le pair.
Joey Adams

144

Aucune des réponses existantes ne dit aux gens comment shutdownet closefonctionne au niveau du protocole TCP, il vaut donc la peine d'ajouter cela.

Une connexion TCP standard est interrompue par une finalisation à 4 voies:

  1. Une fois qu'un participant n'a plus de données à envoyer, il envoie un paquet FIN à l'autre
  2. L'autre partie renvoie un ACK pour la FIN.
  3. Lorsque l'autre partie a également terminé le transfert de données, elle envoie un autre paquet FIN
  4. Le participant initial retourne un ACK et finalise le transfert.

Cependant, il existe une autre façon "émergente" de fermer une connexion TCP:

  1. Un participant envoie un paquet RST et abandonne la connexion
  2. L'autre côté reçoit une TVD, puis abandonne également la connexion

Dans mon test avec Wireshark, avec les options de socket par défaut, shutdownenvoie un paquet FIN à l'autre extrémité, mais c'est tout ce qu'il fait. Jusqu'à ce que l'autre partie vous envoie le paquet FIN, vous pouvez toujours recevoir des données. Une fois que cela s'est produit, vous Receiveobtiendrez un résultat de taille 0. Donc, si vous êtes le premier à arrêter l'envoi, vous devez fermer le socket une fois que vous avez fini de recevoir les données.

D'un autre côté, si vous appelez closealors que la connexion est toujours active (l'autre côté est toujours actif et vous pouvez également avoir des données non envoyées dans la mémoire tampon du système), un paquet RST sera envoyé à l'autre côté. C'est bon pour les erreurs. Par exemple, si vous pensez que l'autre partie a fourni des données erronées ou a refusé de fournir des données (attaque DOS?), Vous pouvez fermer le socket immédiatement.

Mon opinion sur les règles serait:

  1. Réfléchissez shutdownavant closesi possible
  2. Si vous avez fini de recevoir (données de taille 0 reçues) avant de décider d'arrêter, fermez la connexion une fois le dernier envoi (le cas échéant) terminé.
  3. Si vous souhaitez fermer la connexion normalement, fermez la connexion (avec SHUT_WR, et si vous ne vous souciez pas de recevoir des données après ce point, avec SHUT_RD également), et attendez jusqu'à ce que vous receviez une donnée de taille 0, puis fermez le prise.
  4. Dans tous les cas, si une autre erreur s'est produite (timeout par exemple), fermez simplement le socket.

Implémentations idéales pour SHUT_RD et SHUT_WR

Les éléments suivants n'ont pas été testés, faites confiance à vos risques et périls. Cependant, je pense que c'est une façon raisonnable et pratique de faire les choses.

Si la pile TCP reçoit un arrêt avec SHUT_RD uniquement, elle doit marquer cette connexion comme plus de données attendues. Toutes les readdemandes en attente et suivantes (quel que soit le thread dans lequel elles se trouvent) seront alors renvoyées avec un résultat de taille nulle. Cependant, la connexion est toujours active et utilisable - vous pouvez toujours recevoir des données OOB, par exemple. En outre, le système d'exploitation supprimera toutes les données qu'il reçoit pour cette connexion. Mais c'est tout, aucun colis ne sera envoyé de l'autre côté.

Si la pile TCP reçoit un arrêt avec SHUT_WR uniquement, elle doit marquer cette connexion car plus aucune donnée ne peut être envoyée. Toutes les demandes d'écriture en attente seront terminées, mais les demandes d'écriture suivantes échoueront. De plus, un paquet FIN sera envoyé à un autre côté pour les informer que nous n'avons plus de données à envoyer.


2
"si vous appelez close alors que la connexion est toujours en vie" est tautologique et n'entraîne pas l'envoi d'un RST. (1) n'est pas nécessaire. Dans (4), les délais d'attente ne sont pas nécessairement fatals à la connexion et n'indiquent pas invariable que vous pouvez la fermer.
Marquis de Lorne

4
@EJP Non, ce n'est pas tautologique. Vous pouvez shutdown()la connexion et puis il n'est plus en vie. Vous avez toujours le descripteur de fichier. Vous pouvez toujours à recv()partir du tampon de réception. Et vous devez toujours appeler close()pour supprimer le descripteur de fichier.
Pavel Šimerda

C'est la meilleure réponse concernant le format de fil. Je serais intéressé par plus de détails sur l'arrêt de SHUT_RD. Il n'y a pas de signalisation TCP pour ne pas attendre plus de données, non? N'y a-t-il pas seulement FIN pour signaler de ne pas envoyer plus de données?
Pavel Šimerda

3
@ PavelŠimerda Oui TCP ne signale pas de ne pas attendre plus de données. Cela devrait être pris en compte dans les protocoles de haut niveau. À mon avis, cela n'est généralement pas nécessaire. Vous pouvez fermer votre porte, mais vous ne pouvez pas empêcher les gens de mettre des cadeaux devant la porte. C'est LEUR décision, pas la vôtre.
Earth Engine

1
@EJP Avez-vous des arguments réels? Sinon ça ne m'intéresse pas.
Pavel Šimerda

35

Il existe certaines limitations close()qui peuvent être évitées si l'on utilise à la shutdown()place.

close()mettra fin aux deux directions sur une connexion TCP. Parfois, vous voulez dire à l'autre point de terminaison que vous avez fini d'envoyer des données, mais que vous souhaitez quand même recevoir des données.

close()décrémente le nombre de références des descripteurs (conservé dans l'entrée de la table de fichiers et compte le nombre de descripteurs actuellement ouverts qui font référence à un fichier / socket) et ne ferme pas le socket / fichier si le descripteur n'est pas 0. Cela signifie que si vous bifurquez, le nettoyage ne se produit qu'après que le nombre de références est tombé à 0. Avec l' shutdown()un, on peut lancer une séquence de fermeture TCP normale en ignorant le nombre de références.

Les paramètres sont les suivants:

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

int how peut être:

SHUT_RDou les 0 autres réceptions sont interdites

SHUT_WRou d' 1 autres envois sont interdits

SHUT_RDWRou 2 encore les envois et les réceptions sont interdits


8
Les deux fonctions sont à des fins complètement différentes. Le fait que la fermeture finale d'un socket lance une séquence d'arrêt si celle-ci n'a pas déjà été lancée ne change pas le fait que la fermeture sert à nettoyer les structures de données du socket et l'arrêt consiste à lancer une séquence d'arrêt au niveau TCP.
Len Holgate

25
Vous ne pouvez pas «utiliser l'arrêt à la place». Vous pouvez également l'utiliser . mais vous devez fermer le socket un certain temps.
Marquis de Lorne

15

Cela peut être spécifique à la plate-forme, j'en doute quelque peu, mais de toute façon, la meilleure explication que j'ai vue est ici sur cette page msdn où ils expliquent l'arrêt, les options de persistance , la fermeture de socket et les séquences générales de terminaison de connexion.

En résumé, utilisez shutdown pour envoyer une séquence d'arrêt au niveau TCP et close pour libérer les ressources utilisées par les structures de données de socket dans votre processus. Si vous n'avez pas émis de séquence d'arrêt explicite au moment où vous appelez close, une est lancée pour vous.


9

J'ai également réussi sous Linux à utiliser shutdown()un pthread pour forcer un autre pthread actuellement bloqué connect()à abandonner tôt.

Sous d'autres OS (OSX au moins), j'ai trouvé l'appel close() étaient suffisants pour connect()échouer.


7

"shutdown () ne ferme pas réellement le descripteur de fichier, il modifie simplement son utilisation. Pour libérer un descripteur de socket, vous devez utiliser close ()." 1


3

Fermer

Lorsque vous avez fini d'utiliser un socket, vous pouvez simplement fermer son descripteur de fichier avec close; S'il reste des données en attente de transmission via la connexion, normalement close tente de terminer cette transmission. Vous pouvez contrôler ce comportement à l'aide de l'option de socket SO_LINGER pour spécifier un délai d'expiration; voir Options de socket.

Fermer

Vous pouvez également arrêter uniquement la réception ou la transmission sur une connexion en appelant shutdown.

La fonction d'arrêt coupe la connexion de la prise. Son argument comment spécifie l'action à effectuer: 0 Arrêtez de recevoir des données pour ce socket. Si d'autres données arrivent, rejetez-les. 1 Arrêtez d'essayer de transmettre des données depuis cette prise. Jetez toutes les données en attente d'envoi. Arrêtez de chercher l'accusé de réception des données déjà envoyées; ne le retransmettez pas s'il est perdu. 2 Arrêtez la réception et la transmission.

La valeur de retour est 0 en cas de succès et -1 en cas d'échec.


2

dans mon test.

close enverra le paquet fin et détruira fd immédiatement lorsque le socket n'est pas partagé avec d'autres processus

shutdown SHUT_RD , le processus peut toujours récupérer les données du socket, mais recvrenverra 0 si le tampon TCP est vide recv.

shutdown SHUT_WR enverra un paquet fin pour indiquer que les autres envois sont interdits. le pair peut récupérer des données mais il récupérera 0 si son tampon TCP est vide

shutdown SHUT_RDWR (égal à utiliser à la fois SHUT_RD et SHUT_WR ) enverra le premier paquet si le pair envoie plus de données.


Je viens de l'essayer et j'ai close()envoyé RST sur Linux au lieu de FIN.
Pavel Šimerda

Vouliez-vous aussi vraiment déclarer qu'après avoir arrêté la lecture, le programme recevra plus de données?
Pavel Šimerda

@ PavelŠimerda je pense que cela peut dépendre de l'état tcp pour envoyer RST ou FIN? je ne suis pas sûr.
simpx

1. «Après que les pairs ont envoyé plus de données, les données recv()seront renvoyées» n'est pas correct. 2. Le comportement si l'homologue envoie plus de données après SHUT_RDdépend de la plate-forme.
Marquis de Lorne

@EJP Je me suis essayé, désolé j'ai oublié sur quelle plateforme je fais ce test, centos ou Mac. et
voici
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.