zamknij vs zamknij gniazdo?


214

W C zrozumiałem, że jeśli zamkniemy gniazdo, oznacza to, że gniazdo zostanie zniszczone i może być ponownie użyte później.

Co powiesz na zamknięcie? Opis mówi, że zamyka połowę połączenia dupleksowego z tym gniazdem. Ale czy to gniazdo zostanie zniszczone jak closewywołanie systemowe?


Nie można go ponownie wykorzystać później. Zamknięte. Skończone. Gotowe.
Markiz Lorne

Odpowiedzi:


191

Jest to wyjaśnione w sieciowy Beej użytkownika. shutdownto elastyczny sposób blokowania komunikacji w jednym lub obu kierunkach. Gdy drugim parametrem jest SHUT_RDWR, blokuje zarówno wysyłanie, jak i odbieranie (jak close). Jednak closejest sposób, aby rzeczywiście zniszczyć gniazdo.

Dzięki shutdownnadal będziesz mógł odbierać oczekujące dane, które zostały już wysłane przez partnera (dziękuję Joeyowi Adamsowi, że to zauważył).


23
Pamiętaj, że nawet jeśli zamkniesz () gniazdo TCP, i tak niekoniecznie będzie można go ponownie użyć natychmiast, ponieważ będzie w stanie TIME_WAIT, podczas gdy system operacyjny upewni się, że nie ma zaległych pakietów, które mogłyby zostać pomylone jako nowe informacje, jeśli miały natychmiast ponownie użyć tego gniazda do czegoś innego.
alesplin,

91
Dużą różnicą między zamykaniem i zamykaniem gniazda jest zachowanie, gdy gniazdo jest współużytkowane przez inne procesy. Shutdown () wpływa na wszystkie kopie gniazda, a close () wpływa tylko na deskryptor pliku w jednym procesie.
Zan Lynx,

5
Jednym z powodów, dla których możesz chcieć używać shutdownobu kierunków, ale nie closejest to, że użyłeś FILEodwołania do gniazda używającego fdopen. Jeśli masz closegniazdo, nowo otwarty plik może mieć przypisane to samo fd, a późniejsze użycie FILEspowoduje odczyt / zapis niewłaściwego miejsca, co może być bardzo złe. Jeśli tak shutdown, kolejne użycie FILEspowoduje tylko błędy, dopóki nie fclosezostanie wywołane.
R .. GitHub ZATRZYMAJ POMOC ICE

25
Komentarz na temat TIME_WAIT jest niepoprawny. Dotyczy to portów, a nie gniazd. Nie można ponownie użyć gniazd.
Markiz Lorne

48
-1. Zarówno ten post, jak i link pomijają ważny pojęciowy powód, dla którego warto skorzystać shutdown: do sygnalizowania EOF peerowi i nadal być w stanie odbierać oczekujące dane wysłane przez peer.
Joey Adams,

144

Żadna z istniejących odpowiedzi nie mówi ludziom, jak shutdowni closedziała na poziomie protokołu TCP, dlatego warto to dodać.

Standardowe połączenie TCP zostaje zakończone przez czterostronną finalizację:

  1. Gdy uczestnik nie ma już danych do wysłania, wysyła pakiet FIN do drugiego
  2. Druga strona zwraca ACK dla FIN.
  3. Gdy druga strona również zakończy transfer danych, wysyła kolejny pakiet FIN
  4. Początkowy uczestnik zwraca ACK i finalizuje transfer.

Istnieje jednak inny „wschodzący” sposób zamknięcia połączenia TCP:

  1. Uczestnik wysyła pakiet RST i porzuca połączenie
  2. Druga strona otrzymuje RST, a następnie porzuca również połączenie

W moim teście z Wireshark, z domyślnymi opcjami gniazd, shutdownwysyła pakiet FIN na drugi koniec, ale to wszystko, co robi. Dopóki druga strona nie wyśle ​​Ci pakietu FIN, nadal możesz odbierać dane. Gdy to się Receivestanie, otrzymasz wynik w rozmiarze 0. Jeśli więc jako pierwszy wyłączysz wysyłanie, powinieneś zamknąć gniazdo po zakończeniu odbierania danych.

Z drugiej strony, jeśli zadzwonisz, closegdy połączenie jest nadal aktywne (druga strona jest nadal aktywna i możesz mieć również niewysłane dane w buforze systemowym), pakiet RST zostanie wysłany na drugą stronę. To jest dobre na błędy. Na przykład, jeśli uważasz, że druga strona podała nieprawidłowe dane lub odmówiła podania danych (atak DOS?), Możesz od razu zamknąć gniazdo.

Moje zdanie na temat zasad brzmiałoby:

  1. Zastanów się shutdown, zanim close, jeśli to możliwe
  2. Jeśli zakończyłeś odbieranie (otrzymano dane o rozmiarze 0) przed podjęciem decyzji o zamknięciu, zamknij połączenie po zakończeniu ostatniego wysyłania (jeśli w ogóle).
  3. Jeśli chcesz normalnie zamknąć połączenie, zamknij połączenie (za pomocą SHUT_WR, a jeśli nie zależy ci na otrzymywaniu danych po tym momencie, również za pomocą SHUT_RD) i poczekaj, aż otrzymasz dane o rozmiarze 0, a następnie zamknij gniazdo elektryczne.
  4. W każdym razie, jeśli wystąpi inny błąd (na przykład przekroczenie limitu czasu), po prostu zamknij gniazdo.

Idealne implementacje dla SHUT_RD i SHUT_WR

Następujące nie zostały przetestowane, zaufaj na własne ryzyko. Uważam jednak, że jest to rozsądny i praktyczny sposób robienia rzeczy.

Jeśli stos TCP otrzyma zamknięcie tylko za pomocą SHUT_RD, oznacza to połączenie jako brak oczekiwanych danych. Wszelkie oczekujące i kolejne readżądania (niezależnie od tego, w którym wątku się znajdują) zostaną zwrócone z wynikiem zerowym. Jednak połączenie jest nadal aktywne i użyteczne - na przykład nadal możesz odbierać dane OOB. Ponadto system operacyjny usunie wszystkie dane otrzymane dla tego połączenia. Ale to wszystko, żadne paczki nie zostaną wysłane na drugą stronę.

Jeśli stos TCP otrzyma zamknięcie tylko za pomocą SHUT_WR, oznacza to połączenie, ponieważ nie można wysłać więcej danych. Wszystkie oczekujące żądania zapisu zostaną zakończone, ale kolejne żądania zapisu zakończą się niepowodzeniem. Ponadto pakiet FIN zostanie wysłany na inną stronę, aby poinformować ich, że nie mamy więcej danych do wysłania.


2
„jeśli zadzwonisz, gdy połączenie jest jeszcze aktywne”, jest tautologiczne i nie powoduje wysłania RST. (1) nie jest konieczne. W (4) przekroczenia limitu czasu niekoniecznie mają fatalny wpływ na połączenie i nie niezmiennie wskazują, że można je zamknąć.
Markiz Lorne

4
@EJP Nie, to nie jest tautologiczne. Możesz shutdown()połączenie, a wtedy nie jest już żywe. Nadal masz deskryptor pliku. Możesz nadal recv()z bufora odbiorczego. I nadal musisz zadzwonić, close()aby pozbyć się deskryptora pliku.
Pavel Šimerda

To najlepsza odpowiedź dotycząca formatu drutu. Byłbym zainteresowany bardziej szczegółowymi informacjami na temat zamykania za pomocą SHUT_RD. Nie ma sygnalizacji TCP, że nie należy oczekiwać więcej danych, prawda? Czy nie jest tylko FIN do sygnalizowania, że ​​nie wysyłamy więcej danych?
Pavel Šimerda

3
@ PavelŠimerda Tak TCP nie sygnalizuje, że nie oczekuje więcej danych. Należy to uwzględnić w protokołach wysokiego poziomu. Moim zdaniem nie jest to w ogóle konieczne. Możesz zamknąć drzwi, ale nie możesz powstrzymać ludzi przed wręczaniem prezentów. To ICH decyzja, nie twoja.
Earth Engine

1
@EJP Czy masz jakieś rzeczywiste argumenty? W przeciwnym razie nie jestem zainteresowany.
Pavel Šimerda

35

Istnieją pewne ograniczenia close(), których można uniknąć, jeśli się je stosuje shutdown().

close()zakończy oba kierunki w połączeniu TCP. Czasami chcesz powiedzieć drugiemu punktowi końcowemu, że skończyłeś wysyłać dane, ale nadal chcesz otrzymywać dane.

close()zmniejsza liczbę odwołań deskryptorów (utrzymywanych we wpisie tabeli plików i zlicza liczbę aktualnie otwartych deskryptorów odnoszących się do pliku / gniazda) i nie zamyka gniazda / pliku, jeśli deskryptor nie ma wartości 0. Oznacza to, że jeśli rozwidlasz się, czyszczenie odbywa się tylko wtedy, gdy liczba referencji spadnie do 0. Za pomocą shutdown()można zainicjować normalną sekwencję zamykania TCP ignorując liczbę referencji.

Parametry są następujące:

int shutdown(int s, int how); // s is socket descriptor

int how może być:

SHUT_RDlub 0 Dalsze otrzymywania są niedozwolone

SHUT_WRlub 1 Dalsze wysyłki są niedozwolone

SHUT_RDWRlub 2 Dalsze wysyłanie i odbieranie jest niedozwolone


8
Te dwie funkcje służą do zupełnie innych celów. Fakt, że końcowe zamknięcie gniazda inicjuje sekwencję zamykania, jeśli jeszcze nie zostało zainicjowane, nie zmienia faktu, że zamknięcie służy do czyszczenia struktur danych gniazd, a zamknięcie służy do inicjowania sekwencji zamykania na poziomie tcp.
Len Holgate,

25
Nie można zamiast tego użyć „zamknięcia systemu”. Możesz go również użyć . ale musisz zamknąć gniazdo na jakiś czas.
Markiz Lorne

15

Może to być specyficzne dla platformy, w jakiś sposób w to wątpię, ale tak czy inaczej, najlepsze wyjaśnienie, jakie widziałem, znajduje się tutaj na tej stronie msdn, gdzie wyjaśniają one na temat zamykania, opcji linger, zamknięcia gniazda i ogólnych sekwencji zakończenia połączenia.

Podsumowując, użyj shutdown, aby wysłać sekwencję zamykania na poziomie TCP i użyj close, aby zwolnić zasoby wykorzystywane przez struktury danych gniazd w twoim procesie. Jeśli nie wydałeś wyraźnej sekwencji zamykania do czasu, gdy zadzwonisz close, zostanie ona dla Ciebie zainicjowana.


9

Miałem również sukces pod Linuksem, używając shutdown()jednego pthreada, aby zmusić inny pthread, który jest obecnie zablokowany, connect()aby przerwać wcześniej.

W innych systemach operacyjnych (przynajmniej OSX) stwierdziłem, że wywołanie close()wystarczyło, aby connect()zakończyć się niepowodzeniem.


7

„shutdown () tak naprawdę nie zamyka deskryptora pliku - po prostu zmienia jego użyteczność. Aby zwolnić deskryptor gniazda, musisz użyć close ().” 1


3

Blisko

Po zakończeniu korzystania z gniazda możesz po prostu zamknąć jego deskryptor pliku za pomocą polecenia close; Jeśli nadal istnieją dane oczekujące na przesłanie przez połączenie, zwykle zamknięcie próbuje zakończyć tę transmisję. Możesz kontrolować to zachowanie za pomocą opcji gniazda SO_LINGER, aby określić limit czasu; patrz Opcje gniazd.

Zamknąć

Możesz także wyłączyć tylko odbiór lub transmisję połączenia, wywołując zamknięcie.

Funkcja zamykania wyłącza połączenie gniazda. Jego argument określa, jakie działanie wykonać: 0 Zatrzymaj odbieranie danych dla tego gniazda. Jeśli pojawią się dalsze dane, odrzuć je. 1 Przestań próbować przesyłać dane z tego gniazda. Odrzuć wszystkie dane oczekujące na wysłanie. Przestań szukać potwierdzenia danych już wysłanych; nie przesyłaj go ponownie, jeśli zostanie zgubiony. 2 Zatrzymaj odbiór i transmisję.

Zwracana wartość to 0 w przypadku sukcesu i -1 w przypadku niepowodzenia.


2

w moim teście.

close wyśle ​​pakiet fin i natychmiast zniszczy fd, gdy gniazdo nie zostanie udostępnione innym procesom

shutdown SHUT_RD , proces może nadal odzyskiwać dane z gniazda, ale recvzwróci 0, jeśli bufor TCP jest pusty. Po przesłaniu przez peer więcej danych, recvponownie zwróci dane.

shutdown SHUT_WR wyśle ​​pakiet fin, aby wskazać, że dalsze wysyłanie jest niedozwolone. peer może odzyskać dane, ale odzyska 0, jeśli jego bufor TCP będzie pusty

shutdown SHUT_RDWR (równy użyciu SHUT_RD i SHUT_WR ) wyśle ​​pierwszy pakiet, jeśli peer wyśle ​​więcej danych.


Właśnie spróbowałem i close()wysłałem RST na Linuksie zamiast FIN.
Pavel Šimerda

Czy naprawdę chciałeś też powiedzieć, że po wyłączeniu strony odczytu program otrzyma więcej danych?
Pavel Šimerda

@ PavelŠimerda i myślę, że może to zależeć od stanu tcp, aby wysłać RST lub FIN? Nie jestem pewny.
simpx

1. „Po przesłaniu większej ilości danych przez urządzenie równorzędne recv()zwróci dane” jest niepoprawne. 2. Zachowanie, jeśli peer wysyła później więcej danych, SHUT_RDzależy od platformy.
Markiz Lorne

@EJP Próbowałem sam, przepraszam, zapomniałem, którą platformę robię w tym teście, centos lub Mac. i to właśnie dostałem
simpx
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.