web-dev-qa-db-fra.com

TCP option SO_LINGER (zéro) - lorsqu'elle est requise

Je pense comprendre le sens formel de l'option. Dans certains codes hérités que je gère maintenant, l'option est utilisée. Le client se plaint de la TVD en réponse à FIN de son côté lors de la connexion rapprochée de son côté.

Je ne suis pas sûr de pouvoir l'enlever en toute sécurité, car je ne comprends pas quand il devrait être utilisé.

Pouvez-vous donner un exemple du moment où l'option serait requise?

83
dimba

La raison typique pour définir un SO_LINGER _ timeout of zero permet d'éviter un grand nombre de connexions dans le TIME_WAIT _ state, liant toutes les ressources disponibles sur un serveur.

Quand une connexion TCP est fermée proprement, l'extrémité qui a initié la fermeture ("fermeture active") se termine avec la connexion assise dans TIME_WAIT pendant plusieurs minutes. Donc, si votre protocole est un protocole où serveur initie la fermeture de la connexion et implique un très grand nombre de connexions éphémères, il pourrait être sujet à ce problème.

Ce n'est pas une bonne idée, cependant - TIME_WAIT existe pour une raison (pour s'assurer que les paquets parasites provenant d'anciennes connexions n'interfèrent pas avec les nouvelles connexions). C'est une meilleure idée de redéfinir votre protocole en un protocole où le client initie la fermeture de la connexion, si possible.

71
caf

Pour ma suggestion, veuillez lire la dernière section: “Quand utiliser SO_LINGER avec timeout 0” .

Avant d’y arriver, une petite conférence sur:

  • Arrêt normal TCP
  • TIME_WAIT
  • FIN, ACK et RST

Arrêt normal TCP

La séquence de terminaison normale TCP ressemble à ceci (simplifiée):

Nous avons deux pairs: A et B

  1. A appelle close()
    • A envoie FIN à B
    • A passe à l'état FIN_WAIT_1
  2. B reçoit FIN
    • B envoie ACK à A
    • B passe à l'état CLOSE_WAIT
  3. A reçoit ACK
    • A passe à l'état FIN_WAIT_2
  4. B appelle close()
    • B envoie FIN à A
    • B passe à l'état LAST_ACK
  5. A reçoit FIN
    • A envoie ACK à B
    • A passe à l'état TIME_WAIT
  6. B reçoit ACK
    • B passe à l'état CLOSED - c'est-à-dire est supprimé des tables de socket

TEMPS D'ATTENTE

Ainsi, l’homologue qui initie la terminaison - c’est-à-dire appelle close() en premier - se retrouvera dans l’état TIME_WAIT.

Pour comprendre pourquoi l'état TIME_WAIT Est notre ami, veuillez lire la section 2.7 de la troisième édition de "UNIX Network Programming" de Stevens et al. (Page 43).

Cependant, il peut y avoir un problème avec beaucoup de sockets à l'état TIME_WAIT Sur un serveur car cela pourrait éventuellement empêcher de nouvelles connexions d'être acceptées.

Pour contourner ce problème, j'ai vu plusieurs personnes suggérer de définir l'option de socket SO_LINGER avec un délai d'expiration égal à 0 avant d'appeler close(). Cependant, c'est une mauvaise solution car cela provoque la connexion de la connexion TCP avec une erreur.

A la place, concevez votre protocole d’application pour que la terminaison de la connexion soit toujours lancée du côté client. Si le client sait toujours quand il a lu toutes les données restantes, il peut lancer la séquence de terminaison. Par exemple, un navigateur sait à partir de l'en-tête HTTP Content-Length Quand il a lu toutes les données et peut initier la fermeture. (Je sais que dans HTTP 1.1, il restera ouvert pendant un moment en vue d'une réutilisation éventuelle, puis le fermera.)

Si le serveur doit fermer la connexion, configurez le protocole d'application pour qu'il demande au client d'appeler close().

Quand utiliser SO_LINGER avec timeout 0

Encore une fois, selon "UNIX Network Programming", troisième édition, pages 202-203, définir SO_LINGER Avec un délai d'attente supérieur à 0 avant d'appeler close() entraîne la séquence de terminaison normale pas à initier.

Au lieu de cela, l’homologue définissant cette option et appelant close() enverra un RST (réinitialisation de la connexion) indiquant une condition d’erreur et la manière dont elle sera perçue à l’autre bout. Vous verrez généralement des erreurs du type "Connexion réinitialisée par un homologue".

Par conséquent, dans la situation normale, il est très déconseillé de définir SO_LINGER Avec un délai d'expiration égal à 0 avant d'appeler close() - désormais appelé fermeture abortive - dans une application serveur.

Cependant, certaines situations le justifient quand même:

  • Si le client de votre application serveur se comporte mal (délai expiré, données non valides, etc.), une fermeture avortée a du sens pour éviter de rester bloqué dans CLOSE_WAIT Ou de se retrouver dans l'état TIME_WAIT.
  • Si vous devez redémarrer votre application serveur qui compte actuellement des milliers de connexions client, vous pouvez envisager de définir cette option de socket pour éviter des milliers de sockets de serveur dans TIME_WAIT (Lors de l'appel de close() à partir de la fin du serveur): cela pourrait empêcher le serveur d'obtenir des ports disponibles pour les nouvelles connexions client après leur redémarrage.
  • À la page 202 du livre susmentionné, il est spécifiquement indiqué: "Certaines circonstances justifient l’utilisation de cette fonctionnalité pour envoyer une fermeture avortée. Par exemple, un serveur de terminal RS-232 pourrait être bloqué indéfiniment dans CLOSE_WAIT. transmettre les données à un port bloqué, mais réinitialiserait correctement le port bloqué s'il obtenait un RST pour ignorer les données en attente. "

Je recommanderais this long article qui, je crois, donne une très bonne réponse à votre question.

168
mgd

Lorsque linger est activé mais que le délai d’attente est de zéro, la pile TCP n’attend pas l’envoi des données en attente avant de fermer la connexion. Des données risquent d’être perdues pour cette raison mais en définissant linger de cette manière. Nous acceptons cela et demandons que la connexion soit réinitialisée immédiatement plutôt que fermée normalement, ce qui entraîne l'envoi d'une RST plutôt que le FIN habituel.

Merci à EJP pour son commentaire, voir ici pour plus de détails.

16
Len Holgate

Que vous puissiez ou non supprimer le reste de votre code en toute sécurité dépend du type de votre application: est-ce un "client" (ouverture TCP et la fermeture active en premier)) ou est-ce un " serveur “(en écoutant un TCP ouvrir et le fermer après que l'autre côté a initié la fermeture)??

Si votre application a la saveur d'un "client" (en commençant par la fermeture) ET si vous établissez et fermez un grand nombre de connexions vers différents serveurs (par exemple, lorsque votre application est une application de surveillance supervisant l'accessibilité d'un grand nombre de serveurs différents), votre application a le problème que toutes vos connexions client sont bloquées dans l'état TIME_WAIT. Ensuite, je vous recommanderais de réduire le délai d’attente à une valeur inférieure à la valeur par défaut afin de toujours arrêter en douceur tout en libérant les ressources de connexion client plus tôt. Je ne définirais pas le délai d'attente à 0, car 0 ne s'arrête pas normalement avec FIN mais est abortif avec RST.

Si votre application a le style d'un "client" et doit extraire une quantité énorme de petits fichiers du même serveur, vous ne devez pas initier une nouvelle connexion TCP par fichier et vous retrouver dans une Un nombre considérable de connexions client dans TIME_WAIT, mais gardez la connexion ouverte et récupérez toutes les données sur la même connexion. L'option Linger peut et doit être supprimée.

Si votre application est un "serveur" (seconde à la suite de la fermeture de l’homologue), à ​​la fermeture de (), votre connexion est interrompue de manière transparente et les ressources sont libérées car vous ne passez pas à l’état TIME_WAIT. Linger ne doit pas être utilisé. Mais si votre application serveur dispose d'un processus de supervision détectant les connexions ouvertes inactives inactives pendant une longue période ("long" doit être défini), vous pouvez arrêter cette connexion inactive de votre côté - voyez-la comme une sorte de gestion des erreurs - avec un arrêt inopiné. Ceci est fait en réglant le délai d'attente sur 0. close () enverra alors un RST au client, lui disant que vous êtes en colère :-)

5
Grandswiss