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 d'approfondir la façon dont les différents systèmes d'exploitation les gèrent, il convient de noter que l'implémentation de socket BSD est la mère de toutes les implémentations de socket. Fondamentalement, tous les autres systèmes ont copié l'implémentation du socket BSD à un moment donné (ou au moins ses interfaces), puis ont commencé à le faire évoluer par eux-mêmes. Bien sûr, l'implémentation du socket BSD a également évolué en même temps et donc les systèmes qui l'ont copié plus tard ont obtenu des fonctionnalités qui manquaient dans les systèmes qui l'ont copié plus tôt. Comprendre l'implémentation de 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 devez 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'un socket est défini lors de la création d'un socket avec la socket()
fonction. L'adresse source et le port sont définis avec la bind()
fonction. L'adresse et le port de destination sont définis avecconnect()
fonction. UDP étant un protocole sans connexion, les sockets UDP peuvent être utilisés sans les connecter. Pourtant, il est autorisé de les connecter et dans certains cas, très avantageux pour votre code et la conception générale de votre application. En mode sans connexion, les sockets UDP qui n'étaient pas explicitement liés lorsque des données leur sont envoyées pour la première fois sont généralement automatiquement liés par le système, car un socket UDP non lié ne peut pas recevoir de données (de réponse). Il en va de même pour un socket TCP non lié, il est automatiquement lié avant d'être connecté.
Si vous liez explicitement un socket, il est possible de le lier au port 0
, ce qui signifie "n'importe quel port". Puisqu'un socket ne peut pas vraiment être lié à tous les ports existants, le système devra choisir lui-même un port spécifique dans ce cas (généralement à partir d'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
dans le cas d'IPv4 et::
en cas d'IPv6). Contrairement au cas des ports, un socket peut vraiment être lié à "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 un socket ne peut pas être connecté et en même temps lié à une adresse IP locale. En fonction de l'adresse de destination et du contenu de la table de routage, le système choisira une adresse source appropriée et remplacera 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 n'est en fait pas pertinente. La liaison socketA
vers A:X
et socketB
vers B:Y
, où A
et B
sont des adresses et X
et Y
sont des ports, est toujours possible tant que cela X != Y
est vrai. Cependant, même si X == Y
, la liaison est toujours possible tant qu'elle reste A != B
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 réussiront. Gardez cependant à l'esprit qu'un socket peut être lié 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 port21
, quelle que soit l'adresse IP spécifique à laquelle il essaie de se lier, car cela 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 au système d'exploitation lorsque la réutilisation des adresses entre en jeu. Nous commençons par BSD, car comme je l'ai dit ci-dessus, 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 existe un conflit avec un autre socket lié exactement à la même combinaison d'adresse source et de port. Maintenant, vous vous demandez peut-être en quoi cela est-il différent qu'auparavant? Le mot clé est "exactement". SO_REUSEADDR
modifie principalement la façon dont les adresses génériques ("toute adresse IP") sont traitées lors de la recherche de conflits.
Sans SO_REUSEADDR
, la liaison socketA
à 0.0.0.0:21
puis la liaison socketB
à 192.168.0.1:21
échouera (avec 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 ce socket et cela inclut 192.168.0.1
également. Avec SO_REUSEADDR
elle réussira, car 0.0.0.0
et 192.168.0.1
sont pas exactement la même adresse, on 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 l'énoncé ci-dessus est vrai quel que soit l'ordre socketA
et socketB
les liens; sans SO_REUSEADDR
elle échouera toujours, avec SO_REUSEADDR
elle réussira toujours.
Pour vous donner un meilleur aperçu, faisons un tableau ici 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)
MARCHE / ARRÊT 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 Erreur 192.168.1.0:21 (EADDRINUSE)
OFF 192.168.1.0:21 0.0.0.0:21 Erreur (EADDRINUSE)
MARCHE 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 qu'il socketA
a déjà été lié avec succès à l'adresse indiquée pour socketA
, puis socketB
est créé, est SO_REUSEADDR
défini ou non, et enfin est 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'est pas pertinente 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 serveur en premier lieu. Pour l'autre utilisation importante de cette option, nous devons approfondir le fonctionnement du protocole TCP.
Une socket a un tampon d'envoi et si un appel à la send()
fonction réussit, cela ne signifie pas que les données demandées ont réellement été réellement envoyées, cela signifie seulement 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 très rapidement, sinon immédiatement, mais pour les sockets TCP, il peut y avoir un délai relativement long entre l'ajout de données au 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 votre code les considère comme envoyées, car lesend()
l'appel a réussi. Si l'implémentation TCP fermait le socket immédiatement à votre demande, toutes ces données seraient perdues et votre code n'en serait même pas informé. TCP est considéré comme un protocole fiable et la perte de données comme celle-ci 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 jusqu'à ce qu'un délai 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 ou non encore des données en vol, est appelée Linger Time . Le temps d'attente est globalement configurable sur la plupart des systèmes et par défaut assez long (deux minutes est une valeur courante 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ée pour raccourcir ou allonger le délai d'attente, et même pour le désactiver complètement. La désactiver complètement est une très mauvaise idée, cependant, car fermer un socket TCP avec élégance est un processus légèrement complexe et implique l'envoi et le retour de quelques paquets (ainsi que le renvoi de ces paquets en cas de perte) et tout ce processus de fermeture est également limité par le Linger Time. Si vous désactivez la persistance, votre socket peut non seulement perdre des données en vol, 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 dépassent le cadre de cette réponse, si vous souhaitez en savoir plus, je vous recommande de consulter cette page . Et même si vous avez désactivé la persistance SO_LINGER
, si votre processus meurt sans fermer explicitement le socket, BSD (et éventuellement d'autres systèmes) persistera néanmoins, ignorant ce que vous avez configuré. Cela se produira par exemple si votre code appelle simplementexit()
(assez courant pour les petits programmes de serveur simples) ou le processus est tué par un signal (qui inclut la possibilité qu'il se bloque simplement à cause d'un accès illégal à la mémoire). Il n'y a donc rien que vous puissiez faire pour vous assurer qu'une prise ne persistera jamais en toutes 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, un socket en état TIME_WAIT
est considéré comme étant toujours lié à l'adresse et au port source et toute tentative de lier un nouveau socket à la même adresse et au même port échouera jusqu'à ce que le socket soit vraiment fermé, ce qui peut prendre aussi longtemps comme heure de sonnerie configurée . Ne vous attendez donc pas à pouvoir lier à nouveau 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'étatTIME_WAIT
est simplement ignoré, après tout, il 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 lier un socket à exactement la même adresse et le même port qu'un socket mourant dans l' TIME_WAIT
état peut avoir des effets secondaires inattendus et généralement indésirables si 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 en pratique.
Il y a une dernière chose que vous devez savoir SO_REUSEADDR
. Tout ce qui est écrit ci-dessus fonctionnera tant que la socket à laquelle 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 soit dans un TIME_WAIT
état, ait également défini ce drapeau lors de sa liaison. Le code qui décide de la réussite ou de l'échec de la liaison inspecte uniquement l' SO_REUSEADDR
indicateur du socket introduit dans l' bind()
appel, pour tous les autres sockets inspectés, cet indicateur n'est même pas examiné.
SO_REUSEPORT
SO_REUSEPORT
est ce que la plupart des gens s'attendent SO_REUSEADDR
à être. Fondamentalement, 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 sockets liés précédemment ont également été SO_REUSEPORT
définis avant d'être liés. Si le premier socket lié à une adresse et un port n'a pas été SO_REUSEPORT
défini, aucun autre socket ne peut être lié exactement à la même adresse et au même port, que cet autre socket soit SO_REUSEPORT
défini ou non, jusqu'à ce que le premier socket libère à nouveau sa liaison. Contrairement au cas du SO_REUESADDR
traitement du code SO_REUSEPORT
, non seulement vérifiera que le socket actuellement lié a été SO_REUSEPORT
défini, mais il vérifiera également que le socket avec une adresse et un port en conflit avait été SO_REUSEPORT
défini lors de sa liaison.
SO_REUSEPORT
n'implique pas SO_REUSEADDR
. Cela signifie que si un socket n'a pas été SO_REUSEPORT
défini lorsqu'il était lié et qu'un autre socket a SO_REUSEPORT
défini lorsqu'il est lié exactement à la même adresse et au même 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
état. Pour pouvoir lier un socket aux mêmes adresses et ports qu'un autre socket en TIME_WAIT
état, il faut soit SO_REUSEADDR
être défini sur ce socket, soit SO_REUSEPORT
avoir été défini sur les deux sockets avant de les lier. Bien sûr, il est autorisé de définir les deux, SO_REUSEPORT
et SO_REUSEADDR
, sur un socket.
Il n'y a pas grand-chose d'autre à dire sur le SO_REUSEPORT
fait qu'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 l'ajout de cette option, et qu'il n'y avait pas façon de lier deux sockets à exactement la même adresse de socket dans BSD avant cette option.
Connect () Retourner EADDRINUSE?
La plupart des gens savent que bind()
l'erreur peut échouer EADDRINUSE
, cependant, lorsque vous commencez à jouer avec la réutilisation des adresses, vous pouvez également rencontrer l'étrange situation qui connect()
échoue avec cette erreur. Comment se peut-il? Comment une adresse distante, après tout ce que la connexion ajoute à un socket, peut-elle déjà être utilisée? La connexion de plusieurs sockets à exactement 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 en souvenez? Et j'ai aussi dit que ces cinq valeurs doivent être uniques sinon le système ne peut plus distinguer deux connexions, non? 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 ces deux sockets également à la même adresse et au même port de destination, 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 des données arrivaient pour l'une des deux connexions, le système ne pouvait pas savoir à quelle connexion les données appartenaient.
Donc, si vous liez deux sockets du même protocole à la même adresse source et au même port et essayez de les connecter tous les deux à la même adresse et au même port de destination, connect()
échouera en fait avec l'erreur EADDRINUSE
pour le deuxième socket que vous essayez de connecter, ce qui signifie qu'un socket avec un tuple identique de cinq valeurs est déjà connecté.
Adresses de multidiffusion
La plupart des gens ignorent le fait qu'il existe des adresses de multidiffusion, mais elles existent. Alors que les adresses de monodiffusion sont utilisées pour les communications un à un, les adresses de multidiffusion sont utilisées pour les communications un à plusieurs. La plupart des gens ont découvert les adresses de multidiffusion lorsqu'ils ont découvert IPv6, mais les adresses de multidiffusion existaient également dans IPv4, même si cette fonctionnalité n'a jamais été largement utilisée sur Internet public.
La signification des SO_REUSEADDR
changements pour les adresses de multidiffusion car elle permet à plusieurs sockets d'être liés 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 unicast. En fait, le code traite SO_REUSEADDR
et SO_REUSEPORT
identiquement pour les adresses de multidiffusion, cela signifie que vous pouvez dire que cela SO_REUSEADDR
implique SO_REUSEPORT
pour toutes les adresses de multidiffusion et vice versa.
FreeBSD / OpenBSD / NetBSD
Ce sont toutes des fourches assez 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 qu'en BSD.
macOS (MacOS X)
À la base, macOS est simplement un UNIX de style BSD nommé " Darwin ", basé sur une fourche assez tardive du code BSD (BSD 4.3), qui a ensuite été même resynchronisé avec FreeBSD (à cette époque) Base de 5 codes pour la version Mac OS 10.3, afin qu'Apple puisse obtenir une conformité POSIX complète (macOS est certifié POSIX). En dépit d'avoir un micro-noyau à sa base (" 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 n'est qu'un fork de macOS avec un noyau légèrement modifié et découpé, un ensemble d'outils d'espace utilisateur quelque peu allégé et un ensemble de framework par défaut légèrement différent. watchOS et tvOS sont des fourches iOS, qui sont encore plus dépouillées (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
existait. Cette option se comporte généralement de la même manière que dans BSD avec deux exceptions importantes:
Tant qu'un socket TCP d'écoute (serveur) est lié à un port spécifique, l' SO_REUSEADDR
option est entièrement ignorée pour tous les sockets ciblant ce port. La liaison d'un deuxième socket au même port n'est possible que si elle était également possible dans BSD sans avoir SO_REUSEADDR
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 lier 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, tant que les deux avaient cet indicateur défini avant d'être liés. La raison de cette autorisation était simplement qu'il était important de pouvoir lier plusieurs sockets à exactement à la même adresse de socket UDP pour divers protocoles et comme il n'y en avait pas SO_REUSEPORT
avant 3.9, le comportement de a SO_REUSEADDR
été modifié en conséquence pour combler cette lacune . Sous cet aspect, Linux est moins restrictif que BSD.
Linux> = 3,9
Linux 3.9 a également ajouté l'option SO_REUSEPORT
à Linux. Cette option se comporte exactement comme l'option dans BSD et permet de se lier exactement à la même adresse et au même numéro de port tant que tous les sockets ont cette option définie avant de les lier.
Pourtant, il existe encore deux différences par rapport aux SO_REUSEPORT
autres systèmes:
Pour éviter le "détournement de port", il existe une limitation particulière: tous les sockets qui souhaitent partager la même adresse et la même combinaison de ports doivent appartenir à des processus qui partagent 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 SO_EXCLBIND
/ manquants SO_EXCLUSIVEADDRUSE
.
De plus, le noyau effectue une "magie spéciale" pour les SO_REUSEPORT
sockets qui ne se trouve pas dans d'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 demandes de connexion entrantes (celles acceptées en appelant accept()
) uniformément sur toutes les sockets qui partagent 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 l'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, dans son cœur fonctionne un noyau Linux légèrement modifié, donc tout ce qui s'applique à Linux devrait également s'appliquer à Android.
les fenêtres
Windows ne connaît que l' SO_REUSEADDR
option, il n'y en a pas SO_REUSEPORT
. Le paramétrage SO_REUSEADDR
sur un socket dans Windows se comporte comme le paramétrage SO_REUSEPORT
et SO_REUSEADDR
sur un socket dans BSD, à une exception près: un socket avec SO_REUSEADDR
peut toujours se lier exactement à la même adresse source et au même port qu'un socket déjà lié, même si l'autre socket n'avait pas cette option définir 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 des implications majeures sur la sécurité. Microsoft a réalisé que cela pouvait être un problème et a donc ajouté une autre option de socket SO_EXCLUSIVEADDRUSE
. RéglageSO_EXCLUSIVEADDRUSE
sur un socket s'assure que si la liaison réussit, la combinaison de l'adresse source et du port est la propriété exclusive de ce socket et aucun autre socket ne peut s'y lier, même s'il est SO_REUSEADDR
défini.
Pour encore plus de détails sur la façon dont les indicateurs SO_REUSEADDR
et SO_EXCLUSIVEADDRUSE
fonctionnent sous Windows, comment ils influencent la liaison / la nouvelle liaison, Microsoft a aimablement fourni un tableau similaire à mon tableau en haut de cette réponse. Visitez cette page et faites défiler un peu. En fait, il y a trois tableaux, le premier montre l'ancien comportement (antérieur à Windows 2003), le second le comportement (Windows 2003 et supérieur) et le troisième montre comment le comportement change dans Windows 2003 et plus tard si les bind()
appels sont effectués par différents utilisateurs.
Solaris
Solaris est le successeur de SunOS. SunOS était à l'origine basé sur une fourchette de BSD, SunOS 5 et plus tard était basé sur une fourchette de SVR4, mais SVR4 est une fusion de BSD, System V et Xenix, donc jusqu'à un certain point Solaris est également une fourchette BSD et un assez tôt. Par conséquent, Solaris ne sait queSO_REUSEADDR
qu'il n'y en a pas SO_REUSEPORT
. Le SO_REUSEADDR
comportement est à peu près le même que dans BSD. Pour autant que je sache, il n'y a aucun moyen d'obtenir le même comportement que SO_REUSEPORT
dans Solaris, cela signifie qu'il n'est pas possible de lier deux sockets à exactement la même adresse et le même port.
Semblable à Windows, Solaris a une option pour donner à un socket une liaison exclusive. Cette option est nommée SO_EXCLBIND
. Si cette option est définie sur un socket avant de le lier, la définition SO_REUSEADDR
d'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 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 elle socketA
était SO_EXCLBIND
activée, auquel cas elle échouera quel que soit l' SO_REUSEADDR
indicateur de socketB
.
Autres systèmes
Dans le cas où votre système n'est pas répertorié 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. De plus, si vous pensez que mes résultats sont erronés , veuillez d'abord exécuter ce programme avant de poster des commentaires et éventuellement de fausses déclarations.
Tout ce dont le code a besoin pour construire est un peu d'API POSIX (pour les parties réseau) et un compilateur C99 (en fait, la plupart des compilateurs non C99 fonctionneront aussi longtemps qu'ils le proposent inttypes.h
et stdbool.h
;gcc
deux sont pris en charge bien avant d'offrir un support C99 complet) .
Tout ce que le programme doit exécuter est qu'au moins une interface de votre système (autre que l'interface locale) a une adresse IP attribuée et qu'une route par défaut est définie qui 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
- Sockets normaux, sockets d'écoute (serveur), sockets multicast
SO_REUSEADDR
défini sur socket1, socket2 ou les deux sockets
SO_REUSEPORT
défini sur socket1, socket2 ou les deux sockets
- Toutes les combinaisons d'adresses que vous pouvez créer à partir de
0.0.0.0
(caractère générique), 127.0.0.1
(adresse spécifique) et de 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 un joli tableau. Il fonctionnera également sur les systèmes qui ne savent pas SO_REUSEPORT
, auquel cas cette option n'est tout simplement pas testée.
Ce que le programme ne peut pas tester facilement, c'est comment SO_REUSEADDR
agit sur les socketsTIME_WAIT
état car il est très difficile de forcer et de conserver un socket dans cet état. Heureusement, la plupart des systèmes d'exploitation semblent se comporter simplement 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).
INADDR_ANY
ne lie pas les adresses locales existantes, mais aussi toutes les futures.listen
crée certainement des sockets avec le même protocole exact, l'adresse locale et le port local, même si vous avez dit que ce n'était pas possible.