Myślę, że rozumiem formalne znaczenie tej opcji. W jakimś starszym kodzie, który teraz obsługuję, używana jest opcja. Klient narzeka na RST jako odpowiedź dla FIN ze swojej strony na połączenie zamknięte z jego strony.
Nie jestem pewien, czy mogę go bezpiecznie usunąć, ponieważ nie rozumiem, kiedy należy go użyć.
Czy możesz podać przykład, kiedy ta opcja byłaby wymagana?
Odpowiedzi:
Typowym powodem ustawienia
SO_LINGER
limitu czasu na zero jest uniknięcie dużej liczby połączeń znajdujących się wTIME_WAIT
stanie, które zajmują wszystkie dostępne zasoby na serwerze.Gdy połączenie TCP zostanie prawidłowo zamknięte, koniec, który zainicjował zamknięcie („aktywne zamknięcie”), kończy się utrzymaniem połączenia
TIME_WAIT
przez kilka minut. Więc jeśli twój protokół to taki, w którym serwer inicjuje połączenie blisko i obejmuje bardzo dużą liczbę krótkotrwałych połączeń, może być podatny na ten problem.Nie jest to jednak dobry pomysł -
TIME_WAIT
istnieje z jakiegoś powodu (aby zapewnić, że zbłąkane pakiety ze starych połączeń nie będą kolidować z nowymi połączeniami). Lepszym pomysłem jest przeprojektowanie protokołu na taki, w którym klient inicjuje połączenie, jeśli jest to możliwe.źródło
TIME_WAIT
wola usiądzie na kliencie, nie czyniąc krzywdy. Pamiętaj, jak jest napisane w trzeciej edycji „UNIX Network Programming” (Stevens i in.) Na stronie 203: „Stan TIME_WAIT jest Twoim przyjacielem i jest po to, aby nam pomóc. Zamiast próbować go unikać, powinniśmy to zrozumieć (sekcja 2.7) ”.Aby uzyskać moją sugestię, przeczytaj ostatnią sekcję: „Kiedy używać SO_LINGER z limitem czasu 0” .
Zanim przejdziemy do tego małego wykładu o:
TIME_WAIT
FIN
,ACK
iRST
Normalne zakończenie TCP
Normalna sekwencja zakończenia TCP wygląda następująco (uproszczona):
Mamy dwóch rówieśników: A i B.
close()
FIN
do BFIN_WAIT_1
stanFIN
ACK
do ACLOSE_WAIT
stanACK
FIN_WAIT_2
stanclose()
FIN
do ALAST_ACK
stanFIN
ACK
do BTIME_WAIT
stanACK
CLOSED
stanu - tj. Jest usuwany z tablic gniazdCZAS OCZEKIWANIA
Zatem peer, który zainicjuje zakończenie - tj. Wywołuje
close()
pierwszy - znajdzie się wTIME_WAIT
stanie.Aby zrozumieć, dlaczego
TIME_WAIT
państwo jest naszym przyjacielem, przeczytaj sekcję 2.7 w trzecim wydaniu „UNIX Network Programming” autorstwa Stevensa i wsp. (Strona 43).Jednak może to być problem z wieloma
TIME_WAIT
stanami gniazd na serwerze, ponieważ może to ostatecznie uniemożliwić akceptowanie nowych połączeń.Aby obejść ten problem, widziałem wielu sugerujących ustawienie opcji gniazda SO_LINGER z limitem czasu 0 przed wywołaniem
close()
. Jest to jednak złe rozwiązanie, ponieważ powoduje zakończenie połączenia TCP z błędem.Zamiast tego zaprojektuj protokół aplikacji, tak aby zakończenie połączenia było zawsze inicjowane po stronie klienta. Jeśli klient zawsze wie, kiedy odczytał wszystkie pozostałe dane, może zainicjować sekwencję zakończenia. Na przykład przeglądarka wie z
Content-Length
nagłówka HTTP, kiedy przeczytała wszystkie dane i może zainicjować zamknięcie. (Wiem, że w HTTP 1.1 pozostawi go otwarty przez chwilę w celu ewentualnego ponownego użycia, a następnie zamknie).Jeśli serwer musi zamknąć połączenie, zaprojektuj protokół aplikacji tak, aby serwer poprosił klienta o połączenie
close()
.Kiedy używać SO_LINGER z limitem czasu 0
Ponownie, zgodnie z trzecim wydaniem strony 202-203 „Programowanie sieciowe UNIX”, ustawienie
SO_LINGER
z limitem czasu 0 przed wywołaniemclose()
spowoduje, że normalna sekwencja kończenia nie zostanie zainicjowana.Zamiast tego, peer ustawiając tę opcję i wywołując
close()
, wyśleRST
(reset połączenia), który wskazuje stan błędu i tak będzie to postrzegane po drugiej stronie. Zazwyczaj pojawiają się błędy, takie jak „Resetowanie połączenia przez partnera”.Dlatego w normalnej sytuacji jest naprawdę złym pomysłem ustawienie
SO_LINGER
limitu czasu 0 przed wywołaniemclose()
- od teraz nazywanym nieudanym zamknięciem - w aplikacji serwerowej.Jednak pewna sytuacja i tak to uzasadnia:
CLOSE_WAIT
lub wylądowania w tymTIME_WAIT
stanie.TIME_WAIT
(podczas wywoływaniaclose()
z końca serwera), ponieważ może to uniemożliwić serwerowi uzyskanie dostępnych portów dla nowych połączeń klientów po ponownym uruchomieniu.CLOSE_WAIT
próbując dostarczyć dane do zablokowanego terminala port, ale poprawnie zresetowałby zablokowany port, gdyby miałRST
odrzucić oczekujące dane. "Poleciłbym ten długi artykuł, który moim zdaniem jest bardzo dobrą odpowiedzią na Twoje pytanie.
źródło
TIME_WAIT
jest przyjacielem tylko wtedy, gdy nie zaczyna sprawiać problemów: stackoverflow.com/questions/1803566/…Gdy linger jest włączony, ale limit czasu wynosi zero, stos TCP nie czeka na wysłanie oczekujących danych przed zamknięciem połączenia. Z tego powodu dane mogą zostać utracone, ale ustawiając tę opcję w ten sposób, akceptujesz to i prosisz, aby połączenie zostało zresetowane od razu, a nie z wdziękiem. Powoduje to wysłanie RST zamiast zwykłego FIN.
Podziękowania dla EJP za komentarz, zobacz tutaj po szczegóły.
źródło
To, czy możesz bezpiecznie usunąć zwłoki w kodzie, czy nie, zależy od typu twojej aplikacji: czy jest to „klient” (najpierw otwiera połączenia TCP i aktywnie je zamyka), czy też jest to „serwer” (nasłuchujący otwartego protokołu TCP i zamknięcie go po tym, jak druga strona zainicjowała zamknięcie)?
Jeśli Twoja aplikacja ma posmak „klienta” (najpierw zamykasz) ORAZ inicjujesz i zamykasz ogromną liczbę połączeń z różnymi serwerami (np. Gdy Twoja aplikacja jest aplikacją monitorującą nadzorującą osiągalność ogromnej liczby różnych serwerów), Twoja aplikacja ma problem polegający na tym, że wszystkie połączenia klientów utknęły w stanie TIME_WAIT. Następnie zalecałbym skrócenie limitu czasu do wartości mniejszej niż wartość domyślna, aby nadal bezpiecznie zamykać, ale wcześniej zwolnić zasoby połączeń klienta. Nie ustawiłbym limitu czasu na 0, ponieważ 0 nie zamyka wdzięcznie z FIN, ale przerywa działanie z RST.
Jeśli twoja aplikacja ma posmak „klienta” i musi pobierać ogromną liczbę małych plików z tego samego serwera, nie powinieneś inicjować nowego połączenia TCP na plik i kończyć się ogromną liczbą połączeń klienta w CZAS_CZEKIWANIE, ale utrzymuj połączenie otwarte i pobieraj wszystkie dane przez to samo połączenie. Opcję Linger można i należy usunąć.
Jeśli Twoja aplikacja jest „serwerem” (blisko sekundy jako reakcja na zamknięcie peera), przy close () twoje połączenie jest zamykane z wdziękiem, a zasoby są zwalniane, ponieważ nie wchodzisz w stan TIME_WAIT. Nie należy używać Linger. Ale jeśli aplikacja na serwerze ma proces nadzorczy wykrywający nieaktywne otwarte połączenia, które pozostają w stanie bezczynności przez długi czas (należy zdefiniować „długie”), możesz zamknąć to nieaktywne połączenie ze swojej strony - potraktować to jako rodzaj obsługi błędów - z nieudanym zamknięciem. Odbywa się to poprzez ustawienie limitu czasu lingera na 0. Funkcja close () wyśle następnie RST do klienta, informując go, że jesteś zły :-)
źródło
Na serwerach możesz chcieć wysyłać
RST
zamiastFIN
podczas odłączania źle działających klientów. Że przeskakujeFIN-WAIT
następnieTIME-WAIT
stanach gniazda w serwerze, który zapobiega wyczerpywania zasobów serwera, a tym samym chroni przed tego typu denial-of-service atak.źródło
Podoba mi się obserwacja Maxima, że ataki DOS mogą wyczerpać zasoby serwera. Dzieje się tak również bez faktycznie złośliwego przeciwnika.
Niektóre serwery muszą radzić sobie z „niezamierzonym atakiem DOS”, który ma miejsce, gdy aplikacja kliencka ma błąd związany z wyciekiem połączenia, w którym nadal tworzą nowe połączenie dla każdego nowego polecenia wysyłanego do serwera. A potem być może ostatecznie zamknięcie ich połączeń, jeśli uderzą w ciśnienie GC, lub może połączenia w końcu przekroczą limit czasu.
Inny scenariusz to scenariusz „wszyscy klienci mają ten sam adres TCP”. Wtedy połączenia klientów można rozróżnić tylko po numerach portów (jeśli łączą się z pojedynczym serwerem). A jeśli klienci zaczną szybko otwierać / zamykać połączenia z jakiegokolwiek powodu, mogą wyczerpać przestrzeń krotek (adres klienta + port, adres IP serwera + port).
Myślę więc, że najlepiej byłoby, gdyby serwery przełączyły się na strategię Linger-Zero, gdy widzą dużą liczbę gniazd w stanie TIME_WAIT - chociaż nie naprawia to zachowania klienta, może zmniejszyć wpływ.
źródło
Gniazdo nasłuchujące na serwerze może używać linger z czasem 0, aby mieć dostęp do natychmiastowego wiązania z powrotem z gniazdem i resetowania wszystkich klientów, których połączenia nie zostały jeszcze zakończone. TIME_WAIT to coś, co jest interesujące tylko wtedy, gdy masz sieć wielościeżkową i możesz skończyć z błędnie uporządkowanymi pakietami lub w inny sposób masz do czynienia z dziwnym porządkowaniem pakietów sieciowych / czasem przybycia.
źródło