Per il mio suggerimento, leggi l'ultima sezione: "Quando utilizzare SO_LINGER con timeout 0" .
Prima di arrivare a questo, una piccola conferenza su:
- Terminazione TCP normale
TIME_WAIT
FIN
, ACK
eRST
Terminazione TCP normale
La normale sequenza di terminazione TCP è simile a questa (semplificata):
Abbiamo due pari: A e B
- Una chiamata
close()
- A invia
FIN
a B
- A entra in
FIN_WAIT_1
stato
- B riceve
FIN
- B invia
ACK
ad A
- B entra nello
CLOSE_WAIT
stato
- A riceve
ACK
- A entra in
FIN_WAIT_2
stato
- B chiama
close()
- B invia
FIN
ad A
- B entra nello
LAST_ACK
stato
- A riceve
FIN
- A invia
ACK
a B
- A entra in
TIME_WAIT
stato
- B riceve
ACK
- B passa allo
CLOSED
stato, ovvero viene rimosso dalle tabelle dei socket
TEMPO DI ATTESA
Quindi il peer che avvia la terminazione, ovvero chiama per close()
primo, finirà nello TIME_WAIT
stato.
Per capire perché lo TIME_WAIT
Stato è nostro amico, leggere la sezione 2.7 nella terza edizione "Programmazione di rete UNIX" di Stevens et al (pagina 43).
Tuttavia, può essere un problema con molti socket in TIME_WAIT
stato su un server in quanto potrebbe impedire l'accettazione di nuove connessioni.
Per ovviare a questo problema, ho visto molti suggerire di impostare l'opzione socket SO_LINGER con timeout 0 prima di chiamare close()
. Tuttavia, questa è una cattiva soluzione poiché causa la chiusura della connessione TCP con un errore.
Progettare invece il protocollo dell'applicazione in modo che la terminazione della connessione venga sempre avviata dal lato client. Se il client sa sempre quando ha letto tutti i dati rimanenti, può avviare la sequenza di terminazione. Ad esempio, un browser sa dall'intestazione Content-Length
HTTP quando ha letto tutti i dati e può avviare la chiusura. (So che in HTTP 1.1 lo manterrà aperto per un po 'per un possibile riutilizzo, quindi lo chiuderà.)
Se il server deve chiudere la connessione, progettare il protocollo dell'applicazione in modo che il server chieda al client di chiamare close()
.
Quando utilizzare SO_LINGER con timeout 0
Di nuovo, secondo la "Programmazione di rete UNIX", terza edizione, pagina 202-203, l'impostazione SO_LINGER
con timeout 0 prima della chiamata close()
farà sì che la normale sequenza di terminazione non venga avviata.
Invece, il peer impostando questa opzione e chiamando close()
invierà un RST
(ripristino della connessione) che indica una condizione di errore ed è così che verrà percepita dall'altra parte. In genere vedrai errori come "Connessione ripristinata da peer".
Pertanto, nella situazione normale è davvero una cattiva idea impostare SO_LINGER
con timeout 0 prima della chiamata close()
- d'ora in poi chiamata chiusura abortiva - in un'applicazione server.
Tuttavia, alcune situazioni lo giustificano comunque:
- Se il client della tua applicazione server si comporta male (va in timeout, restituisce dati non validi, ecc.), Una chiusura abortita ha senso per evitare di rimanere bloccati
CLOSE_WAIT
o finire nello TIME_WAIT
stato.
- Se devi riavviare la tua applicazione server che attualmente ha migliaia di connessioni client potresti considerare di impostare questa opzione socket per evitare migliaia di socket server in entrata
TIME_WAIT
(quando si chiama close()
dal lato server) in quanto ciò potrebbe impedire al server di ottenere le porte disponibili per nuove connessioni client dopo essere stato riavviato.
- A pagina 202 del libro di cui sopra si dice specificamente: "Ci sono alcune circostanze che giustificano l'utilizzo di questa funzione per inviare una chiusura abortita. Un esempio è un terminal server RS-232, che potrebbe bloccarsi per sempre nel
CLOSE_WAIT
tentativo di fornire dati a un terminale bloccato port, ma reimposterebbe correttamente la porta bloccata se riuscisse RST
a eliminare i dati in sospeso. "
Consiglierei questo lungo articolo che credo dia un'ottima risposta alla tua domanda.