2010-10-07 7 views
12

Nella mia applicazione ho riscontrato un errore che non sembra essere riproducibile. Ho una connessione socket TCP che non è riuscita e l'applicazione ha provato a ricollegarla. Nella seconda chiamata a connect() che tenta di riconnettersi, ho ottenuto un risultato di errore con errno == EADDRNOTAVAIL che la pagina man per connect() dice significa: "L'indirizzo specificato non è disponibile dal computer locale."Perché connect() fornisce EADDRNOTAVAIL?

Guardando la chiamata a connect(), il secondo argomento sembra essere l'indirizzo a cui si riferisce l'errore, ma come ho capito, questo argomento è l'indirizzo di socket TCP dell'host remoto, quindi sono confuso sulla pagina man riferita alla macchina locale. È possibile che questo indirizzo sull'host del socket TCP remoto non sia disponibile dal mio computer locale? Se è così, perché dovrebbe essere? Doveva essere riuscito a chiamare connect() la prima volta prima che la connessione fallisse e tentò di riconnettersi e ottenne questo errore. Gli argomenti per connect() erano gli stessi entrambe le volte.

Questo errore sarebbe transitorio e, se avessi provato a chiamare di nuovo, sarebbe potuto andare via se avessi aspettato abbastanza? In caso contrario, come dovrei provare a recuperare da questo errore?

+0

Ho un problema simile in un grande ammasso Redis. Qual è il tuo caso d'uso? Il collegamento – Riccardo

risposta

19

controllare questo link

http://www.toptip.ca/2010/02/linux-eaddrnotavail-address-not.html

EDIT: Sì, lo scopo di aggiungere di più, ma ha dovuto tagliare lì a causa di una situazione di emergenza

hai chiuso la presa prima di tentare di riconnettersi? La chiusura dirà al sistema che il socketpair (ip/port) è ora libero.

Qui ci sono elementi aggiuntivi anche guardare:

  • Se la porta locale è già collegato alla data IP remoto e la porta (cioè, c'è già un socketpair identico), riceverete questo errore (si veda collegamento bug sotto).
  • Il binding di un indirizzo socket che non è quello locale produrrà questo errore. se gli indirizzi IP di una macchina sono 127.0.0.1 e 1.2.3.4 e stai tentando di eseguire il binding a 1.2.3.5, otterrai questo errore.
  • EADDRNOTAVAIL: l'indirizzo specificato non è disponibile sulla macchina remota o il campo dell'indirizzo della struttura del nome è tutti zero.

Collegamento con un bug simile al tuo (risposta è vicino al fondo)

http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4294599

Sembra che il socket è sostanzialmente bloccato in uno degli stati interni TCP e che l'aggiunta di un ritardo per riconnessione potresti risolvere il tuo problema come sembrano aver fatto in quel bug report.

+0

è utile, ma non è sufficiente inserire il collegamento, soprattutto quando molti collegamenti diventano obsoleti e inutili. –

+0

Ecco qua :) buona risposta, David. – slezica

2

Questo può succedere anche se una porta non valida è dato, come 0.

+2

Come porta di destinazione. Se è fornito come porta locale a cui collegarsi, è valido. – EJP

0

Un'altra cosa da controllare è che l'interfaccia è attiva. Di recente mi sono confuso con questo usando i namespace di rete, dal momento che sembra che la creazione di un nuovo spazio dei nomi di rete produca un'interfaccia di loopback completamente indipendente ma non lo faccia (almeno, con le versioni di Debian wheezy delle cose). Questo mi è sfuggito per un po ', dal momento che in genere non si pensa al loopback come al solito.

1

Se non si desidera modificare il numero di porte temporanee disponibili (come suggerito da David), o sono necessarie più connessioni rispetto al massimo teorico, esistono altri due metodi per ridurre il numero di porte in uso. Tuttavia, sono a vari gradi violazioni dello standard TCP, quindi dovrebbero essere usati con cautela.

Il primo è di accendere SO_LINGER con un timeout di zero secondi, forzando lo stack TCP per inviare un pacchetto RST e svuotare lo stato della connessione. C'è una sottigliezza, però: si dovrebbe chiamare shutdown sul descrittore di file presa prima di close, in modo da avere la possibilità di inviare un pacchetto FIN prima della RST pacchetto. Così il codice sarà simile:

shutdown(fd, SHUT_RDWR); 
struct linger linger; 
linger.l_onoff = 1; 
linger.l_linger = 0; 
// todo: test for error 
setsockopt(fd, SOL_SOCKET, SO_LINGER, 
      (char *) &linger, sizeof(linger)); 
close(fd); 

Il server dovrebbe vedere solo una connessione prematura azzerato se il pacchetto FIN viene riordinato con il RST pacchetto.

Vedere TCP option SO_LINGER (zero) - when it's required per maggiori dettagli. (Sperimentalmente, non sembra avere importanza in cui si imposta setsockopt.)

Il secondo è quello di utilizzare SO_REUSEADDR e un esplicito bind (anche se sei il cliente), che permetterà a Linux di riutilizzare le porte temporanei quando si corri, prima che siano finiti. Si noti che è necessario utilizzare bind con INADDR_ANY e la porta 0, altrimenti SO_REUSEADDR non è rispettata. Il tuo codice sarà simile:

int opts = 1; 
// todo: test for error 
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, 
     (char *) &opts, sizeof(int)); 

struct sockaddr_in listen_addr; 
listen_addr.sin_family = AF_INET; 
listen_addr.sin_port = 0; 
listen_addr.sin_addr.s_addr = INADDR_ANY; 
// todo: test for error 
bind(fd, (struct sockaddr *) &listen_addr, sizeof(listen_addr)); 

// todo: test for addr 
// saddr is the struct sockaddr_in you're connecting to 
connect(fd, (struct sockaddr *) &saddr, sizeof(saddr)); 

Questa opzione è meno buono perché ci si può comunque saturare le strutture di dati interni del kernel per le connessioni TCP come da netstat -an | grep -e tcp -e udp | wc -l. Tuttavia, non inizierai a riutilizzare le porte finché questo non si verifica.

+0

L'impostazione di 'SO_LINGER' su zero ha risolto il mio problema. Grazie. – Eric