Czym różnią się SO REUSEADDR I SO REUSEPORT?

Dokumentacja man pages i programatora dla opcji gniazd SO_REUSEADDR i SO_REUSEPORT są różne dla różnych systemów operacyjnych i często bardzo mylące. Niektóre systemy operacyjne nie mają nawet opcji SO_REUSEPORT. Sieć jest pełna sprzecznych informacji na ten temat i często można znaleźć informacje, które są prawdziwe tylko dla jednej implementacji gniazda określonego systemu operacyjnego, które mogą nawet nie być wyraźnie wymienione w tekście.

Więc jak czy SO_REUSEADDR różni się od SO_REUSEPORT?

Czy systemy Bez SO_REUSEPORT są bardziej ograniczone?

A jakie dokładnie jest oczekiwane zachowanie, jeśli używam jednego z nich na różnych systemach operacyjnych?

Author: jww, 2013-01-17

2 answers

Witaj w cudownym świecie przenośności... a raczej jej brak. Zanim zaczniemy szczegółowo analizować te dwie opcje i przyjrzeć się, jak różne systemy operacyjne je obsługują, należy zauważyć, że implementacja gniazd BSD jest matką wszystkich implementacji gniazd. Zasadniczo wszystkie inne systemy skopiowały implementację gniazd BSD w pewnym momencie (lub przynajmniej jej interfejsy), a następnie zaczęły ją rozwijać na własną rękę. Oczywiście Gniazdo BSD implementacja była ewoluowana również w tym samym czasie, a więc systemy, które skopiowały ją później, otrzymały funkcje, których brakowało w systemach, które skopiowały ją wcześniej. Zrozumienie implementacji gniazd BSD jest kluczem do zrozumienia wszystkich innych implementacji gniazd, więc powinieneś o tym przeczytać, nawet jeśli nie zależy ci na pisaniu kodu dla systemu BSD.

Jest kilka podstaw, które powinieneś znać, zanim przyjrzymy się tym dwóm opcjom. Połączenie TCP / UDP jest identyfikowane przez krotkę pięć wartości:

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

Każda unikalna kombinacja tych wartości identyfikuje połączenie. W rezultacie żadne dwa połączenia nie mogą mieć tych samych pięciu wartości, w przeciwnym razie system nie byłby w stanie rozróżnić tych połączeń.

Protokół gniazda jest ustawiany, gdy gniazdo jest tworzone za pomocą funkcji socket(). Adres źródłowy i port są ustawiane za pomocą funkcji bind(). Adres docelowy i port są ustawiane za pomocą funkcji connect(). Ponieważ UDP jest protokół bezpołączeniowy, gniazda UDP mogą być używane bez ich podłączania. Jednak dozwolone jest ich łączenie, a w niektórych przypadkach bardzo korzystne dla kodu i ogólnego projektu aplikacji. W trybie bezpołączeniowym gniazda UDP, które nie były bezpośrednio związane, gdy dane są wysyłane przez nie po raz pierwszy, są zwykle automatycznie wiązane przez system, ponieważ niezwiązane Gniazdo UDP nie może odbierać żadnych danych (odpowiedzi). To samo dotyczy niezwiązanego gniazda TCP, jest ono automatycznie związane, zanim zostanie połączone.

Jeśli bezpośrednio połączysz Gniazdo, możliwe jest powiązanie go z portem 0, co oznacza "dowolny port". Ponieważ gniazdo nie może być w rzeczywistości połączone ze wszystkimi istniejącymi portami, system będzie musiał sam wybrać konkretny port (zwykle z predefiniowanego, specyficznego dla systemu operacyjnego zakresu portów źródłowych). Podobny symbol wieloznaczny istnieje dla adresu źródłowego, który może być "dowolnym adresem" (0.0.0.0 w przypadku IPv4 i :: W przypadku IPv6). Inaczej niż w przypadku portów, gniazdo może być naprawdę związane do "dowolnego adresu", co oznacza "wszystkie źródłowe adresy IP wszystkich interfejsów lokalnych". Jeśli gniazdo jest podłączone później, system musi wybrać konkretny źródłowy adres IP, ponieważ gniazdo nie może być Podłączone i jednocześnie być powiązane z dowolnym lokalnym adresem IP. W zależności od adresu docelowego i zawartości tabeli routingu, system wybierze odpowiedni adres źródłowy i zastąpi powiązanie " any " powiązaniem z wybranym źródłowym adresem IP.

Domyślnie nie ma dwóch gniazda mogą być powiązane z tą samą kombinacją adresu źródłowego i portu źródłowego. Tak długo, jak port źródłowy jest inny, adres źródłowy jest w rzeczywistości nieistotny. Powiązanie socketA z A:X i socketB z B:Y, gdzie A i B są adresami, a X i Y są portami, jest zawsze możliwe tak długo, jak X != Y jest prawdziwe. Jednak nawet jeśli X == Y, Wiązanie jest możliwe tak długo, jak długo A != B jest prawdziwe. Np. socketA należy do programu serwera FTP i jest związany z 192.168.0.1:21 i socketB należy do innego programu serwera FTP i jest związany z 10.0.0.1:21, oba powiązania powiodą się. Należy jednak pamiętać, że gniazdo może być lokalnie związane z "dowolnym adresem". Jeśli gniazdo jest przypisane do 0.0.0.0:21, jest przypisane do wszystkich istniejących adresów lokalnych w tym samym czasie i w takim przypadku żadne inne gniazdo nie może być przypisane do portu 21, niezależnie od tego, z którym konkretnym adresem IP próbuje się powiązać, ponieważ 0.0.0.0 koliduje ze wszystkimi istniejącymi lokalnymi adresami IP.

Wszystko powiedziane do tej pory jest prawie równe dla wszystkich głównych system operacyjny. Wszystko zaczyna być specyficzne dla systemu operacyjnego, gdy ponowne użycie adresu wchodzi w grę. Zaczynamy od BSD, ponieważ jak powiedziałem powyżej, jest matką wszystkich implementacji socket.

BSD

SO_REUSEADDR

Jeśli {[26] } jest włączone na gnieździe przed jego powiązaniem, gniazdo może być z powodzeniem powiązane, chyba że istnieje konflikt z innym gniazdem związanym z dokładnie tą samą kombinacją adresu źródłowego i portu. Teraz możesz się zastanawiać, jak to się różni niż wcześniej? Słowo kluczowe to "dokładnie". SO_REUSEADDR głównie zmienia sposób, w jaki adresy wieloznaczne ("dowolny adres IP") są traktowane podczas wyszukiwania konfliktów.

Bez SO_REUSEADDR, powiązanie socketA z 0.0.0.0:21, a następnie powiązanie socketB z 192.168.0.1:21 zakończy się niepowodzeniem (z błędem EADDRINUSE), ponieważ 0.0.0.0 oznacza "dowolny lokalny adres IP", więc wszystkie lokalne adresy IP są uważane za używane przez to gniazdo i obejmuje to również 192.168.0.1. Z SO_REUSEADDR uda się, ponieważ 0.0.0.0 i 192.168.0.1 nie do końca ten sam adres, jeden jest symbolem wieloznacznym dla wszystkich adresów lokalnych, a drugi jest bardzo specyficznym adresem lokalnym. Zauważ, że powyższe stwierdzenie jest prawdziwe niezależnie od tego, w jakiej kolejności socketA i socketB są związane; bez SO_REUSEADDR zawsze się nie powiedzie, z SO_REUSEADDR zawsze się powiedzie.

Aby dać ci lepszy przegląd, zróbmy tabelę tutaj i wymień wszystkie możliwe kombinacje:

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)

Powyższa tabela zakłada, że socketA została już pomyślnie powiązana z adresem podanym dla socketA, następnie tworzony jest socketB, albo otrzymuje SO_REUSEADDR ustawiony lub nie, i na koniec jest związany z adresem podanym dla socketB. Result jest wynikiem operacji bind dla socketB. Jeśli pierwsza kolumna mówi ON/OFF, wartość {[26] } nie ma znaczenia dla wyniku.

Dobra, SO_REUSEADDR ma wpływ na adresy wieloznaczne, dobrze wiedzieć. Jednak nie jest to jedyny efekt, jaki ma. Jest jeszcze jeden dobrze znany efekt, który jest również powodem, dla którego większość ludzi używa SO_REUSEADDR w programach serwerowych w pierwszej kolejności. W przypadku innego ważnego zastosowania tej opcji musimy przyjrzeć się dokładniej, jak działa protokół TCP.

Gniazdo ma bufor wysyłania i jeśli wywołanie funkcji send() powiedzie się, nie oznacza to, że żądane dane zostały rzeczywiście wysłane, a jedynie, że dane zostały dodane do bufora wysyłania. W przypadku gniazd UDP dane są zazwyczaj wysyłane dość szybko, jeśli nie natychmiast, ale w przypadku gniazd TCP może wystąpić stosunkowo duże opóźnienie między dodaniem danych do bufora wysyłania a implementacja TCP naprawdę wysyła te dane. W rezultacie, gdy zamkniesz Gniazdo TCP, w buforze wysyłania mogą nadal znajdować się oczekujące dane, które nie zostały jeszcze wysłane, ale twój kod uważa je za wysłane, ponieważ wywołanie send() powiodło się. Gdyby implementacja TCP zamykała Gniazdo natychmiast na twoje żądanie, wszystkie te dane zostałyby utracone, a Twój kod nawet o tym nie wiedział. Mówi się, że TCP jest niezawodnym protokołem, a utrata danych w ten sposób nie jest zbyt niezawodna. Dlatego gniazdo, które nadal ma dane do wysłania, przejdzie do stanu o nazwie TIME_WAIT po zamknięciu. W tym stanie będzie czekać, aż wszystkie oczekujące dane zostaną pomyślnie wysłane lub do czasu osiągnięcia limitu czasu, w którym to przypadku gniazdo zostanie zamknięte na siłę.

Co najwyżej, czas, przez jaki jądro będzie czekać, zanim zamknie Gniazdo, niezależnie od tego, czy nadal ma dane w locie, nazywany jest czasem oczekiwania . Czas oczekiwania jest globalnie konfigurowalny na większości systemy i domyślnie dość długie (dwie minuty to wspólna wartość, którą można znaleźć na wielu systemach). Jest również konfigurowalny dla każdego gniazda za pomocą opcji gniazda SO_LINGER, która może być użyta do skrócenia lub wydłużenia limitu czasu, a nawet do całkowitego wyłączenia go. Wyłączenie go całkowicie jest bardzo złym pomysłem, ponieważ zamykanie gniazda TCP z wdziękiem jest nieco skomplikowanym procesem i obejmuje wysyłanie z powrotem i z powrotem kilku pakietów (a także ponowne wysyłanie tych pakietów w przypadku ich zgubienia) i cały ten zamknięty Proces jest również ograniczony przez czas oczekiwania. Jeśli wyłączysz przeciąganie, gniazdo może nie tylko utracić dane w locie, ale jest również zawsze zamknięte siłą zamiast wdzięku, co zwykle nie jest zalecane. Szczegóły dotyczące sposobu zamknięcia połączenia TCP są poza zakresem tej odpowiedzi, jeśli chcesz dowiedzieć się więcej na ten temat, polecam zajrzeć na ta strona. / Align = "left" / bez jawnego zamykania gniazda, BSD (i ewentualnie inne systemy) pozostanie w tyle, ignorując to, co skonfigurowałeś. Stanie się tak na przykład, jeśli twój kod wywoła exit() (dość powszechne dla małych, prostych programów serwerowych) lub proces zostanie zabity przez sygnał (który zawiera możliwość, że po prostu zawiesza się z powodu nielegalnego dostępu do pamięci). Więc nie możesz nic zrobić, aby upewnić się, że gniazdo nigdy nie pozostanie w każdych okolicznościach.

Pytanie brzmi, jak system traktuje gniazdo w stanie TIME_WAIT? Jeśli SO_REUSEADDR nie jest ustawione, uważa się, że gniazdo w stanie TIME_WAIT jest nadal związane z adresem źródłowym i portem, a każda próba związania nowego gniazda z tym samym adresem i portem zakończy się niepowodzeniem, dopóki gniazdo nie zostanie naprawdę zamknięte, co może trwać tak długo, jak skonfigurowany pozostanie w czasie. Nie spodziewaj się więc, że możesz ponownie powiązać adres źródłowy gniazda natychmiast po jego zamknięciu. W większości przypadków to się nie powiedzie. Jednakże, jeśli SO_REUSEADDR jest ustawione dla gniazda, które próbujesz powiązać, inne gniazdo związane z tym samym adresem i portem w stanie TIME_WAIT jest po prostu ignorowane, w końcu jest już "pół martwe", a twoje gniazdo może powiązać dokładnie ten sam adres bez żadnego problemu. W takim przypadku nie odgrywa żadnej roli, że drugie gniazdo może mieć dokładnie ten sam adres i port. Zauważ, że powiązanie gniazda z dokładnie tym samym adresem i portem co gniazdo umierające w stanie TIME_WAIT może mieć nieoczekiwane i zazwyczaj niepożądane skutki uboczne w przypadku drugie gniazdo jest nadal "w pracy", ale to wykracza poza zakres tej odpowiedzi i na szczęście te skutki uboczne są raczej rzadkie w praktyce.

Jest jeszcze jedna rzecz, którą powinieneś wiedzieć o SO_REUSEADDR. Wszystko napisane powyżej będzie działać tak długo, jak gniazdo, z którym chcesz powiązać, ma włączone ponowne użycie adresu. Nie jest konieczne, aby inne gniazdo, Które jest już związane lub znajduje się w stanie TIME_WAIT, również miało tę flagę ustawioną, gdy było związane. Kod, który decyduje, czy bind będzie succeed or fail sprawdza tylko flagę SO_REUSEADDR gniazda podanego do wywołania bind(), dla wszystkich innych skontrolowanych gniazd flaga ta nie jest nawet sprawdzana.

SO_REUSEPORT

SO_REUSEPORT jest tym, czego oczekuje większość ludzi. Zasadniczo, SO_REUSEPORT pozwala powiązać dowolną liczbę gniazd z dokładnie tym samym adresem źródłowym i portem, o ile wszystkie poprzednie gniazda również miałySO_REUSEPORT ustawione przed ich powiązaniem. Jeśli pierwsze gniazdo, Które jest związane aby adres i port nie miały ustawionego SO_REUSEPORT, żadne inne gniazdo nie może być powiązane z dokładnie tym samym adresem i portem, niezależnie od tego, czy to drugie gniazdo ma ustawione SO_REUSEPORT, dopóki pierwsze gniazdo nie zwolni ponownie wiązania. W przeciwieństwie do SO_REUESADDR obsługa kodu SO_REUSEPORT nie tylko sprawdzi, czy aktualnie związane gniazdo ma ustawione SO_REUSEPORT, ale także sprawdzi, czy gniazdo o kolidującym adresie i porcie miało ustawione SO_REUSEPORT, gdy zostało połączone.

SO_REUSEPORT nie oznacza SO_REUSEADDR. To oznacza to, że jeśli gniazdo nie miało SO_REUSEPORT ustawionego, gdy było związane, a inne gniazdo ma SO_REUSEPORT ustawione, gdy jest związane dokładnie z tym samym adresem i portem, to Wiązanie nie powiedzie się, co jest oczekiwane, ale również nie powiedzie się, jeśli drugie gniazdo już umiera i znajduje się w stanie TIME_WAIT. Aby móc powiązać gniazdo z tymi samymi adresami i portem, co inne gniazdo w stanie TIME_WAIT wymaga ustawienia SO_REUSEADDR na tym gnieździe lub SO_REUSEPORT musi być ustawione na obu gniazdach przed ich powiązaniem. Oczywiście, że tak. można ustawić zarówno SO_REUSEPORT, jak i SO_REUSEADDR na gnieździe.

Nie ma wiele do powiedzenia o SO_REUSEPORT poza tym, że został dodany później niż SO_REUSEADDR, dlatego nie znajdziesz go w wielu implementacjach gniazd innych systemów, które "rozwidlały" kod BSD przed dodaniem tej opcji, i że nie było sposobu, aby powiązać dwa gniazda do dokładnie tego samego adresu gniazd w BSD przed tą opcją.

Connect () zwraca Eaddrinuse?

Większość ludzi wie, że bind() może się nie udać z błędem EADDRINUSE, jednak gdy zaczniesz bawić się z ponownym użyciem adresu, możesz napotkać dziwną sytuację, że connect() również nie powiedzie się z tym błędem. Jak to możliwe? Jak Zdalny adres, w końcu to, co connect dodaje do gniazda, może być już używany? Podłączenie wielu gniazd do dokładnie tego samego zdalnego adresu nigdy wcześniej nie stanowiło problemu, więc co tu się dzieje?

Jak powiedziałem na samej górze mojej odpowiedzi, połączenie jest zdefiniowane przez krotkę pięć wartości, pamiętasz? I powiedziałem też, że te pięć wartości musi być unikalnych, inaczej system nie może już rozróżniać dwóch połączeń, prawda? Przy ponownym użyciu adresu można powiązać dwa gniazda tego samego protokołu z tym samym adresem źródłowym i portem. Oznacza to, że trzy z tych pięciu wartości są już takie same dla tych dwóch gniazd. Jeśli teraz spróbujesz połączyć oba te gniazda również z tym samym adresem docelowym i portem, utworzysz dwa połączone gniazda, których krotki są absolutnie identyczne. To nie może działać, przynajmniej nie dla połączeń TCP(połączenia UDP i tak nie są prawdziwymi połączeniami). Jeśli dane dotarły do jednego z dwóch połączeń, system nie mógł określić, do którego połączenia dane należą. Co najmniej adres docelowy lub port docelowy musi być inny dla każdego połączenia, tak aby system nie miał problemu z identyfikacją, do którego połączenia należy przychodzące dane.

Więc jeśli połączysz dwa gniazda tego samego protokołu z ten sam adres źródłowy i port i spróbuj połączyć je z tym samym adresem docelowym i portem, connect() w rzeczywistości nie powiedzie się z błędem EADDRINUSE dla drugiego gniazda, które próbujesz połączyć, co oznacza, że gniazdo z identyczną krotką pięciu wartości jest już podłączone.

Adresy Multicast

Większość ludzi ignoruje fakt, że adresy multicastowe istnieją, ale istnieją. Podczas gdy adresy unicast są używane do komunikacji jeden-do-jednego, adresy multicast są używane do komunikacja jeden do wielu. Większość ludzi dowiedziała się o adresach multicast, gdy dowiedziała się o IPv6, ale adresy multicast istniały również w IPv4, mimo że funkcja ta nigdy nie była szeroko stosowana w publicznym Internecie.

Znaczenie SO_REUSEADDR zmienia się dla adresów multicastu, ponieważ pozwala na podłączenie wielu gniazd do dokładnie tej samej kombinacji adresu źródłowego multicastu i portu. Innymi słowy, dla adresów multicast SO_REUSEADDR zachowuje się dokładnie tak samo jak SO_REUSEPORT dla adresów unicast. W rzeczywistości kod traktuje SO_REUSEADDR i SO_REUSEPORT identycznie dla adresów multicastu, co oznacza, że można powiedzieć, że SO_REUSEADDR implikuje SO_REUSEPORT dla wszystkich adresów multicastu i odwrotnie.


FreeBSD / OpenBSD / NetBSD

Wszystkie te elementy są dość późnymi widelcami oryginalnego kodu BSD, dlatego wszystkie trzy oferują te same opcje co BSD i zachowują się tak samo jak w BSD.


macOS (MacOS X)

W swej istocie macOS jest po prostu w stylu BSD UNIX nazwany "Darwin ", oparty na dość późnym widelcu kodu BSD (BSD 4.3), który później został nawet ponownie zsynchronizowany z (w tym czasie aktualnym) kodem FreeBSD 5 Dla Mac OS 10.3, aby Apple mogło uzyskać pełną zgodność z POSIX (macOS posiada certyfikat POSIX). Pomimo posiadania mikrokernela w jądrze ("Mach"), reszta jądra ("XNU") jest w zasadzie tylko jądrem BSD i dlatego macOS oferuje te same opcje, co BSD, a także zachowujÄ ... siÄ ™ tak samo jak w BSD.

IOS / watchOS / tvOS

[154]}IOS to tylko widelec macOS z nieco zmodyfikowanym i przyciętym jądrem, nieco zmniejszonym zestawem narzędzi przestrzeni użytkownika i nieco innym domyślnym zestawem frameworków. watchOS i tvOS to widelce iOS, które są jeszcze bardziej rozebrane (zwłaszcza watchOS). Według mojej najlepszej wiedzy wszystkie zachowują się dokładnie tak, jak macOS.


Linux

Linux

Przed Linuksem 3.9, tylko opcja SO_REUSEADDR istniał. Opcja ta zachowuje się zasadniczo tak samo jak w BSD z dwoma ważnymi wyjątkami:

  1. Tak długo, jak gniazdo TCP nasłuchujące (serwerowe) jest powiązane z określonym portem, opcja SO_REUSEADDR jest całkowicie ignorowana dla wszystkich gniazd kierujących na ten port. Podłączenie drugiego gniazda do tego samego portu jest możliwe tylko wtedy, gdy było możliwe również w BSD bez ustawienia SO_REUSEADDR. Np. nie można powiązać z adresem wieloznacznym, a następnie z bardziej konkretnym lub odwrotnie, oba są możliwe w BSD jeśli ustawisz SO_REUSEADDR. Co możesz zrobić, to możesz powiązać ten sam port i dwa różne adresy, które nie są wieloznaczne, ponieważ zawsze jest to dozwolone. Pod tym względem Linux jest bardziej restrykcyjny niż BSD.

  2. Drugim wyjÄ ... tkiem jest to, Ĺźe dla gniazd klienckich ta opcja zachowuje siÄ ™ dokĹ 'adnie tak jak SO_REUSEPORT W BSD, o ile obie te opcje miaĹ' y ustawiony znacznik zanim zostaĹ 'y zĹ' Ä ... czone. Powodem pozwalającym na to było po prostu to, że ważne jest, aby móc wiązać Wiele gniazd dokładnie do ten sam adres gniazda UDP dla różnych protokołów i ponieważ nie było SO_REUSEPORT przed 3.9, zachowanie SO_REUSEADDR zostało odpowiednio zmienione, aby wypełnić tę lukę. Pod tym względem Linux jest mniej restrykcyjny niż BSD.

Linux > = 3.9

Linux 3.9 dodał również opcję {[69] } do Linuksa. Opcja ta zachowuje się dokładnie tak, jak opcja w BSD i umożliwia powiązanie z dokładnie tym samym adresem i numerem portu, o ile wszystkie gniazda mają tę opcję ustawioną przed powiązaniem oni.

Jednak nadal istnieją dwie różnice w stosunku do SO_REUSEPORT w innych systemach:

  1. Aby zapobiec "przejmowaniu portów", istnieje jedno specjalne ograniczenie: wszystkie gniazda, które chcą współdzielić ten sam adres i kombinację portów, muszą należeć do procesów, które mają ten sam efektywny identyfikator użytkownika! więc jeden użytkownik nie może "ukraść" portów innego użytkownika. Jest to jakaś specjalna magia, która w pewnym stopniu zrekompensuje brakujące SO_EXCLBIND/SO_EXCLUSIVEADDRUSE flagi.

  2. Dodatkowo jądro wykonuje "specjalną magię" dla gniazd SO_REUSEPORT, których nie ma w innych systemach operacyjnych: dla gniazd UDP próbuje równomiernie dystrybuować datagramy, dla gniazd nasłuchujących TCP próbuje równomiernie dystrybuować przychodzące żądania połączeń (te akceptowane przez wywołanie accept()) we wszystkich gniazdach, które mają ten sam adres i kombinację portów. W ten sposób aplikacja może łatwo otworzyć ten sam port w wielu procesach potomnych, a następnie użyć Aby uzyskać bardzo niedrogie równoważenie obciążenia.


Android

Chociaż cały system Android różni się nieco od większości dystrybucji Linuksa, w jego rdzeniu działa nieco zmodyfikowane jądro Linuksa, więc wszystko, co dotyczy Linuksa, powinno dotyczyć również Androida.


okna

Windows zna tylko opcję SO_REUSEADDR, nie ma SO_REUSEPORT. Ustawienie SO_REUSEADDR na gnieździe w Windows zachowuje się jak ustawienie SO_REUSEPORT i SO_REUSEADDR na gnieździe w BSD, z jednym wyjątkiem: gniazdo z SO_REUSEADDR może zawsze wiązać się z dokładnie tym samym adresem źródłowym i portem, co gniazdo już powiązane, nawet jeśli drugie gniazdo nie miało tej opcji ustawionej, gdy było związane. To zachowanie jest nieco niebezpieczne, ponieważ pozwala aplikacji "ukraść" podłączony port innej aplikacji. Nie trzeba dodawać, że może to mieć poważne konsekwencje dla bezpieczeństwa. Microsoft zdał sobie sprawę, że może to być problem i dlatego dodał kolejny opcja socket SO_EXCLUSIVEADDRUSE. Ustawienie SO_EXCLUSIVEADDRUSE na gnieździe upewnia się, że jeśli powiązanie się powiedzie, kombinacja adresu źródłowego i portu jest własnością wyłącznie tego gniazda i żadne inne gniazdo nie może się z nimi połączyć, nawet jeśli ma ustawione SO_REUSEADDR.

Aby uzyskać jeszcze więcej szczegółów na temat tego, jak flagi SO_REUSEADDR i SO_EXCLUSIVEADDRUSE działają na Windows, jak wpływają na Wiązanie/ponowne wiązanie, Microsoft uprzejmie dostarczył tabelę podobną do mojej tabeli w górnej części tej odpowiedzi. po prostu odwiedź tę stronę i przewiń trochę niżej. W rzeczywistości istnieją trzy tabele, pierwsza pokazuje stare zachowanie (wcześniejsze Windows 2003), druga zachowanie (Windows 2003 i nowsze), a trzecia pokazuje, jak zmienia się zachowanie w Windows 2003 i później, jeśli wywołania bind() są wykonywane przez różnych użytkowników.


Solaris

Solaris jest następcą Sunosa. SunOS był pierwotnie oparty na forku BSD, SunOS 5, a później został oparty na forku SVR4, jednak SVR4 jest połączeniem BSD, System V i Xenix, więc do pewnego stopnia Solaris jest także widelcem BSD i dość wczesnym. W rezultacie Solaris wie tylko SO_REUSEADDR, nie ma SO_REUSEPORT. SO_REUSEADDR zachowuje się prawie tak samo jak w BSD. Z tego, co wiem, nie ma sposobu na uzyskanie takiego samego zachowania jak SO_REUSEPORT w Solarisie, co oznacza, że nie jest możliwe powiązanie dwóch gniazd do dokładnie tego samego adresu i portu.

Podobnie jak Windows, Solaris ma możliwość nadania gniazdowi wyłącznego wiązania. Opcja ta ma nazwę SO_EXCLBIND. Jeśli to opcja jest ustawiana na gnieździe przed powiązaniem go, ustawienie SO_REUSEADDR na innym gnieździe nie ma wpływu, jeśli oba gniazda są testowane pod kątem konfliktu adresów. Np. jeśli socketA jest przypisany do adresu wieloznacznego i socketB ma włączone SO_REUSEADDR i jest przypisany do adresu innego niż wieloznaczny i tego samego portu co socketA, to wiązanie zwykle się powiedzie, chyba że socketA ma włączone SO_EXCLBIND, w którym to przypadku zakończy się niepowodzeniem niezależnie od flagi SO_REUSEADDR Z socketB.


Inne Systemy

W przypadku, gdy Twój system jest nie wymienione powyżej, napisałem mały program testowy, który można użyć, aby dowiedzieć się, jak Twój system obsługuje te dwie opcje. również jeśli uważasz, że moje wyniki są złe , proszę najpierw uruchomić ten program przed opublikowaniem jakichkolwiek komentarzy i ewentualnie składania fałszywych roszczeń.

Wszystko, czego kod wymaga do zbudowania, to bitowe API POSIX (dla części sieciowych) i kompilator C99 (w rzeczywistości większość kompilatorów spoza C99 będzie działać tak dobrze, jak długo oferują inttypes.h i stdbool.h; np. gcc obsługiwane zarówno na długo przed oferowaniem pełnej obsługi C99).

Wszystko, co program musi uruchomić, to to, że co najmniej jeden interfejs w systemie (inny niż interfejs lokalny) ma przypisany adres IP i że ustawiona jest domyślna trasa, która używa tego interfejsu. Program zbierze ten adres IP i użyje go jako drugiego "konkretnego adresu".

Testuje wszystkie możliwe kombinacje, które możesz wymyślić:

  • protokół TCP i UDP
  • normalne gniazda, listen( serwer) gniazda, gniazda multicast
  • [[26]} zestaw na socket1, socket2 lub oba gniazda
  • [[69]} zestaw na socket1, socket2 lub oba gniazda
  • W przypadku multicastu jest to po prostu adres, który można utworzyć z 0.0.0.0 (wildcard), 127.0.0.1 (specific address) i drugiego adresu znalezionego w głównym interfejsie (dla multicastu jest to po prostu 224.1.2.3 we wszystkich testach)

I drukuje wyniki w ładnej tabeli. Będzie również działać na systemach, które nie wiedzą SO_REUSEPORT, w takim przypadku ta opcja po prostu nie jest testowana.

Program nie może łatwo przetestować tego, jak SO_REUSEADDR Działa na gniazdkach w stanie TIME_WAIT, ponieważ bardzo trudno jest wymusić i utrzymać gniazdo w tym stanie. Na szczęście większość systemów operacyjnych wydaje się po prostu zachowywać tutaj jak BSD i większość programistów może po prostu ignorować istnienie tego stanu.

Oto kod (nie mogę go tu zamieścić, odpowiedzi mają limit rozmiaru i Kod przesunąłby tę odpowiedź ponad limit).

 1755
Author: Mecki,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2021-02-07 22:29:31

Odpowiedź Meckiego jest absolutnie doskonała, ale warto dodać, że FreeBSD obsługuje również SO_REUSEPORT_LB, które naśladuje zachowanie Linuksa SO_REUSEPORT - równoważy obciążenie; Zobacz setsockopt(2)

 4
Author: Edward Tomasz Napierala,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-10 22:34:05