Willkommen in der wunderbaren Welt der Portabilität ... oder besser gesagt des Fehlens davon. Bevor wir diese beiden Optionen im Detail analysieren und genauer betrachten, wie verschiedene Betriebssysteme damit umgehen, sollte beachtet werden, dass die BSD-Socket-Implementierung die Mutter aller Socket-Implementierungen ist. Im Grunde genommen haben alle anderen Systeme irgendwann die BSD-Socket-Implementierung (oder zumindest ihre Schnittstellen) kopiert und dann begonnen, sie selbst weiterzuentwickeln. Natürlich wurde auch die BSD-Socket-Implementierung zur gleichen Zeit weiterentwickelt, und daher erhielten Systeme, die sie später kopierten, Funktionen, die Systemen, die sie früher kopierten, fehlten. Das Verständnis der BSD-Socket-Implementierung ist der Schlüssel zum Verständnis aller anderen Socket-Implementierungen, also sollten Sie darüber lesen, auch wenn Sie nicht daran interessiert sind, jemals Code für ein BSD-System zu schreiben.
Es gibt ein paar Grundlagen, die Sie kennen sollten, bevor wir uns diese beiden Optionen ansehen. Eine TCP/UDP-Verbindung wird durch ein Tupel aus fünf Werten identifiziert:
{<protocol>, <src addr>, <src port>, <dest addr>, <dest port>}
Jede eindeutige Kombination dieser Werte identifiziert eine Verbindung. Dadurch dürfen keine zwei Verbindungen die gleichen fünf Werte haben, sonst könnte das System diese Verbindungen nicht mehr unterscheiden.
Das Protokoll eines Sockets wird gesetzt, wenn ein Socket mit dem socket()
erstellt wird Funktion. Quelladresse und Port werden mit der bind()
eingestellt Funktion. Zieladresse und Port werden mit connect()
eingestellt Funktion. Da UDP ein verbindungsloses Protokoll ist, können UDP-Sockets verwendet werden, ohne sie zu verbinden. Dennoch ist es erlaubt, sie zu verbinden und in einigen Fällen sehr vorteilhaft für Ihren Code und das allgemeine Anwendungsdesign. Im verbindungslosen Modus werden UDP-Sockets, die nicht explizit gebunden wurden, wenn Daten zum ersten Mal darüber gesendet werden, normalerweise automatisch vom System gebunden, da ein ungebundener UDP-Socket keine (Antwort-)Daten empfangen kann. Dasselbe gilt für einen ungebundenen TCP-Socket, er wird automatisch gebunden, bevor er verbunden wird.
Wenn Sie einen Socket explizit binden, ist es möglich, ihn an Port 0
zu binden , was "jeder Port" bedeutet. Da ein Socket nicht wirklich an alle vorhandenen Ports gebunden werden kann, muss das System in diesem Fall selbst einen bestimmten Port auswählen (normalerweise aus einem vordefinierten, betriebssystemspezifischen Bereich von Quellports). Ein ähnlicher Platzhalter existiert für die Quelladresse, die "jede Adresse" sein kann (0.0.0.0
im Fall von IPv4 und ::
bei IPv6). Im Gegensatz zu Ports kann ein Socket wirklich an "jede Adresse" gebunden werden, was "alle Quell-IP-Adressen aller lokalen Schnittstellen" bedeutet. Wenn der Socket später verbunden wird, muss das System eine bestimmte Quell-IP-Adresse auswählen, da ein Socket nicht verbunden und gleichzeitig an eine beliebige lokale IP-Adresse gebunden werden kann. Abhängig von der Zieladresse und dem Inhalt der Routing-Tabelle wählt das System eine geeignete Quelladresse aus und ersetzt die „beliebige“ Bindung durch eine Bindung an die gewählte Quell-IP-Adresse.
Standardmäßig können keine zwei Sockets an dieselbe Kombination aus Quelladresse und Quellport gebunden werden. Solange der Quellport unterschiedlich ist, ist die Quelladresse eigentlich irrelevant. Bindung socketA
bis ipA:portA
und socketB
bis ipB:portB
ist immer möglich, wenn ipA != ipB
gilt, auch wenn portA == portB
. Z.B. socketA
gehört zu einem FTP-Serverprogramm und ist an 192.168.0.1:21
gebunden und socketB
gehört zu einem anderen FTP-Serverprogramm und ist an 10.0.0.1:21
gebunden , sind beide Bindungen erfolgreich. Beachten Sie jedoch, dass ein Socket lokal an "jede Adresse" gebunden sein kann. Wenn ein Socket an 0.0.0.0:21
gebunden ist , wird es gleichzeitig an alle vorhandenen lokalen Adressen gebunden und in diesem Fall kann kein anderer Socket an Port 21
gebunden werden , unabhängig davon, an welche spezifische IP-Adresse es zu binden versucht, als 0.0.0.0
Konflikte mit allen vorhandenen lokalen IP-Adressen.
Alles, was bisher gesagt wurde, ist für alle wichtigen Betriebssysteme ziemlich gleich. Die Dinge werden betriebssystemspezifisch, wenn die Wiederverwendung von Adressen ins Spiel kommt. Wir beginnen mit BSD, da es, wie ich oben sagte, die Mutter aller Socket-Implementierungen ist.
BSD
SO_REUSEADDR
Wenn SO_REUSEADDR
auf einem Socket aktiviert ist, bevor es gebunden wird, kann der Socket erfolgreich gebunden werden, es sei denn, es gibt einen Konflikt mit einem anderen Socket, der an exakt gebunden ist dieselbe Kombination aus Quelladresse und Port. Jetzt fragen Sie sich vielleicht, wie ist das anders als früher? Das Schlüsselwort ist "genau". SO_REUSEADDR
ändert hauptsächlich die Art und Weise, wie Wildcard-Adressen ("beliebige IP-Adresse") bei der Suche nach Konflikten behandelt werden.
Ohne SO_REUSEADDR
, Bindung socketA
bis 0.0.0.0:21
und dann socketB
binden bis 192.168.0.1:21
schlägt fehl (mit Fehler EADDRINUSE
), da 0.0.0.0 "jede lokale IP-Adresse" bedeutet, werden daher alle lokalen IP-Adressen von diesem Socket als verwendet betrachtet, einschließlich 192.168.0.1
, zu. Mit SO_REUSEADDR
es wird seit 0.0.0.0
erfolgreich sein und 192.168.0.1
sind nicht genau die gleiche Adresse, einer ist ein Platzhalter für alle lokalen Adressen und der andere ist eine sehr spezifische lokale Adresse. Beachten Sie, dass die obige Aussage unabhängig von der Reihenfolge socketA
wahr ist und socketB
gebunden sind; ohne SO_REUSEADDR
es wird immer fehlschlagen, mit SO_REUSEADDR
es wird immer gelingen.
Um Ihnen einen besseren Überblick zu geben, erstellen wir hier eine Tabelle und listen alle möglichen Kombinationen auf:
SO_REUSEADDR socketA socketB Result --------------------------------------------------------------------- ON/OFF 192.168.0.1:21 192.168.0.1:21 Error (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 Error (EADDRINUSE) OFF 192.168.1.0:21 0.0.0.0:21 Error (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 Error (EADDRINUSE)
Die obige Tabelle geht davon aus, dass socketA
wurde bereits erfolgreich an die für socketA
angegebene Adresse gebunden , dann socketB
erstellt wird, erhält entweder SO_REUSEADDR
gesetzt oder nicht, und wird schließlich an die für socketB
angegebene Adresse gebunden . Result
ist das Ergebnis der Bindeoperation für socketB
. Wenn in der ersten Spalte ON/OFF
steht , der Wert von SO_REUSEADDR
ist für das Ergebnis irrelevant.
Okay, SO_REUSEADDR
wirkt sich auf Wildcard-Adressen aus, gut zu wissen. Doch das ist nicht die einzige Wirkung, die es hat. Es gibt noch einen weiteren bekannten Effekt, der auch der Grund ist, warum die meisten Leute SO_REUSEADDR
verwenden in erster Linie in Serverprogrammen. Für die andere wichtige Verwendung dieser Option müssen wir uns die Funktionsweise des TCP-Protokolls genauer ansehen.
Wenn ein TCP-Socket geschlossen wird, wird normalerweise ein 3-Wege-Handshake durchgeführt; die Sequenz heißt FIN-ACK
. Das Problem hierbei ist, dass das letzte ACK dieser Sequenz auf der anderen Seite angekommen sein kann oder nicht angekommen ist und nur wenn dies der Fall ist, betrachtet die andere Seite den Socket auch als vollständig geschlossen. Um die Wiederverwendung einer Kombination aus Adresse und Port zu verhindern, die von einem entfernten Peer möglicherweise noch als offen angesehen wird, betrachtet das System einen Socket nicht sofort als tot, nachdem das letzte ACK
gesendet wurde sondern versetzt den Socket stattdessen in einen Zustand, der allgemein als TIME_WAIT
bezeichnet wird . Er kann minutenlang in diesem Zustand bleiben (systemabhängige Einstellung). Auf den meisten Systemen können Sie diesen Zustand umgehen, indem Sie das Verweilen aktivieren und eine Verweilzeit von null1 festlegen, aber es gibt keine Garantie dafür, dass dies immer möglich ist, dass das System diese Anfrage immer erfüllt, und selbst wenn das System sie erfüllt, verursacht dies die Socket durch einen Reset zu schließen (RST
), was nicht immer eine gute Idee ist. Um mehr über die Verweilzeit zu erfahren, sieh dir meine Antwort zu diesem Thema an.
Die Frage ist, wie behandelt das System einen Socket im Zustand TIME_WAIT
? Wenn SO_REUSEADDR
nicht gesetzt ist, ein Socket im Zustand TIME_WAIT
gilt immer noch als an die Quelladresse und den Port gebunden, und jeder Versuch, einen neuen Socket an dieselbe Adresse und denselben Port zu binden, schlägt fehl, bis der Socket wirklich geschlossen wurde. Erwarten Sie also nicht, dass Sie die Quelladresse eines Sockets sofort nach dem Schließen erneut binden können. In den meisten Fällen wird dies fehlschlagen. Wenn jedoch SO_REUSEADDR
für den Socket gesetzt ist, den Sie zu binden versuchen, ein anderer Socket, der an dieselbe Adresse und denselben Port im Zustand TIME_WAIT
gebunden ist wird einfach ignoriert, schließlich ist es schon "halb tot", und Ihr Socket kann sich problemlos an genau dieselbe Adresse binden. Dabei spielt es keine Rolle, dass der andere Socket möglicherweise exakt dieselbe Adresse und denselben Port hat. Beachten Sie, dass das Binden eines Sockets an genau dieselbe Adresse und denselben Port wie ein sterbender Socket in TIME_WAIT
Der Zustand kann unerwartete und normalerweise unerwünschte Nebenwirkungen haben, falls die andere Buchse noch "bei der Arbeit" ist, aber das würde den Rahmen dieser Antwort sprengen, und glücklicherweise sind diese Nebenwirkungen in der Praxis eher selten.
Es gibt noch eine letzte Sache, die Sie über SO_REUSEADDR
wissen sollten . Alles, was oben geschrieben wurde, funktioniert, solange der Socket, an den Sie binden möchten, die Wiederverwendung von Adressen aktiviert hat. Es ist nicht erforderlich, dass der andere Socket bereits gebunden ist oder sich in einem TIME_WAIT
befindet State, hatte dieses Flag auch gesetzt, als es gebunden wurde. Der Code, der entscheidet, ob die Bindung erfolgreich ist oder fehlschlägt, untersucht nur den SO_REUSEADDR
Flag des in bind()
eingespeisten Sockets aufrufen, für alle anderen überprüften Sockets wird dieses Flag nicht einmal beachtet.
SO_REUSEPORT
SO_REUSEPORT
ist, was die meisten Leute SO_REUSEADDR
erwarten würden sein. Grundsätzlich SO_REUSEPORT
ermöglicht es Ihnen, eine beliebige Anzahl von Sockets genau zu binden dieselbe Quelladresse und denselben Port, solange alle Frühere gebundene Sockets hatten auch SO_REUSEPORT
gesetzt, bevor sie gebunden wurden. Wenn der erste Socket, der an eine Adresse und einen Port gebunden ist, nicht SO_REUSEPORT
hat gesetzt, kann kein anderer Socket an genau dieselbe Adresse und denselben Port gebunden werden, unabhängig davon, ob dieser andere Socket SO_REUSEPORT
hat gesetzt oder nicht, bis der erste Socket seine Bindung wieder löst. Anders bei SO_REUESADDR
die Codebehandlung SO_REUSEPORT
überprüft nicht nur, ob der aktuell gebundene Socket SO_REUSEPORT
hat gesetzt, aber es wird auch überprüft, ob der Socket mit einer widersprüchlichen Adresse und einem Port SO_REUSEPORT
hatte gesetzt, als es gebunden wurde.
SO_REUSEPORT
bedeutet nicht SO_REUSEADDR
. Das heißt, wenn ein Socket nicht SO_REUSEPORT
hatte gesetzt, als es gebunden wurde und ein anderer Socket SO_REUSEPORT
hat gesetzt, wenn es an genau dieselbe Adresse und denselben Port gebunden ist, schlägt die Bindung fehl, was erwartet wird, aber es schlägt auch fehl, wenn der andere Socket bereits stirbt und sich in TIME_WAIT
befindet Zustand. Um einen Socket an die gleichen Adressen und Ports binden zu können wie ein anderer Socket in TIME_WAIT
Zustand erfordert entweder SO_REUSEADDR
auf diesem Socket oder SO_REUSEPORT
gesetzt werden muss beide eingestellt sein Steckdosen, bevor Sie sie binden. Natürlich dürfen beide gesetzt werden, SO_REUSEPORT
und SO_REUSEADDR
, auf einem Socket.
Zu SO_REUSEPORT
gibt es nicht mehr viel zu sagen abgesehen davon, dass es später als SO_REUSEADDR
hinzugefügt wurde , deshalb werden Sie es in vielen Socket-Implementierungen anderer Systeme nicht finden, die den BSD-Code "gegabelt" haben, bevor diese Option hinzugefügt wurde, und dass es zuvor keine Möglichkeit gab, zwei Sockets an genau dieselbe Socket-Adresse in BSD zu binden Option.
Connect() Gibt EADDRINUSE zurück?
Die meisten Leute kennen das bind()
kann mit dem Fehler EADDRINUSE
fehlschlagen , wenn Sie jedoch anfangen, mit der Wiederverwendung von Adressen herumzuspielen, können Sie auf die seltsame Situation stoßen, dass connect()
schlägt auch mit diesem Fehler fehl. Wie kann das sein? Wie kann eine entfernte Adresse, die connect zu einem Socket hinzufügt, bereits verwendet werden? Das Verbinden mehrerer Sockets mit genau derselben Remote-Adresse war noch nie ein Problem, also was läuft hier schief?
Wie ich ganz oben in meiner Antwort sagte, wird eine Verbindung durch ein Tupel aus fünf Werten definiert, erinnern Sie sich? Und ich habe auch gesagt, dass diese fünf Werte eindeutig sein müssen, sonst kann das System zwei Verbindungen nicht mehr unterscheiden, oder? Nun, mit der Wiederverwendung von Adressen können Sie zwei Sockets desselben Protokolls an dieselbe Quelladresse und denselben Port binden. Das bedeutet, dass drei dieser fünf Werte für diese beiden Steckdosen bereits gleich sind. Wenn Sie nun versuchen, diese beiden Sockets auch mit derselben Zieladresse und demselben Port zu verbinden, würden Sie zwei verbundene Sockets erzeugen, deren Tupel absolut identisch sind. Das kann nicht funktionieren, zumindest nicht für TCP-Verbindungen (UDP-Verbindungen sind ohnehin keine echten Verbindungen). Wenn Daten für eine der beiden Verbindungen ankamen, konnte das System nicht erkennen, zu welcher Verbindung die Daten gehören. Zumindest die Zieladresse oder der Zielport müssen für beide Verbindungen unterschiedlich sein, damit das System problemlos erkennen kann, zu welcher Verbindung eingehende Daten gehören.
Wenn Sie also zwei Sockets desselben Protokolls an dieselbe Quelladresse und denselben Port binden und versuchen, sie beide mit derselben Zieladresse und demselben Port zu verbinden, connect()
wird tatsächlich mit dem Fehler EADDRINUSE
fehlschlagen für den zweiten Socket, den Sie zu verbinden versuchen, was bedeutet, dass bereits ein Socket mit einem identischen Tupel von fünf Werten verbunden ist.
Multicast-Adressen
Die meisten Leute ignorieren die Tatsache, dass Multicast-Adressen existieren, aber sie existieren. Während Unicast-Adressen für die Eins-zu-eins-Kommunikation verwendet werden, werden Multicast-Adressen für die Eins-zu-viele-Kommunikation verwendet. Die meisten Leute wurden auf Multicast-Adressen aufmerksam, als sie von IPv6 erfuhren, aber Multicast-Adressen existierten auch in IPv4, obwohl diese Funktion im öffentlichen Internet nie weit verbreitet war.
Die Bedeutung von SO_REUSEADDR
ändert sich für Multicast-Adressen, da mehrere Sockets an genau dieselbe Kombination aus Quell-Multicast-Adresse und Port gebunden werden können. Mit anderen Worten, für Multicast-Adressen SO_REUSEADDR
verhält sich genauso wie SO_REUSEPORT
für Unicast-Adressen. Tatsächlich behandelt der Code SO_REUSEADDR
und SO_REUSEPORT
identisch für Multicast-Adressen, das heißt, Sie könnten sagen, dass SO_REUSEADDR
impliziert SO_REUSEPORT
für alle Multicast-Adressen und umgekehrt.
FreeBSD/OpenBSD/NetBSD
All dies sind ziemlich späte Abzweigungen des ursprünglichen BSD-Codes, deshalb bieten alle drei die gleichen Optionen wie BSD und sie verhalten sich auch genauso wie in BSD.
macOS (MacOS X)
Im Kern ist macOS einfach ein UNIX im BSD-Stil mit dem Namen „Darwin ", basierend auf einem ziemlich späten Fork des BSD-Codes (BSD 4.3), der dann später sogar mit der (damals aktuellen) FreeBSD 5-Codebasis für das Mac OS 10.3-Release neu synchronisiert wurde, damit Apple gewinnen konnte volle POSIX-Kompatibilität (macOS ist POSIX-zertifiziert). Trotz eines Mikrokernels im Kern ("Mach "), der Rest des Kernels ("XNU ") ist im Grunde nur ein BSD-Kernel, und deshalb bietet macOS die gleichen Optionen wie BSD und sie verhalten sich auch genauso wie in BSD.
iOS / watchOS / tvOS
iOS ist nur ein MacOS-Fork mit einem leicht modifizierten und getrimmten Kernel, einem etwas abgespeckten User-Space-Toolset und einem etwas anderen Standard-Framework-Set. watchOS und tvOS sind iOS-Forks, die noch weiter abgespeckt sind (insbesondere watchOS). Meines Wissens nach verhalten sie sich alle genauso wie macOS.
Linux
Linux <3.9
Vor Linux 3.9 nur die Option SO_REUSEADDR
existierte. Diese Option verhält sich im Allgemeinen genauso wie in BSD, mit zwei wichtigen Ausnahmen:
-
Solange ein lauschender (Server-)TCP-Socket an einen bestimmten Port gebunden ist, wird der
SO_REUSEADDR
Die Option wird für alle Sockets, die auf diesen Port abzielen, vollständig ignoriert. Das Binden eines zweiten Sockets an denselben Port ist nur möglich, wenn es auch in BSD ohneSO_REUSEADDR
möglich war einstellen. Z.B. Sie können nicht an eine Wildcard-Adresse binden und dann an eine spezifischere oder umgekehrt, beides ist in BSD möglich, wenn SieSO_REUSEADDR
setzen . Was Sie tun können, ist, dass Sie an denselben Port und zwei verschiedene Adressen ohne Platzhalter binden können, da dies immer zulässig ist. In dieser Hinsicht ist Linux restriktiver als BSD. -
Die zweite Ausnahme ist, dass sich diese Option für Client-Sockets genau wie
SO_REUSEPORT
verhält in BSD, solange beide dieses Flag gesetzt hatten, bevor sie gebunden wurden. Der Grund dafür war einfach, dass es wichtig ist, mehrere Sockets für verschiedene Protokolle an genau dieselbe UDP-Socket-Adresse binden zu können, und weil es früher keinSO_REUSEPORT
gab vor 3.9 das Verhalten vonSO_REUSEADDR
wurde entsprechend geändert, um diese Lücke zu schließen. In dieser Hinsicht ist Linux weniger restriktiv als BSD.
Linux>=3.9
Linux 3.9 hat die Option SO_REUSEPORT
hinzugefügt auch zu Linux. Diese Option verhält sich genau wie die Option in BSD und ermöglicht das Binden an genau die gleiche Adresse und Portnummer, solange alle Sockets diese Option gesetzt haben, bevor sie gebunden werden.
Dennoch gibt es noch zwei Unterschiede zu SO_REUSEPORT
auf anderen Systemen:
-
Um "Port-Hijacking" zu verhindern, gibt es eine spezielle Einschränkung:Alle Sockets, die dieselbe Kombination aus Adresse und Port teilen möchten, müssen zu Prozessen gehören, die dieselbe effektive Benutzer-ID teilen! Ein Benutzer kann also keine Ports eines anderen Benutzers "stehlen". Dies ist eine besondere Magie, um das fehlende
SO_EXCLBIND
etwas zu kompensieren /SO_EXCLUSIVEADDRUSE
Flaggen. -
Zusätzlich führt der Kernel einige "besondere Magie" für
SO_REUSEPORT
aus Sockets, die in anderen Betriebssystemen nicht zu finden ist:Bei UDP-Sockets versucht es, Datagramme gleichmäßig zu verteilen, bei TCP-Listening-Sockets versucht es, eingehende Verbindungsanforderungen zu verteilen (diejenigen, die durch Aufrufen vonaccept()
akzeptiert werden ) gleichmäßig über alle Sockets, die dieselbe Kombination aus Adresse und Port teilen. Daher kann eine Anwendung problemlos denselben Port in mehreren untergeordneten Prozessen öffnen und dannSO_REUSEPORT
verwenden um ein sehr günstiges Load-Balancing zu erhalten.
Android
Auch wenn sich das gesamte Android-System etwas von den meisten Linux-Distributionen unterscheidet, arbeitet im Kern ein leicht modifizierter Linux-Kernel, daher sollte alles, was für Linux gilt, auch für Android gelten.
Windows
Windows kennt nur den SO_REUSEADDR
Option gibt es kein SO_REUSEPORT
. Einstellung SO_REUSEADDR
auf einem Socket in Windows verhält sich wie das Setzen von SO_REUSEPORT
und SO_REUSEADDR
auf einem Socket in BSD, mit einer Ausnahme:
Vor Windows 2003 ein Socket mit SO_REUSEADDR
immer an genau dieselbe Quelladresse und denselben Port gebunden werden wie ein bereits gebundener Socket, selbst wenn beim anderen Socket diese Option nicht gesetzt war, als er gebunden wurde . Dieses Verhalten ermöglichte es einer Anwendung, den verbundenen Port einer anderen Anwendung zu "stehlen". Unnötig zu erwähnen, dass dies erhebliche Auswirkungen auf die Sicherheit hat!
Microsoft hat das erkannt und eine weitere wichtige Socket-Option hinzugefügt:SO_EXCLUSIVEADDRUSE
. Einstellung SO_EXCLUSIVEADDRUSE
auf einem Socket stellt sicher, dass bei erfolgreicher Bindung die Kombination aus Quelladresse und Port ausschließlich diesem Socket gehört und kein anderer Socket sich an sie binden kann, nicht einmal wenn es SO_REUSEADDR
hat eingestellt.
Dieses Standardverhalten wurde zuerst in Windows 2003 geändert, Microsoft nennt das "Enhanced Socket Security" (komischer Name für ein Verhalten, das auf allen anderen wichtigen Betriebssystemen standardmäßig ist). Für weitere Details besuchen Sie einfach diese Seite. Es gibt drei Tabellen:Die erste zeigt das klassische Verhalten (immer noch in Verwendung bei Verwendung von Kompatibilitätsmodi!), die zweite zeigt das Verhalten von Windows 2003 und höher, wenn bind()
Anrufe werden von demselben Benutzer getätigt, und der dritte, wenn der bind()
Anrufe werden von verschiedenen Benutzern getätigt.
Solaris
Solaris ist der Nachfolger von SunOS. SunOS basierte ursprünglich auf einem Fork von BSD, SunOS 5 und basierte später auf einem Fork von SVR4, jedoch ist SVR4 ein Zusammenschluss von BSD, System V und Xenix, sodass Solaris bis zu einem gewissen Grad auch ein BSD-Fork ist, und a ziemlich früh. Folglich kennt Solaris nur SO_REUSEADDR
, gibt es kein SO_REUSEPORT
. Die SO_REUSEADDR
verhält sich ziemlich genauso wie in BSD. Soweit ich weiß, gibt es keine Möglichkeit, das gleiche Verhalten wie SO_REUSEPORT
zu erhalten in Solaris bedeutet dies, dass es nicht möglich ist, zwei Sockets an genau dieselbe Adresse und denselben Port zu binden.
Ähnlich wie Windows hat Solaris die Möglichkeit, einem Socket eine exklusive Bindung zu geben. Diese Option heißt SO_EXCLBIND
. Wenn diese Option vor dem Binden auf einen Socket gesetzt wird, wird SO_REUSEADDR
gesetzt auf einem anderen Socket hat keine Auswirkung, wenn die beiden Sockets auf einen Adresskonflikt getestet werden. Z.B. wenn socketA
ist an eine Platzhalteradresse und socketB
gebunden hat SO_REUSEADDR
aktiviert und ist an eine Adresse ohne Platzhalter und denselben Port wie socketA
gebunden , wird diese Bindung normalerweise erfolgreich sein, es sei denn socketA
hatte SO_EXCLBIND
aktiviert, in diesem Fall schlägt es unabhängig von SO_REUSEADDR
fehl Flag von socketB
.
Andere Systeme
Falls Ihr System oben nicht aufgeführt ist, habe ich ein kleines Testprogramm geschrieben, mit dem Sie herausfinden können, wie Ihr System mit diesen beiden Optionen umgeht. Auch wenn Sie denken, dass meine Ergebnisse falsch sind , führen Sie dieses Programm bitte zuerst aus, bevor Sie Kommentare posten und möglicherweise falsche Behauptungen aufstellen.
Alles, was der Code zum Erstellen benötigt, ist ein wenig POSIX-API (für die Netzwerkteile) und ein C99-Compiler (tatsächlich funktionieren die meisten Nicht-C99-Compiler so gut, wie sie inttypes.h
anbieten und stdbool.h
; z.B. gcc
unterstützte beide, lange bevor C99 volle Unterstützung bot).
Alles, was das Programm ausführen muss, ist, dass mindestens einer Schnittstelle in Ihrem System (außer der lokalen Schnittstelle) eine IP-Adresse zugewiesen ist und dass eine Standardroute festgelegt ist, die diese Schnittstelle verwendet. Das Programm sammelt diese IP-Adresse und verwendet sie als zweite "spezifische Adresse".
Es testet alle denkbaren Kombinationen:
- TCP- und UDP-Protokoll
- Normale Sockets, Listen (Server)-Sockets, Multicast-Sockets
SO_REUSEADDR
set on socket1, socket2, or both socketsSO_REUSEPORT
set on socket1, socket2, or both sockets- Alle Adresskombinationen, die Sie aus
0.0.0.0
machen können (Platzhalter),127.0.0.1
(spezifische Adresse) und die zweite spezifische Adresse, die an Ihrer primären Schnittstelle gefunden wird (für Multicast ist es nur224.1.2.3
in allen Tests)
und druckt die Ergebnisse in einer schönen Tabelle. Es funktioniert auch auf Systemen, die SO_REUSEPORT
nicht kennen , in diesem Fall wird diese Option einfach nicht getestet.
Was das Programm nicht einfach testen kann, ist, wie SO_REUSEADDR
wirkt auf Sockets in TIME_WAIT
Zustand, da es sehr schwierig ist, einen Socket in diesem Zustand zu erzwingen und zu halten. Glücklicherweise scheinen sich die meisten Betriebssysteme hier einfach wie BSD zu verhalten, und die meiste Zeit können Programmierer die Existenz dieses Zustands einfach ignorieren.
Hier ist der Code (ich kann ihn hier nicht einfügen, Antworten haben eine Größenbeschränkung und der Code würde diese Antwort über die Grenze hinausschieben).
Meckis Antwort ist absolut perfekt, aber es lohnt sich hinzuzufügen, dass FreeBSD auch SO_REUSEPORT_LB
unterstützt , das SO_REUSEPORT
von Linux nachahmt Verhalten - es gleicht die Last aus; siehe setsockopt(2)