2012-12-23 5 views
12

Sono un po 'turbato dal fatto che questo non possa essere gestito in modo elegante, dopo aver provato diverse soluzioni (this, this e diverse altre) menzionato nelle risposte a diverse domande SO, non riesco ancora a rilevare la disconnessione socket (scollegando il cavo) .Rilevamento della disconnessione della presa?

Sto usando il socket NIO non bloccante, tutto funziona perfettamente tranne che non trovo alcun modo di rilevare la disconnessione del server.

Ho il seguente codice:

while (true) { 
    handlePendingChanges(); 

    int selectedNum = selector.select(3000); 
    if (selectedNum > 0) { 
     SelectionKey key = null; 
     try { 
      Iterator<SelectionKey> keyIterator = selector.selelctedKeys().iterator(); 
      while (keyIterator.hasNext()) { 
       key = keyIterator.next(); 
       if (!key.isValid()) 
        continue; 

       System.out.println("key state: " + key.isReadable() + ", " + key.isWritable()); 

       if (key.isConnectable()) { 
        finishConnection(key); 
       } else if (key.isReadable()) { 
        onRead(key); 
       } else if (key.isWritable()) { 
        onWrite(key); 
       } 
      } 
     } catch (Exception e) { 
      e.printStackTrace(); 
      System.err.println("I am happy that I can catch some errors."); 
     } finally { 
      selector.selectedKeys().clear(); 
     } 
    } 
} 

Mentre i SocketChannels vengono letti, ho scollegare il cavo, e Selector.select() inizia filatura e ritorno 0, ora non ho alcuna possibilità di leggere o scrittura i canali, perché il codice di scrittura principale & è protetto da if (selectedNum > 0), ora questa è la prima confusione che esce dalla mia testa, da this answer, si dice che quando il canale è rotto, select() restituirà, e la selezione chiave per il canale indicherà leggibile/scrivibile, ma a quanto pare non è il caso qui, le chiavi non sono selezionati, select() restituisce comunque 0.

Inoltre, da EJP's answer a una domanda simile:

Se il peer chiude la presa:

  • read() restituisce -1
  • readLine() restituisce null
  • readXXX() thr OWS EOFException, per qualsiasi altro X.

Non è il caso nemmeno qui, ho provato commentando if (selectedNum > 0) e utilizzando selector.keys().iterator() per ottenere tutte le chiavi a prescindere o meno sono selezionati, la lettura da quelle chiavi non ritorna -1 (0 restituito invece), e la scrittura su quei tasti non ottiene EOFException generata. Ho notato solo una cosa, che anche i tasti non sono selezionati, key.isReadable() restituisce true mentre key.isWritable() restituisce false (suppongo che ciò potrebbe essere dovuto al fatto che non ho registrato i tasti per OP_WRITE).

La mia domanda è perché il socket Java si comporta in questo modo o c'è qualcosa che ho fatto di sbagliato?

+1

È possibile che il sistema operativo non dichiari ancora la connessione interrotta: è possibile ricollegare il cavo e in teoria è possibile riprendere la connessione. – MvG

+0

Sì, a volte la connessione può essere ripristinata quando ho ricollegato il cavo, a volte 'select()' ha continuato a restituire 0 e non posso cancellare la chiave manualmente. – neevek

risposta

19

Hai scoperto che hai bisogno di timer e heartbeat su una connessione TCP.

Se si scollega il cavo di rete, la connessione TCP potrebbe non essere interrotta. Se non si ha nulla da inviare, lo stack TCP/IP non ha nulla da inviare, non sa che un cavo è andato da qualche parte, o che il peer-PC improvvisamente ha preso fuoco. Quella connessione TCP potrebbe essere considerata aperta fino a quando non riavvii il tuo server anni dopo.

Pensateci in questo modo; come può la connessione TCP sapere che l'altra estremità è caduta dalla rete - è fuori dalla rete quindi non può dirvi questo fatto.

Alcuni sistemi sono in grado di rilevare questo se si scollega il cavo dal server e altri no. Se scolleghi il cavo dall'altra parte, ad es. un interruttore ethernet, che non verrà rilevato.

Ecco perché uno sempre bisogno timer supervisore (che ad esempio, inviare un messaggio di battito cardiaco al peer, o chiudere una connessione TCP sulla base di alcuna attività per un determinato periodo di tempo) per una connessione TCP,

Uno molto a buon mercato modo per evitare almeno le connessioni TCP di cui si leggono solo i dati, non si scrive mai, di rimanere aggiornati per anni, è di abilitare TCP keepalive su un socket TCP - essere consapevoli che i timeout predefiniti per TCP keepalive sono spesso 2 ore.

+0

La tua spiegazione cancella completamente la mia confusione. ma c'è ancora una cosa che voglio sapere, a volte quando ricollego il cavo, la connessione riprende, a volte non lo è, perché è così? – neevek

+1

@neevek Probabilmente la fine della trasmissione è scaduta.La fine trasmittente rileverà che l'altra estremità è scomparsa, in quanto non riceve alcun messaggio, quindi tra le altre cose dipenderà dal fatto che si ricolleghi il cavo prima che lo stack tcp interrompa la connessione. – nos

+0

Uno non ha sempre bisogno di timer supervisore. HTTP, il protocl di applicazione più utilizzato sul pianeta, non ne ha uno, per esempio. I timeout di lettura e le IOException in scrittura sono sufficienti. – EJP

8

Né si applicano tali risposte. Il primo riguarda il caso in cui la connessione è interrotta e la seconda (la mia) riguarda il caso in cui il peer chiude la connessione.

In una connessione TCP, a meno che i dati non vengano inviati o ricevuti, in linea di principio non si tratta di tirare un cavo che dovrebbe interrompere una connessione, poiché TCP è progettato appositamente per essere robusto in questo genere di cose, e c'è sicuramente niente a riguardo che dovrebbe guardare all'applicazione locale come la chiusura tra pari.

L'unico modo per rilevare una connessione interrotta in TCP è tentare di inviare dati attraverso di esso, o interpretare un timeout di lettura come una connessione persa dopo un intervallo appropriato, che è una decisione dell'applicazione.

È anche possibile impostare TCP keep-alive per consentire il rilevamento di connessioni interrotte e in alcuni sistemi è persino possibile controllare il timeout per socket. Tuttavia, non via Java, quindi sei bloccato con il sistema predefinito, che dovrebbe essere di due ore a meno che non sia stato modificato.

Il tuo codice dovrebbe chiamare keyIterator.remove() dopo aver chiamato keyIterator.next().

+0

Ehi, @EJP, sapevo che saresti venuto a salvare, grazie. * È inoltre possibile impostare TCP keep-alive per abilitare il rilevamento di connessioni interrotte *, quale ruolo svolge keep-alive qui nel rilevamento di una connessione interrotta? qual è la differenza tra l'impostazione e non l'impostazione keep-alive? come per 'keyIterator.remove()', ho già usato 'selector.selectedKeys(). clear()' in finally block. – neevek

+0

@neveek Perso quello. TCP keep-alive invia un pacchetto di tanto in tanto, uno che richiede una risposta, e se non arriva (prendendo in considerazione tentativi e timeout) la connessione verrà considerata interrotta: si otterrà un 'reset della connessione' sul prossimo I/O. – EJP

+0

Non sto implementando un protocollo personalizzato, sto usando HTTP, quindi se un pacchetto viene inviato via cavo di tanto in tanto, quel pacchetto verrà interpretato come parte dell'header o del corpo HTTP? e se io, come cliente, ricevo il pacchetto keep-alive, come posso gestirlo? – neevek