2013-02-08 15 views
6

Sto eseguendo il debug di un programma per socket Linux basato su c. Come tutti gli esempi disponibili in siti web, ho applicato la seguente struttura:Socket Linux: come rilevare la rete disconnessa in un programma client?

sockfd= socket(AF_INET, SOCK_STREAM, 0); 

connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)); 

send_bytes = send(sockfd, sock_buff, (size_t)buff_bytes, MSG_DONTWAIT); 

posso rilevare la disconnessione quando il server chiude la sua rimozione programma server. Ma se disconnetto il cavo ethernet, la funzione send restituisce comunque valori positivi anziché -1.

Come posso verificare la connessione di rete in un programma client supponendo che non posso cambiare lato server?

risposta

28

Ma se scollego il cavo Ethernet, la funzione di invio restituisce ancora i valori positivi anziché -1.

Prima di tutto si deve sapere send in realtà non inviare nulla, è solo una chiamata di funzione/sistema di memoria-la copia. Copia i dati dal tuo processo al kernel - a volte il kernel recupererà quei dati e li invierà all'altro lato dopo averlo confezionato in segmenti e pacchetti. Pertanto send può restituire solo un errore se:

  • La presa è valido (per esempio descrittore di file fasullo)
  • Il collegamento è chiaramente valido, per esempio, non è stato stabilito oppure è già stata terminata in qualche modo (FIN, RST, timeout - vedi sotto)
  • non c'è più spazio per copiare i dati

Il punto principale è che send non invia nulla e quindi il suo codice di ritorno non dice nulla sui dati che effettivamente raggiungono l'altro lato.

Torna alla tua domanda, quando TCP invia i dati si aspetta una conferma valida in un ragionevole lasso di tempo. Se non ne riceve uno, si reinvia. Quanto spesso si rispedisce? Ogni stack TCP fa le cose in modo diverso, ma la norma è usare backoff esponenziali. Cioè, prima attendi 1 secondo, poi 2, poi 4 e così via. In alcune pile questo processo può richiedere alcuni minuti.

Il punto principale è che in caso di un'interruzione TCP dichiarerà una connessione morta solo dopo un grave periodo di silenzio (su Linux fa qualcosa come 15 tentativi - più di 5 minuti).

Un modo per risolvere questo è implementare un meccanismo di riconoscimento nell'applicazione. Ad esempio, è possibile inviare una richiesta al server "risposta entro 5 secondi o dichiarare questa connessione non valida" e quindi recv con un timeout.

1

Controllare il valore di ritorno, e vedere se è uguale a questo valore:

EPIPE
Questo socket è stato collegato ma la connessione è ora interrotta. In questo caso, send genera prima un segnale SIGPIPE; se quel segnale viene ignorato o bloccato, o se il suo gestore ritorna, quindi inviare fallisce con EPIPE.

Aggiungere un controllo per il segnale SIGPIPE nel gestore, per renderlo più controllabile.

0

Non è possibile rilevare il cavo Ethernet scollegato solo con la funzione call write(). Ciò avviene a causa della ritrasmissione tcp eseguita dallo stack TCP senza la tua coscienza. Ecco le soluzioni.

Anche se è già stata impostata l'opzione keepalive sul socket dell'applicazione, non è possibile rilevare in tempo lo stato di connessione guasto del socket, nel caso in cui l'app continui a scrivere sul socket. Ciò è dovuto alla ritrasmissione tcp da parte dello stack tcp del kernel. tcp_retries1 e tcp_retries2 sono parametri del kernel per la configurazione del timeout di ritrasmissione tcp. È difficile prevedere l'ora esatta del timeout di ritrasmissione perché è calcolata dal meccanismo RTT. Puoi vedere questo calcolo in rfc793. (3.7. Data Communication)

https://www.rfc-editor.org/rfc/rfc793.txt

Ogni piattaforme hanno configurazioni del kernel per tcp ritrasmissione.

Linux : tcp_retries1, tcp_retries2 : (exist in /proc/sys/net/ipv4) 

http://linux.die.net/man/7/tcp

HPUX : tcp_ip_notify_interval, tcp_ip_abort_interval 

http://www.hpuxtips.es/?q=node/53

AIX : rto_low, rto_high, rto_length, rto_limit 

http://www-903.ibm.com/kr/event/download/200804_324_swma/socket.pdf

È necessario impostare valore più basso per tcp_retries2 (default 15) se si desidera rilevare presto morti connessione, ma è tempo non preciso come ho già fatto disse. Inoltre, attualmente non è possibile impostare tali valori solo per singolo socket. Quelli sono parametri globali del kernel. C'era qualche prova per applicare l'opzione socket di ritrasmissione tcp per singolo socket (http://patchwork.ozlabs.org/patch/55236/), ma non penso che sia stata applicata alla linea principale del kernel. Non riesco a trovare la definizione di queste opzioni nei file di intestazione di sistema.

Per riferimento, è possibile monitorare l'opzione presa keepalive tramite 'netstat --timers' come di seguito. https://stackoverflow.com/questions/34914278

netstat -c --timer | grep "192.0.0.1:43245    192.0.68.1:49742" 

tcp  0  0 192.0.0.1:43245    192.0.68.1:49742   ESTABLISHED keepalive (1.92/0/0) 
tcp  0  0 192.0.0.1:43245    192.0.68.1:49742   ESTABLISHED keepalive (0.71/0/0) 
tcp  0  0 192.0.0.1:43245    192.0.68.1:49742   ESTABLISHED keepalive (9.46/0/1) 
tcp  0  0 192.0.0.1:43245    192.0.68.1:49742   ESTABLISHED keepalive (8.30/0/1) 
tcp  0  0 192.0.0.1:43245    192.0.68.1:49742   ESTABLISHED keepalive (7.14/0/1) 
tcp  0  0 192.0.0.1:43245    192.0.68.1:49742   ESTABLISHED keepalive (5.98/0/1) 
tcp  0  0 192.0.0.1:43245    192.0.68.1:49742   ESTABLISHED keepalive (4.82/0/1) 

Inoltre, quando capita per timeout keepalive, si possono incontrare diversi eventi di ritorno seconda piattaforme in uso, in modo che non si deve decidere lo stato della connessione morto solo da eventi di ritorno. Ad esempio, HP restituisce l'evento POLLERR e AIX restituisce solo l'evento POLLIN quando si verifica il timeout keepalive. Si verificherà l'errore ETIMEDOUT nella chiamata recv() in quel momento.

Nella recente versione del kernel (dal 2.6.37), è possibile utilizzare l'opzione TCP_USER_TIMEOUT funzionerà correttamente. Questa opzione può essere utilizzata per una presa singola.

Infine, è possibile utilizzare la funzione di lettura con il flag MSG_PEEK, che consente di verificare che il socket sia a posto. (MSG_PEEK fa semplicemente capolino se i dati arrivano al buffer dello stack del kernel e non copia mai i dati nel buffer dell'utente.) Quindi puoi usare questo flag solo per verificare che il socket sia a posto senza alcun effetto collaterale.

Problemi correlati