GNU/Linux >> LINUX-Kenntnisse >  >> Linux

Wie unterscheiden sich SO_REUSEADDR und SO_REUSEPORT?

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:

  1. 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 ohne SO_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 Sie SO_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.

  2. 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 kein SO_REUSEPORT gab vor 3.9 das Verhalten von SO_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:

  1. 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.

  2. 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 von accept() 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 dann SO_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 sockets
  • SO_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 nur 224.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)


Linux
  1. So verwalten und listen Sie Dienste in Linux auf

  2. $bashpid und $$ unterscheiden sich in manchen Fällen?

  3. Was ist Podman und wie unterscheidet es sich von Docker?

  4. Gewusst wie:Socket-Programmierung in Python

  5. Wie gehe ich mit den Linux-Socket-Revents POLLERR, POLLHUP und POLLNVAL um?

Wie installiere und verwende ich den Linux-Bildschirm?

So benennen Sie Dateien und Verzeichnisse in Linux um

So komprimieren Sie Dateien und Verzeichnisse unter Linux

So installieren und konfigurieren Sie SeedDMS

So installieren und konfigurieren Sie Grafana

Wie vernetzt man Ubuntu und Windows 10?