man pages
et les documentations du programmeur pour les options de socket SO_REUSEADDR
et SO_REUSEPORT
sont différents pour différents systèmes d'exploitation et sont souvent très déroutants. Certains systèmes d'exploitation n'ont même pas l'option SO_REUSEPORT
. Le site 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 peut même ne pas être explicitement mentionnée dans le texte.
Alors, comment exactement SO_REUSEADDR
est-il différent de SO_REUSEPORT
?
Les systèmes sans SO_REUSEPORT
sont-ils plus limités?
Et quel est le comportement attendu si j'utilise l'un ou l'autre sur différents systèmes d'exploitation?
Bienvenue dans le monde merveilleux de la portabilité ... ou plutôt de son absence. Avant de commencer à analyser ces deux options en détail et à examiner de plus près la manière dont 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é la mise en œuvre du socket BSD à un moment donné (ou au moins ses interfaces), puis ont commencé à la faire évoluer par eux-mêmes. Bien entendu, l’implémentation du socket BSD a également évolué en même temps. Ainsi, les systèmes qui l’ont copiée ont par la suite obtenu des fonctionnalités manquantes par rapport aux systèmes qui les copiaient auparavant. Comprendre l'implémentation du socket BSD est la clé de la compréhension de toutes les autres implémentations de socket. Vous devriez donc en prendre connaissance, même si vous ne vous souciez pas d'écrire du code pour un système BSD.
Vous devez connaître quelques notions de base 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. De ce fait, deux connexions ne peuvent pas avoir les mêmes cinq valeurs, sinon le système ne serait plus en mesure de distinguer ces connexions.
Le protocole d'une socket est défini lorsqu'une socket est créée avec la fonction socket()
. L'adresse source et le port sont définis avec la fonction bind()
. L'adresse et le port de destination sont définis avec la fonction connect()
. UDP étant un protocole sans connexion, vous pouvez utiliser les sockets UDP sans les connecter. Pourtant, il est permis de les connecter et dans certains cas, très avantageux pour votre code et la conception générale de vos applications. En mode sans connexion, les sockets UDP qui n'étaient pas explicitement liés lors de la première transmission de données sont généralement automatiquement liés par le système, car un socket UDP non lié ne peut recevoir aucune donnée (réponse). Il en va de même pour une socket non liée TCP, elle est automatiquement liée avant sa connexion.
Si vous liez explicitement un socket, il est possible de le lier au port 0
, ce qui signifie "n'importe quel port". Dans la mesure où un socket ne peut pas vraiment être lié à tous les ports existants, le système devra choisir lui-même un port spécifique (généralement dans une plage de ports source prédéfinie et spécifique 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.0
en cas d'IPv4 et ::
en cas d'IPv6). Contrairement aux ports, un socket peut en réalité être lié à "toute 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 un socket ne peut pas être connecté et en même temps être lié à une adresse IP locale. En fonction de l'adresse de destination et du contenu de la table de routage, le système choisit une adresse source appropriée et remplace la liaison "tout" 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 en réalité non pertinente. Reliure socketA
à A:X
et socketB
à B:Y
, où A
et B
sont des adresses et X
et Y
sont des ports, est toujours possible tant que X != Y
est vrai. Cependant, même si X == Y
, la liaison est toujours possible tant que A != B
est vraie. 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 aboutiront. Gardez toutefois à l'esprit qu'un socket peut être lié localement à "n'importe quelle adresse". Si un socket est lié à 0.0.0.0:21
, il est lié à toutes les adresses locales existantes en même temps. Dans ce cas, aucun autre socket ne peut être lié au port 21
, quelle que soit l'adresse IP spécifique essayée. bind to, as 0.0.0.0
entre en conflit avec toutes les adresses IP locales existantes.
Tout ce qui a été dit jusqu'à présent est à peu près égal pour tous les principaux systèmes d'exploitation. Les choses commencent à devenir spécifiques à l'OS lorsque la réutilisation des adresses entre en jeu. Nous commençons par BSD car, comme je l’ai dit plus haut, c’est la mère de toutes les implémentations de socket.
Si SO_REUSEADDR
est activé sur un socket avant de le lier, le socket peut être lié avec succès sauf en cas de conflit avec un autre socket lié à exactement la même combinaison d'adresse source et de port. Maintenant, vous pouvez vous demander en quoi 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 SO_REUSEADDR
, lier socketA
à 0.0.0.0:21
, puis lier socketB
à 192.168.0.1:21
échouera (avec l'erreur EADDRINUSE
), car 0.0.0.0 signifie "toute adresse IP locale", donc toutes les adresses IP locales sont considérées comme utilisées par cette socket, ce qui inclut également 192.168.0.1
. Avec SO_REUSEADDR
cela réussira, puisque 0.0.0.0
et 192.168.0.1
ne sont pas exactement la même adresse, on est un joker 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 dans lequel socketA
et socketB
sont liés; sans SO_REUSEADDR
il échouera toujours, avec SO_REUSEADDR
il réussira toujours.
Pour vous donner une meilleure vue d'ensemble, créons un tableau et listons 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: 192 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
défini ou non, et est finalement lié. à l'adresse indiquée pour socketB
. Result
est le résultat de l'opération de liaison pour socketB
. Si la première colonne indique ON/OFF
, la valeur de SO_REUSEADDR
n'a aucune incidence sur le résultat.
Ok, SO_REUSEADDR
a un effet sur les adresses génériques, bon à savoir. Pourtant, ce n’est pas seulement son effet. Il existe un autre effet bien connu qui est également la raison pour laquelle la plupart des gens utilisent SO_REUSEADDR
dans les programmes serveur en premier lieu. Pour l’autre utilisation importante de cette option, nous devons examiner de plus près le fonctionnement du protocole TCP.
Une socket a un tampon d’envoi et si un appel à la fonction send()
aboutit, 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 assez rapidement, sinon immédiatement, mais pour les sockets TCP, il peut s'écouler un délai relativement long entre l'ajout de données dans le tampon d'envoi et la mise en oeuvre effective de TCP. ces données. Par conséquent, lorsque vous fermez un socket TCP, il peut rester des données en attente dans le tampon d'envoi, qui n'a pas encore été envoyé, mais votre code le considère comme envoyé, car l'appel send()
a réussi. Si l'implémentation TCP fermait immédiatement le socket à votre demande, toutes ces données seraient perdues et votre code n'en aurait même pas connaissance. TCP est considéré comme un protocole fiable et la perte de données n'est pas très fiable. C'est pourquoi un socket qui a encore des données à envoyer entrera dans un état appelé TIME_WAIT
lorsque vous le fermerez. Dans cet état, il attendra que toutes les données en attente aient été envoyées avec succès ou qu'un délai d'attente soit atteint, auquel cas le socket est fermé de force.
La durée pendant laquelle le noyau attendra avant de fermer le socket, qu’il ait toujours des données en vol ou non, est appelée Linger Time. Le Linger Time est configurable globalement 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 à l'aide de l'option de socket SO_LINGER
, qui peut être utilisée pour raccourcir ou allonger le délai, voire le désactiver complètement. Désactiver complètement est une très mauvaise idée, cependant, car fermer un socket TCP est un processus légèrement complexe qui implique l’envoi et le retour de deux paquets (ainsi que le renvoi de ces paquets au cas où ils seraient perdus) et tout ce processus de fermeture est également limité par le Linger Time. Si vous désactivez l'attente, votre socket risque non seulement de perdre des données en vol, il est également toujours fermé de manière forcée au lieu d'être gracieux, ce qui n'est généralement pas recommandé. Les détails de la fermeture d'une connexion TCP vont au-delà de la portée de cette réponse. Si vous voulez en savoir plus sur, je vous recommande de consulter cette page . Et même si vous désactiviez votre attente avec SO_LINGER
, si votre processus meurt sans fermer explicitement le socket, BSD (et éventuellement d’autres systèmes) s’attardera néanmoins, en ignorant ce que vous avez configuré. Cela se produira par exemple si votre code appelle simplement exit()
(assez commun pour des programmes serveur minuscules et simples) ou si le processus est tué par un signal (ce qui inclut la possibilité qu'il se bloque simplement à cause d'un accès illégal à la mémoire). Vous ne pouvez donc rien faire pour vous assurer qu'un socket ne s'attardera jamais dans toutes les circonstances.
La question qui se pose est de savoir comment le système traite un socket dans l’état TIME_WAIT
? Si SO_REUSEADDR
n'est pas défini, un socket dans l'état TIME_WAIT
est toujours considéré comme 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 a vraiment été fermé, ce qui peut prendre aussi longtemps que le Linger Time configuré. Donc, ne vous attendez pas à pouvoir relier l’adresse source d’un socket immédiatement après sa fermeture. Dans la plupart des cas, cela échouera. Cependant, si SO_REUSEADDR
est défini pour le socket que vous essayez de lier, un autre socket lié à la même adresse et au même port dans l'état TIME_WAIT
est simplement ignoré, après que tout soit déjà à moitié mort, et votre socket peut se lier exactement à la même adresse sans aucun problème. Dans ce cas, le fait que l'autre socket puisse avoir exactement la même adresse et le même port ne joue aucun rôle. Notez que lier un socket à la même adresse et au même port qu’un socket mourant dans l’état TIME_WAIT
peut avoir des effets secondaires inattendus, et généralement indésirables, dans le cas où l’autre socket est toujours "au travail", mais elle dépasse les La portée 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 devez savoir sur SO_REUSEADDR
. Tout ce qui est écrit ci-dessus fonctionnera tant que le socket auquel vous souhaitez vous connecter a la réutilisation d'adresse activée. Il n'est pas nécessaire que l'autre socket, celle qui est déjà liée ou se trouve dans un état TIME_WAIT
, avait également cet indicateur défini quand elle était liée. Le code qui décide si la liaison va réussir ou échouer, inspecte uniquement le drapeau SO_REUSEADDR
du socket introduit dans l'appel bind()
, pour tous les autres sockets inspectés, ce drapeau n'est même pas regardé.
SO_REUSEPORT
est ce à quoi la plupart des gens s'attendent de ce que SO_REUSEADDR
soit. SO_REUSEPORT
vous permet de lier un nombre arbitraire de sockets à exactement la même adresse source et le même port tant que tous les précédents sockets liés avaient également SO_REUSEPORT
défini avant leur liaison. Si le premier socket lié à une adresse et à un port n'a pas SO_REUSEPORT
défini, aucun autre socket ne peut être lié à la même adresse ni au même port, que cet autre socket ait ou non SO_REUSEPORT
set , jusqu’à ce que le premier socket relâche sa liaison. Contrairement à SO_REUESADDR
, le code manipulant SO_REUSEPORT
vérifiera non seulement que le socket actuellement lié a SO_REUSEPORT
défini, mais il vérifiera également que le socket avec une adresse et un port en conflit avait SO_REUSEPORT
défini quand il a été lié.
SO_REUSEPORT
n'implique pas SO_REUSEADDR
. Cela signifie que si SO_REUSEPORT
n'est pas défini sur une socket lorsqu'il a été lié et que SO_REUSEPORT
est défini sur une autre, lorsqu'il est lié à la même adresse et au même port, la liaison échoue, ce qui est attendu. échoue également si l'autre socket est déjà en train de mourir et est dans l'état TIME_WAIT
. Pour pouvoir lier un socket aux mêmes adresses et au même port qu’un autre socket de l’état TIME_WAIT
, il faut soit que SO_REUSEADDR
soit défini sur ce socket, soit que SO_REUSEPORT
ait été défini sur les deux sockets avant de les lier. Bien sûr, il est permis de définir à la fois SO_REUSEPORT
et SO_REUSEADDR
sur un socket.
Il n'y a pas grand chose à dire sur SO_REUSEPORT
autre que celui-ci a été ajouté plus tard que SO_REUSEADDR
, c'est pourquoi vous ne le trouverez pas dans de nombreuses implémentations de sockets d'autres systèmes, qui "fourchissaient" le code BSD avant cette option a été ajoutée, et qu'il n'y avait aucun moyen de lier deux sockets exactement à la même adresse de socket dans BSD avant cette option.
La plupart des gens savent que bind()
peut échouer avec l'erreur EADDRINUSE
. Cependant, lorsque vous commencez à jouer avec la réutilisation des adresses, vous pouvez vous retrouver dans la situation étrange où connect()
échoue également avec cette erreur. . Comment se peut-il? Comment une adresse distante, après tout ce que connect ajoute-t-elle à une prise, peut-elle déjà être utilisée? La connexion de plusieurs sockets à la même adresse distante n'a jamais été un problème auparavant, 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 vous souvenez? 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 des adresses, 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 également ces deux sockets à la même adresse et au même port de destination, vous créerez deux sockets connectés, dont les nuplets sont absolument identiques. Cela ne peut pas fonctionner, du moins pas pour les connexions TCP (les connexions UDP ne sont de toute façon pas de véritables connexions). Si des données sont arrivées pour l'une ou l'autre des deux connexions, le système ne peut pas dire à quelle connexion les données appartiennent. Au moins l'adresse de destination ou le port de destination doit être différent pour l'une ou l'autre connexion, afin que le système n'ait aucun problème pour identifier à quelle connexion appartiennent les données entrantes.
Donc, si vous liez deux sockets du même protocole à la même adresse source et au même port et tentez de les connecter à la même adresse et au même port de destination, connect()
échouera en réalité avec l'erreur EADDRINUSE
pour la seconde socket que vous essayez de connecter, ce qui signifie qu’une socket avec un tuple identique de cinq valeurs est déjà connectée.
La plupart des gens ignorent le fait que les adresses de multidiffusion existent, mais elles existent. Alors que les adresses unicast sont utilisées pour une communication un à un, les adresses multicast sont utilisées pour une communication un à plusieurs. La plupart des gens se sont rendus compte des adresses de multidiffusion lorsqu'ils ont découvert IPv6, mais des adresses de multidiffusion existaient également dans IPv4, même si cette fonctionnalité n'avait jamais été largement utilisée sur Internet.
La signification de SO_REUSEADDR
change pour les adresses de multidiffusion, car elle permet de lier plusieurs sockets exactement à la même combinaison d'adresse de multidiffusion source et de port. En d'autres termes, pour les adresses de multidiffusion, SO_REUSEADDR
se comporte exactement comme SO_REUSEPORT
pour les adresses unicast. En fait, le code traite SO_REUSEADDR
et SO_REUSEPORT
de manière identique pour les adresses de multidiffusion, ce qui signifie que vous pouvez dire que SO_REUSEADDR
implique SO_REUSEPORT
pour toutes les adresses de multidiffusion et l'inverse.
Ce sont toutes des versions tardives du code BSD d'origine, c'est pourquoi elles offrent toutes les trois les mêmes options que BSD et se comportent également de la même manière que dans BSD.
MacOS est essentiellement un UNIX de type BSD nommé "Darwin", basé sur un fork du code BSD assez récent (BSD 4.3), qui a ensuite même été resynchronisé avec le (à ce moment-là, la base de code FreeBSD 5 pour Mac OS 10.3, de sorte que Apple puisse obtenir une conformité POSIX complète (macOS est certifié POSIX). Bien que son noyau repose sur un micro-noyau ("Mach"), le reste du noyau ("XNU") est essentiellement un noyau BSD, et c'est pourquoi macOS offre les mêmes options que BSD et se comportent également de la même manière que dans BSD.
iOS est juste un fork de MacOS avec un noyau légèrement modifié et ajusté, un ensemble d'outils d'espace utilisateur quelque peu réduit et un ensemble de framework par défaut légèrement différent. watchOS et tvOS sont des fourchettes iOS qui sont encore plus réduites (en particulier watchOS). À ma connaissance, ils se comportent tous exactement comme macOS.
Avant Linux 3.9, seule l'option SO_REUSEADDR
existait. Cette option se comporte généralement de la même manière que sous BSD, à deux exceptions importantes près:
Tant qu'un socket d'écoute (serveur) TCP est lié à un port spécifique, l'option SO_REUSEADDR
est entièrement ignorée pour tous les sockets ciblant ce port. La liaison d'un second socket sur le même port n'est possible que si cela était également possible dans BSD sans que SO_REUSEADDR
soit défini. Par exemple. vous ne pouvez pas vous lier à une adresse générique, puis à une adresse plus spécifique ou inversement, les deux sont possibles dans BSD si vous définissez SO_REUSEADDR
. Ce que vous pouvez faire, c'est que vous pouvez vous connecter au même port et à deux adresses non génériques différentes, comme cela est toujours autorisé. Sous cet aspect, Linux est plus restrictif que BSD.
La deuxième exception est que pour les sockets client, cette option se comporte exactement comme SO_REUSEPORT
dans BSD, à condition que cet indicateur ait été défini avant d'être lié. La raison de cela est simplement qu'il est important de pouvoir relier plusieurs sockets exactement à la même adresse de socket UDP pour différents protocoles et qu'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. Sous cet aspect, Linux est moins restrictif que BSD.
Linux 3.9 a également ajouté l’option SO_REUSEPORT
à Linux. Cette option se comporte exactement comme l'option de BSD et permet de lier exactement la même adresse et le même numéro de port à condition que cette option ait été définie pour tous les sockets avant de les lier.
Cependant, il existe encore deux différences avec SO_REUSEPORT
sur d'autres systèmes:
Pour empêcher le "piratage de port", il existe une limitation spéciale: Tous 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. C'est une magie spéciale pour compenser quelque peu les drapeaux manquants SO_EXCLBIND
/SO_EXCLUSIVEADDRUSE
.
De plus, le noyau exécute une "magie spéciale" pour les sockets SO_REUSEPORT
introuvables dans les autres systèmes d'exploitation: pour les sockets UDP, il tente de distribuer les datagrammes de manière uniforme, pour les sockets d'écoute TCP, il tente de le distribuer demandes de connexion entrantes (celles acceptées en appelant accept()
) de manière uniforme sur tous les sockets 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 économique.
Bien que l'ensemble du système Android diffère quelque peu de la plupart des distributions Linux, son noyau Linux est légèrement modifié. Ainsi, tout ce qui s'applique à Linux devrait également s'appliquer à Android.
Windows ne connait que l'option SO_REUSEADDR
, il n'y a pas de SO_REUSEPORT
. Définir SO_REUSEADDR
sur un socket sous Windows se comporte comme si vous définissiez SO_REUSEPORT
et SO_REUSEADDR
sur un socket dans BSD, à une exception près: un socket avec SO_REUSEADDR
peut toujours se lier exactement au même adresse source et port en tant que socket déjà lié , même si cette option n’avait pas cette option configurée lorsqu’elle était liée . 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 des conséquences majeures sur 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
. La définition de SO_EXCLUSIVEADDRUSE
sur un socket permet de s'assurer que si la liaison réussit, la combinaison adresse source et port appartient exclusivement à ce socket et qu'aucun autre socket ne peut s'y lier, même s'il a SO_REUSEADDR
défini. .
Pour plus de détails sur le fonctionnement des indicateurs SO_REUSEADDR
et SO_EXCLUSIVEADDRUSE
sur Windows et sur leur influence sur la liaison/nouvelle liaison, Microsoft a gracieusement fourni une table similaire à la mienne située en haut de cette réponse. Il suffit de visiter 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 deuxième le comportement (Windows 2003 et supérieur) et la troisième montre comment le comportement change dans Windows 2003 et versions ultérieures si la bind()
les appels sont passés par différents utilisateurs.
Solaris est le successeur de SunOS. SunOS était à l'origine basé sur un fork de BSD, SunOS 5 et, plus tard, sur un fork de SVR4. Toutefois, SVR4 est une fusion de BSD, System V et Xenix. Solaris est donc aussi, dans une certaine mesure, un fork plutôt tôt. Par conséquent, Solaris ne connaît que SO_REUSEADDR
, il n’existe pas de SO_REUSEPORT
. Le SO_REUSEADDR
se comporte pratiquement de la même manière que sous BSD. Autant que je sache, il n’existe aucun moyen d’obtenir le même comportement que SO_REUSEPORT
dans Solaris, ce qui signifie qu’il est impossible de lier deux sockets à la même adresse et au même port.
Semblable à Windows, Solaris a la possibilité de donner une liaison exclusive à un socket. Cette option s'appelle SO_EXCLBIND
. Si cette option est définie sur un socket avant de le lier, définir SO_REUSEADDR
sur un 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 que socketB
a SO_REUSEADDR
activé et est lié à une adresse non générique et au même port que socketA
, cette liaison aboutira normalement , sauf si socketA
avait SO_EXCLBIND
activé, auquel cas il échouera quel que soit le drapeau SO_REUSEADDR
de socketB
.
Si votre système ne figure pas dans la liste ci-dessus, j’ai écrit un petit programme de test que vous pouvez utiliser pour déterminer comment votre système gère ces deux options. Aussi, si vous pensez que mes résultats sont erronés , lancez d'abord ce programme avant de poster des commentaires et éventuellement de faire de fausses déclarations.
Tout ce dont le code a besoin pour créer 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 un support complet de C99).
Tout ce que le programme doit exécuter est qu’au moins une interface de votre système (autre que l’interface locale) se voit attribuer une adresse IP et qu’une route par défaut définissant cette interface soit définie. Le programme va collecter cette adresse IP et l'utiliser comme seconde "adresse spécifique".
Il teste toutes les combinaisons possibles auxquelles vous pouvez penser:
SO_REUSEADDR
défini sur socket1, socket2 ou les deux socketsSO_REUSEPORT
défini sur socket1, socket2 ou les deux sockets0.0.0.0
(caractère générique), 127.0.0.1
(adresse spécifique) et à la deuxième adresse spécifique trouvée sur votre interface principale (pour la multidiffusion, il s'agit simplement de 224.1.2.3
dans tous les tests )et affiche les résultats dans une belle table. Cela fonctionnera également sur les systèmes qui ne connaissent pas SO_REUSEPORT
, auquel cas cette option n'est tout simplement pas testée.
Ce que le programme ne peut pas facilement tester, c'est comment SO_REUSEADDR
agit sur les sockets dont l'état est TIME_WAIT
car il est très délicat de forcer et de maintenir un socket dans cet état. Heureusement, la plupart des systèmes d'exploitation semblent se comporter simplement comme BSD 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).