Jak przymusowo zamknąć gniazdo w TIME_WAIT?


113

Na Linuksie uruchamiam określony program, który czasami ulega awarii. Jeśli otworzysz go szybko potem, będzie nasłuchiwał na gnieździe 49201 zamiast 49200, jak to robił za pierwszym razem. Netstat ujawnia, że ​​49200 jest w stanie TIME_WAIT.

Czy istnieje program, który można uruchomić, aby natychmiast zmusić to gniazdo do wyjścia ze stanu TIME_WAIT?


1
Jeśli jesteś tutaj z powodu „zbyt wielu TIME_WAITna serwerze” , po prostu pomiń pierwsze trzy odpowiedzi, które unikają pytania, zamiast na nie odpowiadać.
Pacerier

Odpowiedzi:


148
/etc/init.d/networking restart

Pozwól mi rozwinąć. Protokół kontroli transmisji (TCP) został zaprojektowany jako dwukierunkowy, uporządkowany i niezawodny protokół transmisji danych między dwoma punktami końcowymi (programami). W tym kontekście termin niezawodny oznacza, że ​​prześle pakiety ponownie, jeśli zgubi się w środku. TCP gwarantuje niezawodność, wysyłając z powrotem pakiety potwierdzenia (ACK) dla jednego lub szeregu pakietów otrzymanych od partnera.

To samo dotyczy sygnałów sterujących, takich jak żądanie / odpowiedź o zakończenie. RFC 793 definiuje stan CZAS OCZEKIWANIA w następujący sposób:

CZAS OCZEKIWANIA - oznacza oczekiwanie na wystarczającą ilość czasu, aby upewnić się, że zdalny TCP otrzymał potwierdzenie żądania zakończenia połączenia.

Zobacz następujący diagram stanu TCP: alternatywny tekst

TCP jest dwukierunkowym protokołem komunikacyjnym, więc po ustanowieniu połączenia nie ma różnicy między klientem a serwerem. Ponadto każdy z nich może wywoływać rezygnacje, a obydwaj uczestnicy muszą zgodzić się na zamknięcie, aby całkowicie zamknąć ustanowione połączenie TCP.

Zadzwońmy do pierwszego, który wywołuje wyjścia jako aktywny bliżej, a drugi do pasywnego bliżej. Kiedy aktywny bliżej wysyła FIN, stan przechodzi do FIN-WAIT-1. Następnie otrzymuje potwierdzenie dla wysłanego FIN, a stan przechodzi do FIN-WAIT-2. Gdy otrzyma FIN również z pasywnego bliższego, aktywny bliższy wysyła potwierdzenie do FIN, a stan przechodzi do CZASU OCZEKIWANIA. W przypadku, gdy pasywne zbliżenie nie otrzymało potwierdzenia do drugiego FIN, retransmituje pakiet FIN.

RFC 793 ustawia TIME-OUT na dwukrotność maksymalnego czasu życia segmentu lub 2MSL. Od czasu MSL maksymalny czas, w którym pakiet może wędrować po Internecie, jest ustawiony na 2 minuty, 2MSL to 4 minuty. Ponieważ nie ma ACK dla ACK, aktywne zamknięcie nie może zrobić nic innego, jak tylko czekać 4 minuty, jeśli poprawnie przestrzega protokołu TCP / IP, na wypadek, gdyby pasywny nadawca nie otrzymał ACK do jego FIN (teoretycznie) .

W rzeczywistości brakujące pakiety są prawdopodobnie rzadkie i bardzo rzadkie, jeśli wszystko dzieje się w sieci LAN lub na jednym komputerze.

Aby odpowiedzieć dosłownie na pytanie, jak przymusowo zamknąć gniazdo w TIME_WAIT ?, nadal będę trzymać się mojej oryginalnej odpowiedzi:

/etc/init.d/networking restart

Praktycznie rzecz biorąc, zaprogramowałbym go tak, aby ignorował stan CZAS OCZEKIWANIA za pomocą opcji SO_REUSEADDR, jak wspomniano w WMR. Co dokładnie robi SO_REUSEADDR?

Ta opcja gniazda informuje jądro, że nawet jeśli ten port jest zajęty (w
stanie TIME_WAIT), idź dalej i użyj go ponownie. Jeśli jest zajęty, ale z innym stanem, nadal pojawia się błąd już używanego adresu. Jest to przydatne, jeśli serwer został zamknięty, a następnie ponownie uruchomiony od razu, gdy gniazda są nadal aktywne na jego porcie. Należy pamiętać, że jeśli pojawią się jakieś nieoczekiwane dane, mogą one mylić serwer, ale chociaż jest to możliwe, nie jest prawdopodobne.


8
Świetna odpowiedź, ale nie poprawna odpowiedź na jego pytanie. Ponowne uruchomienie sieci działałoby, ale tak samo zrestartowałoby się, więc nie może tak być.
Chris Huang-Leaver,

3
@Chris Huang-Leaver, pytanie brzmi: „Czy istnieje program, który można uruchomić, aby natychmiast wymusić przeniesienie tego gniazda ze stanu TIME_WAIT?” jeśli ponowne uruchomienie można uznać za uruchomienie programu, to również byłaby właściwa odpowiedź. Jak myślisz, dlaczego tak nie jest?
Eugene Yokota,

8
WMR ma najbardziej przydatną odpowiedź (co robię, gdy napotykam tego rodzaju problem). Ponowne uruchomienie sieci jest zbyt drastyczne, aby mogło być rozwiązaniem, i może potrwać dłużej niż zwykłe czekanie na przekroczenie limitu czasu. Prawidłowa odpowiedź na jego pytanie brzmi „nie”, ale SO nie pozwala na wpisanie dwóch liter :-)
Chris Huang- Leaver

6
och okej, następnym razem, gdy jakiś proces zawiesi się na SIGTERM, po prostu rozwalę komputer zamiast go naprawić.
Longpoke

Uogólnieniem tego jest „restart usług sieciowych”. Określona lokalizacja /etc/init.d/networkingjest specyficzna dla platformy (Debian?), Więc dokładna linia poleceń będzie inna (czasem raczej radykalna) dla innych systemów. Zgadzam się z innymi komentatorami, że wydaje się to poważną przesadą i oczywiście zakłócającą wszelkie niepowiązane usługi sieciowe.
tripleee

51

Nie wiem, czy masz kod źródłowy tego konkretnego programu, który uruchamiasz, ale jeśli tak, możesz po prostu ustawić SO_REUSEADDR, dzięki setsockopt(2)któremu możesz powiązać ten sam adres lokalny, nawet jeśli gniazdo jest w stanie TIME_WAIT (chyba że gniazdo aktywnie nasłuchuje, patrz socket(7)).

Aby uzyskać więcej informacji na temat stanu TIME_WAIT, zobacz często zadawane pytania dotyczące gniazda Unix .


ale nie dostałem już związanego błędu. kiedy ponownie uruchamiam program, nasłuchuje on w poście (123456). Widzę również, że system wyświetla TIME_WAIT dla tego portu, ale nadal mogę się połączyć. dlaczego?
Jayapal Chandran

2
Nawet w przypadku SO_REUSEADDR nadal można uzyskać błąd „Adres już używany”. Aby uzyskać szczegółowe informacje, patrz hea-www.harvard.edu/~fine/Tech/addrinuse.html .
Jingguo Yao

@WMR SO_REUSEADDRnie „zamyka” gniazda. Pozwala jedynie ponownie wykorzystać te, które są już otwarte. Więc pytanie brzmi: „Jak przymusowo zamknąć gniazdo TIME_WAIT?”
Pacerier

To poprawna odpowiedź, ale pytanie nie było całkowicie poprawne. Przynajmniej rozwiązałem mój problem ładnie (nie jak zrestartowanie całej sieci i zerwanie wszystkich innych połączeń).
V-Mark,

SO_REUSEADDRpozwoli bind()kontynuować; ale jeśli chcesz słuchać tego gniazda, listen()zwróci EADDRINUSEto samo. Innymi słowy, ta odpowiedź może pomóc oprogramowaniu klienckiemu używającym efemerycznych portów, ale nie rozwiązuje problemu z oprogramowaniem serwera.
Czy

33

O ile wiem, nie ma sposobu, aby przymusowo zamknąć gniazdo poza napisaniem lepszego programu obsługi sygnałów w twoim programie, ale istnieje plik / proc, który kontroluje, jak długo trwa przekroczenie limitu czasu. Plik jest

/proc/sys/net/ipv4/tcp_tw_recycle

i możesz ustawić limit czasu na 1 sekundę, wykonując następujące czynności:

echo 1 > /proc/sys/net/ipv4/tcp_tw_recycle 

Jednak ta strona zawiera ostrzeżenie o możliwych problemów z niezawodnością podczas ustawiania tej zmiennej.

Istnieje również powiązany plik

/proc/sys/net/ipv4/tcp_tw_reuse

który kontroluje, czy gniazda TIME_WAIT mogą być ponownie użyte (prawdopodobnie bez przekroczenia limitu czasu).

Nawiasem mówiąc, dokumentacja jądra ostrzega, aby nie zmieniać żadnej z tych wartości bez „porad / wniosków ekspertów technicznych”. Którego nie jestem.

Program musi zostać napisany, aby podjąć próbę powiązania z portem 49200, a następnie zwiększyć o 1, jeśli port jest już w użyciu. Dlatego jeśli masz kontrolę nad kodem źródłowym, możesz zmienić to zachowanie, aby poczekać kilka sekund i spróbować ponownie na tym samym porcie, zamiast zwiększania.


myślę, że dwa pozostałe przykłady powinny być s / rw / tw / edytowałbym, ale brakuje wystarczającej liczby powtórzeń.

1
Zaczerpnięte z dokumentacji jądra: Uwaga. Zarówno tcp_tw_recycle, jak i tcp_tw_reuse mogą powodować problemy. Nie należy włączać bez zrozumienia topologii sieci między węzłami, które są używane lub używane przez węzeł, w którym parametr jest włączony. Połączenia przechodzące przez węzły, które są świadome stanów połączeń TCP, takie jak zapora ogniowa, translator NAT lub moduł równoważenia obciążenia, mogą zacząć opuszczać ramki z powodu tego ustawienia. Problem stanie się widoczny, gdy będzie wystarczająco duża liczba połączeń.

Ustawienie to 1działa dla przyszłych połączeń, ale co z tymi obecnymi, które są już otwarte?
Pacerier

18

W rzeczywistości istnieje sposób na zabicie połączenia - killcx . Twierdzą, że działa w dowolnym stanie połączenia (którego nie zweryfikowałem). Musisz jednak znać interfejs, w którym odbywa się komunikacja, domyślnie przyjmuje się, że eth0.

AKTUALIZACJA: innym rozwiązaniem jest kuter, który jest dostępny w repozytoriach niektórych dystrybucji Linuksa.


3

Inną opcją jest użycie opcji SO_LINGER z limitem czasu 0. W ten sposób, kiedy zamykasz gniazdo, jest przymusowo zamykane, wysyłając RST zamiast przechodzić w zachowanie FIN / ACK. Pozwoli to uniknąć stanu TIME_WAIT i może być bardziej odpowiednie dla niektórych zastosowań.


2
Traci także wszelkie dane wychodzące, które są nadal w transporcie, i może powodować błąd na drugim końcu. Niepolecane.
user207421,

@EJP Niepowodzenie wcześnie to prawie zawsze właściwe połączenie. Sieć nie jest niezawodna, a walka spowolni. Awaria aplikacji nie może zakładać, że jakiekolwiek dane dotarły bezpiecznie.
Tobu,

1
Właściwie poleciłbym to każdego dnia, gdy drugim punktem końcowym jest wadliwa, wbudowana brama magistrali przemysłowej, która implementuje własny niezawodny transport warstwy aplikacji przez TCP, przy czym wspomniany transport uniemożliwia zamknięcie połączenia, chyba że odbierze RST, a tym samym zapełni się limit połączenia na tej bramie. Tam. Dałem ci bardzo konkretny i bardzo prawdziwy przykład, który niestety wymaga ucieczki się do takich hacków.
andyn

@Tobu Networking nie jest niezawodny, ale TCP stara się być, a pogarszanie go nie oznacza ulepszania niczego, a pozwolenie TCP na wykonywanie swojej pracy nie oznacza „walki”.
user207421

2

Alternatywnym rozwiązaniem byłoby posiadanie pewnego niezawodnego oprogramowania proxy lub przekierowania portów, które nasłuchuje na porcie 49200, a następnie przekazywanie połączenia do jednego z kilku wystąpień mniej niezawodnego programu przy użyciu różnych portów ... HAPROXY przychodzi na myśl.

Nawiasem mówiąc, port, na którym się łączysz, jest dość wysoki. Możesz spróbować użyć nieużywanego tuż powyżej zakresu 0-1024. Twój system rzadziej używa niższego numeru portu jako portu efemerycznego.


0

TIME_WAIT jest najczęstszym problemem w architekturze serwerów klienckich programujących gniazda. Poczekaj kilka sekund, okresowe próby są najlepszym rozwiązaniem. Do aplikacji w czasie rzeczywistym potrzebują serwera, który musi natychmiast wstać. Istnieje dla nich opcja SO_REUSEADDR.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.