Come chiudere forzatamente un socket in TIME_WAIT?


113

Eseguo un programma particolare su Linux che a volte si blocca. Se lo apri rapidamente dopo, è in ascolto sul socket 49201 anziché 49200 come ha fatto per la prima volta. netstat rivela che 49200 è nello stato TIME_WAIT.

Esiste un programma che puoi eseguire per forzare immediatamente lo spostamento del socket dallo stato TIME_WAIT?


1
Se sei qui a causa di "troppi TIME_WAITsul server" , salta semplicemente le prime tre risposte che evitano la domanda invece di rispondere.
Pacerier,

Risposte:


148
/etc/init.d/networking restart

Lasciami elaborare. Il protocollo TCP (Transmission Control Protocol) è progettato per essere un protocollo di trasmissione dati bidirezionale, ordinato e affidabile tra due punti finali (programmi). In questo contesto, il termine affidabile significa che ritrasmetterà i pacchetti se si perde nel mezzo. TCP garantisce l'affidabilità inviando indietro i pacchetti di riconoscimento (ACK) per un singolo o un intervallo di pacchetti ricevuti dal peer.

Lo stesso vale per i segnali di controllo come richiesta / risposta di terminazione. RFC 793 definisce lo stato TIME-WAIT come segue:

TIME-WAIT: rappresenta l'attesa del tempo sufficiente per assicurarsi che il TCP remoto abbia ricevuto il riconoscimento della sua richiesta di terminazione della connessione.

Vedi il seguente diagramma di stato TCP: testo alternativo

TCP è un protocollo di comunicazione bidirezionale, quindi quando viene stabilita la connessione, non c'è differenza tra client e server. Inoltre, uno può chiamare quits ed entrambi i peer devono concordare la chiusura per chiudere completamente una connessione TCP stabilita.

Chiamiamo il primo a chiamare le uscite come il più vicino attivo, e l'altro scruta il più vicino passivo. Quando il vicino attivo invia FIN, lo stato passa a FIN-WAIT-1. Quindi riceve un ACK per il FIN inviato e lo stato passa a FIN-WAIT-2. Una volta che riceve FIN anche dal più vicino passivo, il più vicino attivo invia l'ACK al FIN e lo stato passa a TIME-WAIT. Nel caso in cui il vicino passivo non abbia ricevuto l'ACK al secondo FIN, ritrasmetterà il pacchetto FIN.

RFC 793 imposta il TIME-OUT su due volte la durata massima del segmento o 2MSL. Poiché MSL, il tempo massimo che un pacchetto può spostarsi su Internet, è impostato su 2 minuti, 2MSL è 4 minuti. Poiché non esiste alcun ACK per un ACK, il dispositivo di chiusura attivo non può fare altro che attendere 4 minuti se aderisce correttamente al protocollo TCP / IP, nel caso in cui il mittente passivo non abbia ricevuto l'ACK nel proprio FIN (teoricamente) .

In realtà, i pacchetti mancanti sono probabilmente rari e molto rari se tutto accade all'interno della LAN o all'interno di una singola macchina.

Per rispondere alla domanda alla lettera, Come chiudere forzatamente un socket in TIME_WAIT ?, mi atterrò comunque alla mia risposta originale:

/etc/init.d/networking restart

In pratica, lo programmerei in modo che ignori lo stato TIME-WAIT usando l'opzione SO_REUSEADDR come menzionato WMR. Cosa fa esattamente SO_REUSEADDR?

Questa opzione socket dice al kernel che anche se questa porta è occupata (nello
stato TIME_WAIT), andare avanti e riutilizzarla comunque. Se è occupato, ma con un altro stato, otterrai comunque un indirizzo già in uso. È utile se il server è stato spento e quindi riavviato immediatamente mentre i socket sono ancora attivi sulla sua porta. Dovresti essere consapevole che se arrivano dati imprevisti, potrebbero confondere il tuo server, ma mentre ciò è possibile, è improbabile.


8
Ottima risposta, ma non la risposta corretta alla sua domanda. Il riavvio della rete funzionerebbe, ma allo stesso modo si riavvierebbe, quindi non può essere giusto.
Chris Huang-Leaver,

3
@Chris Huang-Leaver, la domanda è "Esiste un programma che puoi eseguire per forzare immediatamente lo spostamento del socket dallo stato TIME_WAIT?" se il riavvio potrebbe essere considerato in esecuzione un programma, anche questa sarebbe una risposta giusta. Perché pensi che questo non possa essere giusto?
Eugene Yokota,

8
WMR ha la risposta più utile (che è quello che faccio quando incontro questo tipo di problema). Il riavvio della rete è troppo drastico per essere una soluzione e potrebbe richiedere più tempo della semplice attesa del timeout. La risposta corretta alla sua domanda è "No", ma SO non ti consente di digitare due lettere di risposta :-)
Chris Huang- Almeno il

6
oh okay, la prossima volta che qualche processo si bloccherà su SIGTERM, romperò il mio computer invece di ripararlo.
Longpoke,

La generalizzazione di questo è "riavvia i servizi di rete". La posizione specifica /etc/init.d/networkingè specifica della piattaforma (Debian?), Quindi la precisa riga di comando sarà diversa (a volte piuttosto radicalmente) per altri sistemi. Concordo con altri commentatori sul fatto che questo sembra un grave eccessivo e ovviamente distruttivo per qualsiasi servizio di rete non correlato.
Tripleee

51

Non so se hai il codice sorgente di quel particolare programma che stai eseguendo, ma in tal caso potresti semplicemente impostare SO_REUSEADDR tramite il setsockopt(2)quale ti consente di associare lo stesso indirizzo locale anche se il socket è nello stato TIME_WAIT (a meno che socket sta ascoltando attivamente, vedi socket(7)).

Per ulteriori informazioni sullo stato TIME_WAIT, consultare le Domande frequenti sul socket Unix .


ma non ho riscontrato l'errore già associato. quando eseguo di nuovo il programma che ascolta in post (123456), vedo anche che il sistema mostra TIME_WAIT per quella porta ma riesco comunque a connettermi. perché?
Jayapal Chandran,

2
Anche con SO_REUSEADDR, è ancora possibile ottenere l'errore "Indirizzo già in uso". Per i dettagli, consultare hea-www.harvard.edu/~fine/Tech/addrinuse.html .
Jingguo Yao,

@WMR SO_REUSEADDRnon "chiude" un socket. Ti consente semplicemente di riutilizzare quelli che sono già aperti. Quindi la domanda è ancora "Come chiudere forzatamente un socket TIME_WAIT?"
Pacerier,

Questa è la risposta corretta, ma la domanda non era totalmente corretta. Almeno risolto bene il mio problema (non come riavviare l'intera rete interrompendo anche tutte le altre connessioni).
V-Mark,

SO_REUSEADDRlasceremo bind()procedere; ma se poi vuoi ascoltare quel socket, listen()tornerà EADDRINUSElo stesso. In altre parole, questa risposta può aiutare il software client che utilizza porte effimere, ma non risolve il problema relativo al software server.
Sarà il

33

Per quanto ne so non c'è modo di chiudere forzatamente il socket al di fuori della scrittura di un gestore di segnale migliore nel programma, ma esiste un file / proc che controlla quanto tempo impiega il timeout. Il file è

/proc/sys/net/ipv4/tcp_tw_recycle

e puoi impostare il timeout su 1 secondo in questo modo:

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

Tuttavia, questa pagina contiene un avviso sui possibili problemi di affidabilità durante l'impostazione di questa variabile.

C'è anche un file correlato

/proc/sys/net/ipv4/tcp_tw_reuse

che controlla se i socket TIME_WAIT possono essere riutilizzati (presumibilmente senza alcun timeout).

Per inciso, la documentazione del kernel ti avverte di non modificare nessuno di questi valori senza "consigli / richieste di esperti tecnici". Non lo sono.

Il programma deve essere stato scritto per tentare un'associazione alla porta 49200 e quindi incrementare di 1 se la porta è già in uso. Pertanto, se si ha il controllo del codice sorgente, è possibile modificare questo comportamento in modo da attendere alcuni secondi e riprovare sulla stessa porta, invece di incrementare.


penso che i secondi due esempi dovrebbero essere s / rw / tw / modificherei, ma non ho abbastanza rep.

1
Tratto dalla documentazione del kernel: Attenzione. Sia tcp_tw_recycle che tcp_tw_reuse possono causare problemi. Non è necessario abilitarlo senza comprendere la topologia di rete tra i nodi che stanno utilizzando o utilizzati dal nodo in cui il parametro è abilitato. Le connessioni che passano attraverso nodi che sono a conoscenza degli stati di connessione TCP, come firewall, NAT o bilanciamento del carico, potrebbero iniziare a eliminare i frame a causa dell'impostazione. Il problema diventerà visibile quando c'è un numero sufficiente di connessioni.

L'impostazione su 1funziona per connessioni future, ma per quanto riguarda quelle attuali che sono già aperte?
Pacerier,

18

In realtà c'è un modo per uccidere una connessione: killcx . Sostengono che funziona in qualsiasi stato della connessione (che non ho verificato). È necessario conoscere l'interfaccia in cui avviene la comunicazione, tuttavia sembra presupporre eth0 per impostazione predefinita.

AGGIORNAMENTO: un'altra soluzione è il cutter che arriva nei repository di alcune distribuzioni di Linux.


3

Un'altra opzione è quella di utilizzare l'opzione SO_LINGER con un timeout di 0. In questo modo, quando si chiude il socket viene forzatamente chiuso, inviando un RST invece di entrare nel comportamento di chiusura FIN / ACK. Ciò eviterà lo stato TIME_WAIT e potrebbe essere più appropriato per alcuni usi.


2
Inoltre, perde tutti i dati in uscita che sono ancora in transito e può causare un errore all'altro capo. Non consigliato.
user207421

@EJP Fallire presto è quasi sempre la chiamata giusta. Il networking non è affidabile e la lotta che rallenterà le cose. Un'app bloccata non può presumere che tutti i dati siano stati trasmessi in modo sicuro.
Tobu,

1
In realtà, lo consiglierei ogni giorno in cui l'altro endpoint è un gateway bus industriale incorporato buggy che implementa il proprio trasporto affidabile a livello di applicazione su TCP, dove detto trasporto impedisce la chiusura costante della connessione a meno che non riceva RST e quindi si riempia il limite di connessione su quel gateway. Là. Ti ho dato un esempio molto specifico e molto reale che, purtroppo, richiede il ricorso a hack come questo.
Andyn,

@Tobu Networking non è affidabile, ma TCP cerca di esserlo, e peggiorare le cose non significa migliorare nulla, e lasciare che TCP faccia il suo lavoro non significa "combattere" nulla.
user207421

2

Una soluzione alternativa sarebbe quella di avere un proxy affidabile o un software di port forwarding in ascolto sulla porta 49200, quindi inoltrare la connessione a una delle diverse istanze del programma meno affidabile usando porte diverse ... HAPROXY mi viene in mente.

Per inciso, la porta su cui ti connetti è piuttosto alta. Potresti provare a usarne uno inutilizzato appena sopra l'intervallo 0-1024. È meno probabile che il sistema utilizzi un numero di porta inferiore come porta effimera.


0

TIME_WAIT è il problema più comune nell'architettura del server client di programmazione socket. Attendere qualche secondo provando periodicamente è la soluzione migliore per questo. Per le applicazioni in tempo reale hanno bisogno che il server si alzi immediatamente C'è un'opzione SO_REUSEADDR per loro.

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.