Question Les options de socket SO_REUSEADDR et SO_REUSEPORT, en quoi diffèrent-elles? Est-ce qu'ils signifient la même chose pour tous les principaux systèmes d'exploitation?


le man pages et documentations du programmeur pour les options de socket SO_REUSEADDR et SO_REUSEPORT sont différents pour différents systèmes d'exploitation et souvent très confus. Certains systèmes d'exploitation n'ont même pas l'option SO_REUSEPORT. Le WEB regorge d'informations contradictoires sur ce sujet et vous pouvez souvent trouver des informations qui ne sont vraies que pour une implémentation de socket d'un système d'exploitation spécifique, qui ne peut même pas être explicitement mentionnée dans le texte.

Alors, comment exactement est SO_REUSEADDR différent de SO_REUSEPORT?

Sont des systèmes sans SO_REUSEPORT plus limité?

Et quel est exactement le comportement attendu si j'utilise l'un ou l'autre sur des systèmes d'exploitation différents?


524
2018-01-17 21:45


origine


Réponses:


Bienvenue dans le merveilleux monde de la portabilité ... ou plutôt le manque de portabilité. Avant de commencer à analyser ces deux options en détail et d'examiner de plus près comment les différents systèmes d'exploitation les gèrent, il convient de noter que l'implémentation du socket BSD est la mère de toutes les implémentations de socket. Fondamentalement tous les autres systèmes ont copié l'implémentation de socket BSD à un moment donné (ou au moins ses interfaces) et ont ensuite commencé à l'évoluer par eux-mêmes. Bien sûr, l'implémentation de la socket BSD a également évolué en même temps et les systèmes qui l'ont ensuite copiée ont des fonctionnalités qui manquaient dans les systèmes qui l'ont copiée plus tôt. Comprendre l'implémentation du socket BSD est la clé pour comprendre toutes les autres implémentations de socket, donc vous devriez lire à ce sujet même si vous ne vous souciez jamais d'écrire du code pour un système BSD.

Il y a quelques notions de base que vous devriez connaître avant d'examiner ces deux options. Une connexion TCP / UDP est identifiée par un tuple de cinq valeurs:

{<protocol>, <src addr>, <src port>, <dest addr>, <dest port>}

Toute combinaison unique de ces valeurs identifie une connexion. Par conséquent, deux connexions ne peuvent pas avoir les mêmes cinq valeurs, sinon le système ne pourrait plus distinguer ces connexions.

Le protocole d'une socket est défini quand une socket est créée avec le socket() fonction. L'adresse source et le port sont définis avec bind() fonction. L'adresse de destination et le port sont définis avec connect() fonction. Comme UDP est un protocole sans connexion, les sockets UDP peuvent être utilisés sans les connecter. Pourtant, il est permis de les connecter et dans certains cas très avantageux pour votre code et la conception de l'application générale. En mode sans connexion, les sockets UDP qui n'étaient pas explicitement liées lors de la première transmission des données sont automatiquement liées par le système, car une socket UDP non liée ne peut recevoir aucune donnée (réponse). La même chose est vraie pour un socket TCP non lié, il est automatiquement lié avant qu'il ne soit connecté.

Si vous liez explicitement un socket, il est possible de le lier au port 0, ce qui signifie "tout port". Puisqu'une socket ne peut pas vraiment être liée à tous les ports existants, le système devra choisir un port spécifique dans ce cas (généralement à partir d'une plage prédéfinie de ports source spécifiques au système d'exploitation). Un caractère générique similaire existe pour l'adresse source, qui peut être "n'importe quelle adresse" (0.0.0.0dans le cas de IPv4 et :: en cas d'IPv6). Contrairement à ce qui se passe avec les ports, une socket peut vraiment être liée à «n'importe quelle adresse», ce qui signifie «toutes les adresses IP source de toutes les interfaces locales». Si le socket est connecté ultérieurement, le système doit choisir une adresse IP source spécifique, car une socket ne peut pas être connectée et en même temps être liée à une adresse IP locale. En fonction de l'adresse de destination et du contenu de la table de routage, le système sélectionne une adresse source appropriée et remplace la liaison "any" par une liaison à l'adresse IP source choisie.

Par défaut, deux sockets ne peuvent pas être liés à la même combinaison d'adresse source et de port source. Tant que le port source est différent, l'adresse source est réellement non pertinente. Contraignant socketA à A:X et socketB à B:Y, où A et B sont des adresses et X et Y sont des ports, est toujours possible aussi longtemps que X != Y qui est vrai. Cependant, même si X == Y, la liaison est toujours possible tant que A != B qui est vrai. Par exemple. socketA appartient à un programme de serveur FTP et est lié à 192.168.0.1:21 et socketB appartient à un autre programme de serveur FTP et est lié à 10.0.0.1:21, les deux liaisons réussiront. Gardez à l'esprit, cependant, qu'une socket peut être liée localement à "n'importe quelle adresse". Si une socket est liée à 0.0.0.0:21, il est lié à toutes les adresses locales existantes en même temps et dans ce cas, aucun autre socket ne peut être lié au port 21, quelle que soit l'adresse IP spécifique à laquelle elle tente de se lier, 0.0.0.0 est en conflit avec toutes les adresses IP locales existantes.

Tout ce qui est dit jusqu'ici est à peu près égal pour tous les principaux systèmes d'exploitation. Les choses commencent à être spécifiques au système d'exploitation lorsque la réutilisation d'adresses entre en jeu. Nous commençons avec BSD, puisque comme je l'ai dit plus haut, c'est la mère de toutes les implémentations de socket.

BSD

SO_REUSEADDR

Si SO_REUSEADDR est activé sur un socket avant de le lier, le socket peut être lié avec succès, sauf s'il y a un conflit avec un autre socket lié à exactement la même combinaison d'adresse source et de port. Maintenant, vous pouvez vous demander comment cela est-il différent d'avant? Le mot clé est "exactement". SO_REUSEADDR modifie principalement la manière dont les adresses génériques ("toute adresse IP") sont traitées lors de la recherche de conflits.

Sans pour autant SO_REUSEADDR, contraignant socketA à 0.0.0.0:21 puis reliure socketB à 192.168.0.1:21 échouera (avec erreur EADDRINUSE), puisque 0.0.0.0 signifie "toute adresse IP locale", toutes les adresses IP locales sont donc utilisées par cette socket et ceci inclut 192.168.0.1, aussi. Avec SO_REUSEADDR ça va réussir, puisque 0.0.0.0 et 192.168.0.1 sont pas exactement la même adresse, l'un est un caractère générique pour toutes les adresses locales et l'autre est une adresse locale très spécifique. Notez que la déclaration ci-dessus est vraie quel que soit l'ordre socketA et socketB sont liés; sans pour autant SO_REUSEADDR il échouera toujours, avec SO_REUSEADDR ça va toujours réussir.

Pour vous donner une meilleure vue d'ensemble, faisons une table ici et énumérons toutes les combinaisons possibles:

SO_REUSEADDR socketA socketB Résultat
-------------------------------------------------- -------------------
  ON / OFF 192.168.0.1:21 192.168.0.1:21 Erreur (EADDRINUSE)
  ON / OFF 192.168.0.1:21 10.0.0.1:21 OK
  ON / OFF 10.0.0.1:21 192.168.0.1:21 OK
   OFF 0.0.0.0:21 192.168.1.0:21 Erreur (EADDRINUSE)
   OFF 192.168.1.0:21 0.0.0.0:21 Erreur (EADDRINUSE)
   ON 0.0.0.0:21 192.168.1.0:21 OK
   ON 192.168.1.0:21 0.0.0.0:21 OK
  ON / OFF 0.0.0.0:21 0.0.0.0:21 Erreur (EADDRINUSE)

Le tableau ci-dessus suppose que socketA a déjà été lié avec succès à l'adresse indiquée pour socketA, puis socketB est créé, soit obtient SO_REUSEADDR ensemble ou non, et est finalement lié à l'adresse donnée pour socketB. Result est le résultat de l'opération de liaison pour socketB. Si la première colonne dit ON/OFF, la valeur de SO_REUSEADDR n'est pas pertinent pour le résultat.

D'accord, SO_REUSEADDR a un effet sur les adresses génériques, bon à savoir. Pourtant, ce n'est pas seulement son effet. Il y a un autre effet bien connu qui est aussi la raison pour laquelle la plupart des gens utilisent SO_REUSEADDR dans les programmes de serveur en premier lieu. Pour l'autre utilisation importante de cette option, nous devons examiner de plus près comment fonctionne le protocole TCP.

Une socket a un tampon d'envoi et si un appel à la send() la fonction réussit, cela ne signifie pas que les données demandées ont réellement été envoyées, cela signifie simplement que les données ont été ajoutées au tampon d'envoi. Pour les sockets UDP, les données sont généralement envoyées rapidement, sinon immédiatement, mais pour les sockets TCP, il peut y avoir un délai relativement long entre l'ajout de données dans le tampon d'envoi et le fait que l'implémentation TCP envoie réellement ces données. Par conséquent, lorsque vous fermez un socket TCP, il peut toujours y avoir des données en attente dans le tampon d'envoi, qui n'ont pas encore été envoyées mais que votre code considère comme envoyées, car le send() appel réussi. Si l'implémentation TCP fermait le socket immédiatement sur votre requête, toutes ces données seraient perdues et votre code n'en serait même pas informé. TCP est dit être un protocole fiable et perdre des données comme ça n'est pas très fiable. C'est pourquoi une socket qui a encore des données à envoyer ira dans un état appelé TIME_WAIT quand vous le fermez. Dans cet état, il attendra que toutes les données en attente aient été envoyées avec succès ou jusqu'à ce qu'un délai d'expiration soit atteint, auquel cas le socket est fermé avec force.

Le temps que le noyau attendra avant de fermer le socket, qu'il ait ou non des données d'envoi en attente, s'appelle le Linger Time. le Linger Time est globalement configurable sur la plupart des systèmes et par défaut plutôt long (deux minutes est une valeur commune que vous trouverez sur de nombreux systèmes). Il est également configurable par socket en utilisant l'option socket SO_LINGER qui peut être utilisé pour rendre le délai d'attente plus court ou plus long, et même pour le désactiver complètement. Le désactiver complètement est une très mauvaise idée, car fermer un socket TCP avec élégance est un processus un peu complexe qui implique l'envoi et le retour de quelques paquets (ainsi que renvoyer ces paquets au cas où ils seraient perdus) et tout ce processus de fermeture est également limitée par le Linger Time. Si vous désactivez l'attente, votre socket peut non seulement perdre des données en attente, mais elle est également toujours fermée avec force au lieu de gracieusement, ce qui n'est généralement pas recommandé. Les détails sur la façon dont une connexion TCP est fermée avec élégance sont au-delà de la portée de cette réponse, si vous voulez en savoir plus sur, je vous recommande de jeter un oeil à cette page. Et même si vous avez désactivé persistant avec SO_LINGERSi votre processus meurt sans fermer explicitement le socket, BSD (et peut-être d'autres systèmes) persiste, ignorant ce que vous avez configuré. Cela arrivera par exemple si votre code appelle juste exit() (assez commun pour les programmes serveur minuscules et simples) ou le processus est détruit par un signal (ce qui inclut la possibilité qu'il se bloque simplement à cause d'un accès mémoire illégal). Il n'y a donc rien que vous puissiez faire pour vous assurer qu'une douille ne s'attardera jamais dans toutes les circonstances.

La question est, comment le système traite-t-il un socket en état? TIME_WAIT? Si SO_REUSEADDR n'est pas défini, une socket en état TIME_WAIT est considéré comme étant toujours lié à l'adresse source et au port et toute tentative de liaison d'un nouveau socket à la même adresse et au même port échouera jusqu'à ce que le socket soit réellement fermé, ce qui peut prendre tant Linger Time. Donc, ne vous attendez pas à ce que vous puissiez relier l'adresse source d'un socket immédiatement après l'avoir fermé. Dans la plupart des cas, cela échouera. Toutefois, si SO_REUSEADDR est défini pour le socket que vous essayez de lier, un autre socket lié à la même adresse et le port dans l'état TIME_WAIT est simplement ignoré, après tout c'est déjà "à moitié mort", et votre socket peut se lier exactement à la même adresse sans aucun problème. Dans ce cas, il ne joue aucun rôle que l'autre socket peut avoir exactement la même adresse et le même port. Notez que la liaison d'une socket à exactement la même adresse et le port d'une socket mourante dans TIME_WAIT L'état peut avoir des effets secondaires inattendus et généralement indésirables dans le cas où l'autre socket est toujours "au travail", mais cela dépasse le cadre de cette réponse et, heureusement, ces effets secondaires sont plutôt rares dans la pratique.

Il y a une dernière chose que vous devriez savoir à propos de SO_REUSEADDR. Tout ce qui est écrit ci-dessus fonctionnera tant que la socket à laquelle vous voulez vous connecter a une réutilisation d'adresse activée. Il n'est pas nécessaire que l'autre socket, celui qui est déjà lié ou soit dans un TIME_WAIT état, a également eu ce drapeau quand il était lié. Le code qui décide si la liaison réussira ou échoue inspecte uniquement le SO_REUSEADDR drapeau de la prise alimentée dans le bind() appel, pour toutes les autres prises inspectées, ce drapeau n'est même pas regardé.

SO_REUSEPORT

SO_REUSEPORT est ce que la plupart des gens attendraient SO_REUSEADDR être. Fondamentalement, SO_REUSEPORT vous permet de lier un nombre arbitraire de sockets à exactement la même adresse source et le port aussi longtemps que tout les prises antérieures obligatoires avaient également SO_REUSEPORT mis avant qu'ils ont été liés. Si le premier socket lié à une adresse et un port n'a pas SO_REUSEPORT ensemble, aucun autre socket peut être lié à exactement la même adresse et le port, peu importe si cette autre socket a SO_REUSEPORT définir ou non, jusqu'à ce que la première socket libère à nouveau sa liaison. Contrairement à en cas de SO_REUESADDR la gestion du code SO_REUSEPORT ne vérifie pas seulement que la socket actuellement connectée SO_REUSEPORT ensemble, mais il va également vérifier que le socket avec une adresse et un port conflictuel avait SO_REUSEPORT définir quand il était lié.

SO_REUSEPORT n'implique pas SO_REUSEADDR. Cela signifie que si une socket n'a pas SO_REUSEPORT définir quand il était lié et une autre prise a SO_REUSEPORT défini quand il est lié exactement à la même adresse et le port, la liaison échoue, ce qui est attendu, mais il échoue également si l'autre socket est déjà en train de mourir et est en TIME_WAIT Etat. Pour pouvoir lier une socket aux mêmes adresses et au même port qu'une autre socket TIME_WAIT état nécessite soit SO_REUSEADDR être réglé sur cette prise ou SO_REUSEPORT doit avoir été défini à la fois prises avant de les lier. Bien sûr, il est permis de définir les deux, SO_REUSEPORT et SO_REUSEADDR, sur une prise

Il n'y a pas beaucoup plus à dire à propos de SO_REUSEPORT autre que cela, il a été ajouté plus tard que SO_REUSEADDR, c'est pourquoi vous ne le trouverez pas dans de nombreuses implémentations de socket d'autres systèmes, qui "bifurquaient" le code BSD avant que cette option soit ajoutée, et qu'il n'y avait aucun moyen de lier deux sockets exactement à la même adresse socket dans BSD. option.

Connect () Retour EADDRINUSE?

La plupart des gens savent que bind() peut échouer avec l'erreur EADDRINUSECependant, lorsque vous commencez à jouer avec la réutilisation d'adresses, vous pouvez rencontrer la situation étrange connect() échoue avec cette erreur aussi bien. Comment se peut-il? Comment une adresse à distance, après tout ce que la connexion ajoute à une socket, peut-elle déjà être utilisée? Connecter plusieurs sockets exactement à la même adresse distante n'a jamais été un problème avant, alors qu'est-ce qui ne va pas ici?

Comme je l'ai dit tout en haut de ma réponse, une connexion est définie par un tuple de cinq valeurs, vous souvenez-vous? Et j'ai aussi dit que ces cinq valeurs doivent être uniques sinon le système ne peut plus distinguer deux connexions, n'est-ce pas? Eh bien, avec la réutilisation d'adresse, vous pouvez lier deux sockets du même protocole à la même adresse source et au même port. Cela signifie que trois de ces cinq valeurs sont déjà les mêmes pour ces deux sockets. Si vous essayez maintenant de connecter ces deux sockets à la même adresse de destination et port, vous créerez deux sockets connectés, dont les tuples sont absolument identiques. Cela ne peut pas fonctionner, du moins pas pour les connexions TCP (les connexions UDP ne sont pas de vraies connexions de toute façon). Si les données sont arrivées pour l'une des deux connexions, le système n'a pas pu identifier la connexion à laquelle les données appartiennent. Au moins l'adresse de destination ou le port de destination doit être différent pour chaque connexion, de sorte que le système n'a aucun problème pour identifier à quelle connexion les données entrantes appartiennent.

Donc, si vous liez deux sockets du même protocole à la même adresse source et au même port, essayez de les connecter à la même adresse et au même port de destination, connect() va effectivement échouer avec l'erreur EADDRINUSE pour la deuxième socket, vous essayez de vous connecter, ce qui signifie qu'une socket avec un tuple identique de cinq valeurs est déjà connectée.

Adresses de multidiffusion

La plupart des gens ignorent le fait que les adresses de multidiffusion existent, mais elles existent. Alors que les adresses monodiffusion sont utilisées pour les communications un-à-un, les adresses de multidiffusion sont utilisées pour les communications un-à-plusieurs. La plupart des personnes ont pris connaissance des adresses de diffusion groupée lorsqu'elles ont eu connaissance d'IPv6, mais des adresses de diffusion groupée existaient également dans IPv4, même si cette fonctionnalité n'était jamais largement utilisée sur Internet.

Le sens de SO_REUSEADDR les modifications pour les adresses de multidiffusion car elles permettent à plusieurs sockets d'être liées exactement à la même combinaison d'adresse et de port de multidiffusion source. En d'autres termes, pour les adresses multicast SO_REUSEADDR se comporte exactement comme SO_REUSEPORT pour les adresses monodiffusion. En fait, le code traite SO_REUSEADDR et SO_REUSEPORT identiquement pour les adresses de multidiffusion, cela signifie que vous pourriez dire que SO_REUSEADDR implique SO_REUSEPORT pour toutes les adresses de multidiffusion et l'inverse.


FreeBSD / OpenBSD / NetBSD

Tous ces derniers sont des fourchettes plutôt tardives du code BSD original, c'est pourquoi ils offrent tous les trois les mêmes options que BSD et ils se comportent également de la même manière que dans BSD.


macOS (MacOS X)

À la base, macOS est simplement un UNIX de style BSD nommé "Darwin", basé sur une fourchette plutôt tardive du code BSD (BSD 4.3), qui a ensuite été même re-synchronisé avec la base de code FreeBSD 5 (à l'époque) de la version Mac OS 10.3, de sorte qu'Apple pouvait gagner Conformité POSIX totale (macOS est certifié POSIX) Bien que le micro-noyau soit au cœur ("Mach"), le reste du noyau ("XNU") est fondamentalement juste un noyau BSD et c'est pourquoi macOS offre les mêmes options que BSD et ils se comportent également de la même manière que dans BSD.

iOS / watchOS / tvOS

iOS est juste une fourche macOS avec un noyau légèrement modifié et rogné, un ensemble d'outils d'espace utilisateur un peu dépouillé et un ensemble de framework par défaut légèrement différent. watchOS et tvOS sont des forks iOS, qui sont encore plus loin (en particulier watchOS). À ma connaissance, ils se comportent tous exactement comme macOS.


Linux

Linux <3.9

Avant Linux 3.9, seule l'option SO_REUSEADDR existé. Cette option se comporte généralement de la même manière que dans BSD avec deux exceptions importantes:

  1. Tant qu'une socket TCP (serveur) d'écoute est liée à un port spécifique, le SO_REUSEADDR L'option est entièrement ignorée pour toutes les sockets ciblant ce port. La liaison d'une seconde socket au même port n'est possible que si elle était également possible en BSD sans avoir SO_REUSEADDR ensemble. Par exemple. vous ne pouvez pas lier à une adresse générique, puis à une adresse plus spécifique ou l'inverse, les deux sont possibles dans BSD si vous définissez SO_REUSEADDR. Ce que vous pouvez faire, c'est que vous pouvez vous lier au même port et à deux adresses non génériques différentes, car cela est toujours autorisé. Dans cet aspect, Linux est plus restrictif que BSD.

  2. La deuxième exception est que pour les sockets client, cette option se comporte exactement comme SO_REUSEPORT dans BSD, tant que les deux avaient ce drapeau mis avant qu'ils ont été liés. La raison de permettre cela était simplement qu'il est important d'être capable de lier plusieurs sockets exactement à la même adresse de socket UDP pour divers protocoles et comme il n'y avait pas de SO_REUSEPORT avant 3.9, le comportement de SO_REUSEADDR a été modifié en conséquence pour combler cette lacune. Dans cet aspect, Linux est moins restrictif que BSD.

Linux> = 3.9

Linux 3.9 a ajouté l'option SO_REUSEPORT à Linux aussi bien. Cette option se comporte exactement comme l'option dans BSD et permet de lier exactement la même adresse et le même numéro de port tant que toutes les sockets ont cette option définie avant de les lier.

Pourtant, il y a encore deux différences à SO_REUSEPORT sur d'autres systèmes:

  1. Pour empêcher le "détournement de port", il existe une limitation particulière: Toutes les sockets qui souhaitent partager la même adresse et la même combinaison de ports doivent appartenir à des processus partageant le même ID utilisateur effectif! Un utilisateur ne peut donc pas "voler" les ports d'un autre utilisateur. Ceci est une magie spéciale pour compenser quelque peu les disparus SO_EXCLBIND/SO_EXCLUSIVEADDRUSE drapeaux.

  2. De plus, le noyau effectue une "magie spéciale" pour SO_REUSEPORT sockets que l'on ne trouve pas dans les autres systèmes d'exploitation: Pour les sockets UDP, il essaie de distribuer les datagrammes de manière égale, pour les sockets d'écoute TCP, il essaie de distribuer les requêtes de connexion entrantes (celles acceptées par l'appel accept()) uniformément sur toutes les prises partageant la même adresse et la même combinaison de ports. Ainsi, une application peut facilement ouvrir le même port dans plusieurs processus enfants, puis utiliser SO_REUSEPORT pour obtenir un équilibrage de charge très peu coûteux.


Android

Même si l'ensemble du système Android est quelque peu différent de la plupart des distributions Linux, le noyau Linux est légèrement modifié, donc tout ce qui s'applique à Linux devrait également s'appliquer à Android.


les fenêtres

Windows ne connaît que le SO_REUSEADDR option, il n'y a pas SO_REUSEPORT. Réglage SO_REUSEADDR sur une socket dans Windows se comporte comme le réglage SO_REUSEPORT et SO_REUSEADDR sur une socket dans BSD, à une exception près: une socket avec SO_REUSEADDR peut toujours se lier exactement à la même adresse source et au même port qu'une socket déjà connectée, même si l'autre socket n'a pas cette option définie quand il était lié. Ce comportement est quelque peu dangereux car il permet à une application de "voler" le port connecté d'une autre application. Inutile de dire que cela peut avoir d'importantes implications sur le plan de la sécurité. Microsoft s'est rendu compte que cela pourrait être un problème et a donc ajouté une autre option de socket SO_EXCLUSIVEADDRUSE. Réglage SO_EXCLUSIVEADDRUSE sur une socket s'assure que si la liaison réussit, la combinaison de l'adresse source et du port est détenue exclusivement par cette socket et aucune autre socket ne peut les lier, même si elle a SO_REUSEADDR ensemble.

Pour encore plus de détails sur la façon dont les drapeaux SO_REUSEADDR et SO_EXCLUSIVEADDRUSE travailler sur Windows, comment ils influencent liaison / re-liaison, Microsoft a aimablement fourni une table similaire à ma table près du haut de cette réponse. Visitez cette page et faites défiler un peu. En fait il y a trois tables, la première montre l'ancien comportement (avant Windows 2003), la seconde le comportement (Windows 2003 et plus) et la troisième montre comment le comportement change dans Windows 2003 et plus tard si le bind() les appels sont faits par différents utilisateurs.


Solaris

Solaris est le successeur de SunOS. SunOS a été basé sur une fourchette de BSD, SunOS 5 et plus tard a été basé sur une fourchette de SVR4, mais SVR4 est une fusion de BSD, System V, et Xenix, donc jusqu'à un certain degré Solaris est également une fourchette BSD, et plutôt tôt. Par conséquent, Solaris ne connaît que SO_REUSEADDR, il n'y a pas SO_REUSEPORT. le SO_REUSEADDR se comporte à peu près de la même manière que dans BSD. Pour autant que je sache, il n'y a aucun moyen d'obtenir le même comportement que SO_REUSEPORTdans Solaris, cela signifie qu'il n'est pas possible de lier deux sockets exactement à la même adresse et au même port.

Semblable à Windows, Solaris a la possibilité de donner à un socket une liaison exclusive. Cette option est nommée SO_EXCLBIND. Si cette option est définie sur une socket avant de la lier, définissez SO_REUSEADDR sur une autre socket n'a aucun effet si les deux sockets sont testés pour un conflit d'adresse. Par exemple. si socketA est lié à une adresse générique et socketB a SO_REUSEADDR activé et est lié à une adresse non générique et au même port que socketA, cette liaison réussira normalement, sauf si socketA avait SO_EXCLBIND activé, auquel cas il échouera quelle que soit la SO_REUSEADDR drapeau de socketB.


D'autres systèmes

Dans le cas où votre système ne figure pas ci-dessus, j'ai écrit un petit programme de test que vous pouvez utiliser pour savoir comment votre système gère ces deux options. Aussi, si vous pensez que mes résultats sont faux, veuillez d'abord exécuter ce programme avant de poster des commentaires et éventuellement de faire de fausses déclarations.

Tout ce dont le code a besoin pour construire est un peu l'API POSIX (pour les parties réseau) et un compilateur C99 (en fait, la plupart des compilateurs non-C99 fonctionneront aussi longtemps qu'ils offrent). inttypes.h et stdbool.h; par exemple. gcc pris en charge les deux longtemps avant d'offrir le support complet de C99).

Tout ce que le programme doit exécuter, c'est qu'au moins une interface de votre système (autre que l'interface locale) ait une adresse IP attribuée et qu'une route par défaut utilise cette interface. Le programme rassemblera cette adresse IP et l'utilisera comme deuxième "adresse spécifique".

Il teste toutes les combinaisons possibles auxquelles vous pouvez penser:

  • Protocole TCP et UDP
  • Prises normales, prises d'écoute (serveur), prises de multidiffusion
  • SO_REUSEADDR définir sur socket1, socket2 ou les deux sockets
  • SO_REUSEPORT définir sur socket1, socket2 ou les deux sockets
  • Toutes les combinaisons d'adresses que vous pouvez faire 0.0.0.0 (wildcard), 127.0.0.1 (adresse spécifique), et la deuxième adresse spécifique trouvée sur votre interface principale (pour la multidiffusion, c'est juste 224.1.2.3 dans tous les tests)

et imprime les résultats dans une belle table. Il fonctionnera également sur des systèmes qui ne connaissent pas SO_REUSEPORT, auquel cas cette option n'est simplement pas testée.

Ce que le programme ne peut pas facilement tester, c'est comment SO_REUSEADDR agit sur les prises dans TIME_WAIT Etat car il est très difficile de forcer et de garder une socket dans cet état. Heureusement, la plupart des systèmes d'exploitation semblent simplement se comporter comme BSD ici et la plupart du temps, les programmeurs peuvent simplement ignorer l'existence de cet état.

Voici le code (Je ne peux pas l'inclure ici, les réponses ont une taille limite et le code pousserait cette réponse au-delà de la limite).


1299
2018-01-17 21:45