chiusura vs presa di spegnimento?


214

In C, ho capito che se chiudiamo un socket, significa che il socket verrà distrutto e potrà essere riutilizzato in seguito.

Che ne dici di spegnimento? La descrizione dice che chiude metà di una connessione duplex a quel socket. Ma quel socket verrà distrutto come una closechiamata di sistema?


Non può essere riutilizzato in seguito. È chiuso. Finito. Fatto.
Marchese di Lorne,

Risposte:


191

Questo è spiegato nella guida alla rete di Beej. shutdownè un modo flessibile per bloccare la comunicazione in una o entrambe le direzioni. Quando il secondo parametro è SHUT_RDWR, bloccherà sia l'invio che la ricezione (come close). Però,close è il modo per distruggere effettivamente un socket.

Con shutdown, sarai comunque in grado di ricevere i dati in sospeso del peer già inviato (grazie a Joey Adams per averlo notato).


23
Ricorda, anche se chiudi () un socket TCP, non sarà necessariamente immediatamente riutilizzabile, poiché sarà in uno stato TIME_WAIT mentre il sistema operativo si assicura che non ci siano pacchetti in sospeso che potrebbero essere confusi come nuove informazioni se dovevano riutilizzare immediatamente quel socket per qualcos'altro.
alesplin,

91
La grande differenza tra arresto e chiusura su un socket è il comportamento quando il socket è condiviso da altri processi. Un shutdown () influenza tutte le copie del socket mentre close () influenza solo il descrittore di file in un processo.
Zan Lynx,

5
Uno dei motivi per cui potresti voler usare shutdownentrambe le direzioni ma non closeè se hai fatto FILEriferimento al socket usando fdopen. Se sei closeil socket, un nuovo file aperto potrebbe essere assegnato lo stesso fd, e il successivo uso del FILEleggerà / scriverà nel posto sbagliato che potrebbe essere molto male. Se lo fai shutdown, l'uso successivo del FILEcomando darà solo errori fino a quando non fcloseviene chiamato.
R .. GitHub smette di aiutare ICE il

25
Il commento su TIME_WAIT non è corretto. Questo vale per le porte, non per i socket. Non è possibile riutilizzare i socket.
Marchese di Lorne,

48
-1. Sia questo post che il link omettono un'importante ragione concettuale per voler usare shutdown: segnalare EOF al peer ed essere ancora in grado di ricevere dati in sospeso inviati dal peer.
Joey Adams,

144

Nessuna delle risposte esistenti dice alla gente come shutdowneclose funziona a livello di protocollo TCP, quindi vale la pena aggiungere questo.

Una connessione TCP standard viene terminata dalla finalizzazione a 4 vie:

  1. Una volta che un partecipante non ha più dati da inviare, invia un pacchetto FIN all'altro
  2. L'altra parte restituisce un ACK per la FIN.
  3. Quando anche l'altra parte ha terminato il trasferimento dei dati, invia un altro pacchetto FIN
  4. Il partecipante iniziale restituisce un ACK e finalizza il trasferimento.

Tuttavia, esiste un altro modo "emergente" per chiudere una connessione TCP:

  1. Un partecipante invia un pacchetto RST e abbandona la connessione
  2. L'altra parte riceve un RST e quindi abbandona anche la connessione

Nel mio test con Wireshark, con le opzioni socket predefinite, shutdowninvia un pacchetto FIN all'altra estremità, ma è tutto ciò che fa. Fino a quando l'altra parte non ti invia il pacchetto FIN, è ancora possibile ricevere i dati. Una volta che questo è successo, Receiveotterrai un risultato di dimensione 0. Quindi, se sei il primo a chiudere "invia", dovresti chiudere il socket una volta terminata la ricezione dei dati.

D'altra parte, se chiami close mentre la connessione è ancora attiva (l'altro lato è ancora attivo e si potrebbero avere anche dati non inviati nel buffer di sistema), un pacchetto RST verrà inviato all'altro lato. Questo è buono per gli errori. Ad esempio, se si ritiene che l'altra parte abbia fornito dati errati o che abbia rifiutato di fornire dati (attacco DOS?), È possibile chiudere immediatamente il socket.

La mia opinione sulle regole sarebbe:

  1. Considerare shutdownprima closequando possibile
  2. Se hai finito di ricevere (dati di dimensioni 0 ricevuti) prima di decidere di chiudere, chiudi la connessione dopo l'ultimo invio (se presente).
  3. Se vuoi chiudere normalmente la connessione, chiudi la connessione (con SHUT_WR e se non ti interessa ricevere dati dopo questo punto, anche con SHUT_RD) e attendi fino a quando non ricevi un dato di dimensione 0, quindi chiudi il zoccolo.
  4. In ogni caso, se si sono verificati altri errori (timeout, ad esempio), è sufficiente chiudere il socket.

Implementazioni ideali per SHUT_RD e SHUT_WR

Quanto segue non è stato testato, affidati a tuo rischio e pericolo. Tuttavia, credo che questo sia un modo ragionevole e pratico di fare le cose.

Se lo stack TCP riceve un arresto solo con SHUT_RD, contrassegnerà questa connessione come nessun altro dato previsto. Eventuali readrichieste in sospeso e successive (indipendentemente dal thread in cui si trovano) verranno quindi restituite con risultato di dimensioni zero. Tuttavia, la connessione è ancora attiva e utilizzabile, ad esempio è ancora possibile ricevere dati OOB. Inoltre, il sistema operativo eliminerà tutti i dati ricevuti per questa connessione. Ma questo è tutto, nessun pacchetto verrà inviato dall'altra parte.

Se lo stack TCP riceve un arresto solo con SHUT_WR, deve contrassegnare questa connessione in quanto non è possibile inviare altri dati. Tutte le richieste di scrittura in sospeso saranno terminate, ma le successive richieste di scrittura falliranno. Inoltre, un pacchetto FIN verrà inviato a un'altra parte per informarli che non abbiamo più dati da inviare.


2
"se si chiama close mentre la connessione è ancora attiva" è tautologico e non provoca l'invio di un RST. (1) non è necessario. In (4), i timeout non sono necessariamente fatali per la connessione e non invariabili indicano che è possibile chiuderla.
Marchese di Lorne,

4
@EJP No, non è tautologico. È possibile shutdown()la connessione e quindi non è più attiva. Hai ancora il descrittore di file. Puoi ancora recv()dal buffer di ricezione. E devi ancora chiamare close()per disporre il descrittore di file.
Pavel Šimerda,

Questa è la risposta migliore per quanto riguarda il formato del filo. Sarei interessato a maggiori dettagli sulla chiusura con SHUT_RD. Non ci sono segnali TCP per non aspettarsi più dati, giusto? Non esiste solo FIN per la segnalazione per non inviare più dati?
Pavel Šimerda,

3
@ PavelŠimerda Sì TCP non segnala per non aspettarsi più dati. Questo dovrebbe essere considerato nei protocolli di alto livello. Secondo me, questo in generale non è necessario. Puoi chiudere la tua porta, ma non puoi impedire alle persone di mettere regali davanti alla porta. Questa è la LORO decisione, non la tua.
Earth Engine,

1
@EJP Hai qualche argomento reale? Altrimenti non mi interessa.
Pavel Šimerda,

35

Ci sono alcune limitazioni close()che possono essere evitate se si usa shutdown()invece.

close()terminerà entrambe le direzioni su una connessione TCP. A volte vuoi dire all'altro endpoint che hai finito di inviare i dati, ma vuoi comunque ricevere i dati.

close()diminuisce il conteggio dei riferimenti dei descrittori (gestito nella voce della tabella dei file e conta il numero di descrittori attualmente aperti che si riferiscono a un file / socket) e non chiude il socket / file se il descrittore non è 0. Ciò significa che se si sta eseguendo il fork, la pulizia avviene solo dopo che il conteggio dei riferimenti scende a 0. Con shutdown()uno si può iniziare la normale sequenza di chiusura TCP ignorando il conteggio dei riferimenti.

I parametri sono i seguenti:

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

int how può essere:

SHUT_RDo Non 0 sono consentite ulteriori ricezioni

SHUT_WRo Non 1 sono consentiti ulteriori invii

SHUT_RDWRo Non 2 sono consentiti ulteriori invii e ricezioni


8
Le due funzioni hanno scopi completamente diversi. Il fatto che la chiusura finale su un socket avvii una sequenza di arresto se non è già stata avviata una modifica non cambia il fatto che la chiusura è per ripulire le strutture dei dati del socket e l'arresto è per l'avvio di una sequenza di arresto a livello di tcp.
Len Holgate,

25
Non è possibile "utilizzare l'arresto invece". Puoi anche usarlo . ma è necessario chiudere il socket qualche volta.
Marchese di Lorne,

15

Questo può essere specifico della piattaforma, ne dubito in qualche modo, ma comunque, la migliore spiegazione che ho visto è qui in questa pagina msdn dove spiegano l'arresto, le opzioni di permanenza, la chiusura del socket e le sequenze generali di terminazione della connessione.

In breve, utilizzare shutdown per inviare una sequenza di shutdown a livello TCP e usare close per liberare le risorse utilizzate dalle strutture dati del socket nel processo. Se non hai emesso una sequenza di spegnimento esplicito prima di chiamare close, ne viene avviata una per te.


9

Ho anche avuto successo sotto Linux usando shutdown()da un pthread per forzare un altro pthread attualmente bloccato connect()ad interrompere presto.

Sotto altri sistemi operativi (almeno OSX), ho trovato che chiamare close()era sufficiente per connect()fallire.


7

"shutdown () in realtà non chiude il descrittore di file, ma ne modifica solo la fruibilità. Per liberare un descrittore di socket, devi usare close ()." 1


3

Vicino

Quando hai finito di usare un socket, puoi semplicemente chiudere il suo descrittore di file con close; Se ci sono ancora dati in attesa di essere trasmessi sulla connessione, normalmente chiudi cerca di completare questa trasmissione. È possibile controllare questo comportamento utilizzando l'opzione socket SO_LINGER per specificare un periodo di timeout; vedi Opzioni socket.

Spegnimento

È inoltre possibile arrestare solo la ricezione o la trasmissione su una connessione chiamando shutdown.

La funzione di spegnimento arresta la connessione della presa. Il suo argomento specifica come l'azione da eseguire: 0 Interrompere la ricezione dei dati per questo socket. Se arrivano altri dati, rifiutali. 1 Interrompere il tentativo di trasmettere dati da questo socket. Elimina tutti i dati in attesa di essere inviati. Smetti di cercare il riconoscimento dei dati già inviati; non ritrasmetterlo se viene perso. 2 Arrestare sia la ricezione che la trasmissione.

Il valore restituito è 0 in caso di successo e -1 in caso di fallimento.


2

nel mio test.

close invierà il pacchetto pinna e distruggerà immediatamente fd quando il socket non è condiviso con altri processi

shutdown SHUT_RD , il processo può ancora recuperare i dati dal socket, ma recvrestituirà 0 se il buffer TCP è vuoto. Dopo che il peer invia più dati, recvrestituirà nuovamente i dati.

shutdown SHUT_WR invierà il pacchetto di pinne per indicare che gli Altri invii non sono consentiti. il peer è in grado di recuperare i dati, ma ricadrà 0 se il suo buffer TCP è vuoto

shutdown SHUT_RDWR (uguale a utilizzare sia SHUT_RD che SHUT_WR ) invierà il primo pacchetto se il peer invia più dati.


Ho appena provato e close()inviato RST su Linux invece di FIN.
Pavel Šimerda,

Volevi anche affermare che dopo aver chiuso il lato di lettura, il programma riceverà più dati?
Pavel Šimerda,

@ PavelŠimerda penso che possa dipendere dallo stato tcp per inviare RST o FIN? non ne sono sicuro.
Simpx,

1. 'Dopo che il peer invia più dati, recv()restituirà nuovamente i dati' non è corretto. 2. Il comportamento se il peer invia più dati dopo SHUT_RDdipende dalla piattaforma.
Marchese di Lorne,

@EJP Ho provato me stesso, scusami ho dimenticato quale piattaforma faccio questo test, centos o Mac. e questo è ciò che ho ottenuto
simpx il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.