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:
- Normalne zakończenie TCP
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.
- Wzywa
close()
- A wysyła
FIN
do B
- A wchodzi w
FIN_WAIT_1
stan
- B otrzymuje
FIN
- B wysyła
ACK
do A
- B wchodzi w
CLOSE_WAIT
stan
- A otrzymuje
ACK
- A wchodzi w
FIN_WAIT_2
stan
- B woła
close()
- B wysyła
FIN
do A
- B wchodzi w
LAST_ACK
stan
- A otrzymuje
FIN
- A wysyła
ACK
do B
- A wchodzi w
TIME_WAIT
stan
- B otrzymuje
ACK
- B przechodzi do
CLOSED
stanu - tj. Jest usuwany z tablic gniazd
CZAS OCZEKIWANIA
Zatem peer, który zainicjuje zakończenie - tj. Wywołuje close()
pierwszy - znajdzie się w TIME_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łaniem close()
spowoduje, że normalna sekwencja kończenia nie zostanie zainicjowana.
Zamiast tego, peer ustawiając tę opcję i wywołując close()
, wyśle RST
(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łaniem close()
- od teraz nazywanym nieudanym zamknięciem - w aplikacji serwerowej.
Jednak pewna sytuacja i tak to uzasadnia:
- Jeśli klient twojej aplikacji serwerowej zachowuje się nieprawidłowo (przekracza limit czasu, zwraca nieprawidłowe dane itp.), Nieudane zamknięcie ma sens, aby uniknąć utknięcia
CLOSE_WAIT
lub wylądowania w tym TIME_WAIT
stanie.
- Jeśli musisz zrestartować aplikację serwera, która obecnie ma tysiące połączeń klientów, możesz rozważyć ustawienie tej opcji gniazda, aby uniknąć tysięcy gniazd serwera
TIME_WAIT
(podczas wywoływania close()
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.
- Na stronie 202 wspomnianej książki jest wyraźnie napisane: „Istnieją pewne okoliczności, które uzasadniają użycie tej funkcji do wysłania nieudanego zamknięcia. Jednym z przykładów jest serwer terminali RS-232, który może zawieszać się na zawsze,
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.