2015-07-21 3 views
7

Supponiamo di avere una semplice coppia client/server Echo in Java. A quanto ho capito, una volta che un lato del socket si rompe, l'intera connessione è sparita.Nella programmazione della java network, c'è un modo per mantenere aperto il lato server anche quando il lato client si spegne?

Ma cosa succede se voglio un server che può sempre rimanere in vita, anche se il lato client muore. Voglio essere in grado di riprendere una connessione interrotta.

Echo Server:

import java.net.Socket; 
import java.net.ServerSocket; 

public class EchoServer { 

    public static void main(String[] args) throws Exception { 

     // create socket 
     int port = 4444; 
     ServerSocket serverSocket = new ServerSocket(port); 
     System.err.println("Started server on port " + port); 

     // repeatedly wait for connections, and process 
     while (true) { 

      // a "blocking" call which waits until a connection is requested 
      Socket clientSocket = serverSocket.accept(); 
      System.err.println("Accepted connection from client"); 

      // open up IO streams 
      In in = new In (clientSocket); 
      Out out = new Out(clientSocket); 

      // waits for data and reads it in until connection dies 
      // readLine() blocks until the server receives a new line from client 
      String s; 
      while ((s = in.readLine()) != null) { 
       out.println(s); 
      } 

      // close IO streams, then socket 
      System.err.println("Closing connection with client"); 
      out.close(); 
      in.close(); 
      clientSocket.close(); 
     } 
    } 
} 

qualche consiglio apprezzate grazie

+3

Ulteriori informazioni su TCP e UDP. Con TCP non è possibile, dovrai gestire la riconnessione tu stesso. Con UDP è possibile, ma ha altre implicazioni –

+0

Perché vuoi farlo? Ricollegare una connessione interrotta è impossibile, ma potrebbe esserci un altro modo di fare quello che vuoi. – tbodt

+0

@tbodt - un altro modo - hmm, come cosa?> – Coffee

risposta

5

Quando si desidera creare un server e più clienti, quello che normalmente facciamo è quello di creare un thread responsabile per la comunicazione tra il server e la uno dei client, quindi ogni volta che un client si connette al server, il server inizierà una nuova discussione e gli darà il socket corrispondente.

Il codice dovrebbe essere qualcosa di simile:

import java.net.ServerSocket; 
import java.net.Socket; 
import java.util.ArrayList; 

public class EchoServer { 

    public static void main(String[] args) throws Exception { 

     ArrayList<ClientHandler> clientHandlersList = new ArrayList<ClientHandler>(); 

     // create socket 
     int port = 4444; 
     boolean isOver = false; 
     ServerSocket serverSocket = new ServerSocket(port); 
     System.err.println("Started server on port " + port); 

     // repeatedly wait for connections, and process 
     while (!isOver) { 

      // a "blocking" call which waits until a connection is requested 
      Socket clientSocket = serverSocket.accept(); 
      System.err.println("Accepted connection from client"); 

      ClientHandler handler = new ClientHandler(clientSocket); 
      handler.start(); 
      clientHandlersList.add(handler); 
     } 

     serverSocket.close(); 
    } 
} 

e la classe ClientHandler:

import java.io.IOException; 
import java.io.ObjectInputStream; 
import java.io.ObjectOutputStream; 
import java.net.Socket; 


public class ClientHandler extends Thread { 

    private Socket socket; 
    private ObjectInputStream in; 
    private ObjectOutputStream out; 
    private boolean disconnect = false; 


    public ClientHandler(Socket socket) { 
     this.socket = socket; 
    } 

    @Override 
    public void run() { 
     try { 
      in = new ObjectInputStream(socket.getInputStream()); 
      out = new ObjectOutputStream(socket.getOutputStream()); 
      while(!disconnect){ 
       Object message = readMessage(); 

       //DO something 
      } 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 
    } 

    public Object readMessage(){ 
     try { 
      Object obj = in.readObject(); 
      return obj; 

     } catch (ClassNotFoundException e) { 
      e.printStackTrace(); 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 

     return null; 
    } 

    public void writeMessage(Object obj){ 
     try { 
      out.writeObject(obj); 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 
    } 

    public void disconnect(){ 
     try { 
      disconnect = false; 
      out.close(); 
      in.close(); 
      socket.close(); 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 
    } 
} 
+2

Questa è una risposta degna e utile (+1). È un buon esempio di come gestire più richieste in arrivo sul socket. Non affronta * direttamente * la questione di "come riprendiamo da dove avevamo interrotto", ma fornisce un buon quadro per farlo. NB. invece di sottoclassare 'Thread', implementare' Runnable' e fare '(new Thread (myRunnable)). start()'. NB (2). il progetto 'thread per request' è generalmente pensato per non "scalare" bene (cioè le richieste simultanee di> 100), e ci sono altri design. (vedere NodeJS per le opinioni, vert.x per Java). NB3. HTTP supera i firewall, ha "cookie". –

+0

@DavidBullock - Ero curioso di vert.x, per Java. È qualcosa che può essere fatto in una singola macchina? Cercherò su questo ora – Coffee

11

Cosa ti manca è il concetto di una 'session'. Pensa alla posizione di un server, quando alcuni bit arrivano "sul filo". Cosa fare con questi? Con TCP/IP, c'è qualche informazione già presente sul filo, vale a dire:

  • src addr
  • porta src
  • dest addr
  • porta di destinazione
  • e il messaggio payload stessa
  • e alcune altre cose (come contatori di sequenza, per garantire che i "pacchetti" non vengano confusi durante la trasmissione)

Il sistema operativo del server utilizza le informazioni src/dest addr/port per decidere se si tratta di "una conversazione che è già in corso" oppure no. Principalmente considera lo dest port (poiché il messaggio lo ha già fatto alla macchina stessa, attraverso internet e il firewall) per decidere se è in ascolto per il tuo programma Java sulla "dest port". Tuttavia, utilizza l'intero src-addr/src-port/dest-addr/dest-port per provare a recapitare il payload al tuo programma nell'ordine in cui il mittente li ha inviati (che potrebbe non essere l'ordine in cui sono arrivati , a causa dell'inter-net interposta). Si noti che un singolo "messaggio" potrebbe effettivamente essere suddiviso in più pacchetti. Lo stack TCP/IP del tuo sistema operativo sta facendo un bel po 'di lavoro per te.

Tuttavia, si noti che per eseguire questa funzione a proprio nome, il sistema operativo deve dedicare alcune risorse al "monitoraggio dello stato" della sessione TCP/IP. Per lo meno, per ogni set di porta/addr src/dest, ci deve essere un contatore dell'ultimo pacchetto "ben ricevuto", uno spazio buffer per contenere i pacchetti fino a quando il tuo programma è pronto a consumarli, e così via.

Ora la domanda rivolta all'implementatore di stack TCP/IP è "per quanto tempo dovrei rimanere" in questo stato per "? 10 secondi sono sufficienti? 20 minuti?Ad un certo punto, dopo non aver ascoltato dal client per un po ', la sessione deve "timeout". Se ci sono più pacchetti da inviare in una sequenza e il client inizia a inviarli di nuovo, il server deve essere in grado di dire "mi dispiace, mi stai mandando il pacchetto 234 di qualche messaggio precedente, ma perché non ho sentito da per un po ', ho buttato fuori i pacchetti 1-233. Potremmo ricominciare? ".

Quindi, in questo senso, non c'è modo di impedire al client di "disconnettersi". Certo, puoi continuare ad ascoltare sul socket, nel caso in cui il client si riprenda e mandi altri dati. Ma tu e il tuo cliente avrete bisogno di un modo per "riprendere da dove avevamo lasciato".

In HTTP, ciò viene ottenuto utilizzando un "cookie di sessione", una lunga stringa univoca che il server ha fornito al client e il client re-invia con ogni nuova richiesta (che si verifichi nella stessa sessione a livello TCP o non). Questa è una 'sessione a livello di applicazione' che ha una durata superiore a quella della sessione TCP.

Dato che stai scrivendo un'applicazione e sembra che tu abbia un certo grado di controllo su ciò che il client e il server possono dirsi l'un l'altro (il "protocollo"), hai più opzioni su come concordare su cosa è una sessione, come comportarsi con le cose se il cliente "va via" (timeout della sessione), e su come entrambe le parti si riprenderanno e "riprenderanno da dove abbiamo lasciato" (ristabilire la sessione). Non dimenticarti dell'autenticazione, vero?

Come altri hanno già detto, dipende molto da quello che stai cercando di ottenere.

4

Come qualcuno ha sottolineato, non è possibile farlo con TCP.

Ecco un semplice esempio di un server echo DatagramSocket (UDP).

package test; 

import java.io.*; 
import java.net.*; 

public class UDPEchoServer { 

    public static void main(String[] args) throws IOException 
    { 
     new ServerThread().start(); 
    } 


    static class ServerThread extends Thread 
    { 

     protected DatagramSocket socket = null; 

     public ServerThread() throws IOException 
     { 
      this("ServerThread"); 
     } 

     public ServerThread(String name) throws IOException 
     { 
      super(name); 
      socket = new DatagramSocket(4444); 

     } 

     public void run() 
     { 
      try { 
       while (true) { 
        byte[] buf = new byte[256]; 

        // receive data 
        DatagramPacket packet = new DatagramPacket(buf, buf.length); 
        socket.receive(packet); 

        // send back the response to the client at "address" and "port" 
        InetAddress address = packet.getAddress(); 
        int port = packet.getPort(); 
        packet = new DatagramPacket(buf, buf.length, address, port); 
        socket.send(packet); 
       } 
      } 
      catch (IOException e) { 
       e.printStackTrace(); 
      } 
      socket.close(); 
     } 
    } 
} 

Aggiornamento: Aggiunto informazioni di prova

È possibile verificare con nc

[[email protected]]$ nc -u 127.0.0.1 4444 
testing echo server 
testing echo server 
killing it now 
killing it now 
^C 
[[email protected]]$ nc -u 127.0.0.1 4444 
now we are back 
now we are back 
+1

Ok questo sembra abbastanza pulito. btw, sto usando Windows. Penso che il codice sia fatto su Linux/Mac? '[gustaf @ gustf-air] $ nc -u 127.0.0.1 4444' Quando ho provato a eseguire' java UDPEchoServer 4444' ha dato un errore 'Exception in thread" main "java.net.BindException: Indirizzo già in uso: Can not legare a java.net.DualStackPlainDatagramSocketImpl.socketBind (metodo nativo) ', grazie! – Coffee

+2

Salve, l'opzione 'BindException' potrebbe essere perché hai già il tuo programma" vecchio "in esecuzione. Le porte non possono essere condivise. Basta cambiare '4444' in qualcos'altro che sai non è in uso. Sì, hai ragione Sto usando un mac. C'è probabilmente un comando simile per Windows, ma non so quale. Ma sarebbe una semplice cosa scrivere un cliente da solo. Ecco un buon tutorial di Oracle. https://docs.oracle.com/javase/tutorial/networking/datagrams/clientServer.html – gustf

0

Con utilizzando l'API presa come mostrato nel codice, questo non dovrebbe essere un problema. Per una connessione singola se uno dei due lati viene chiuso (o per esempio le matrici) l'associazione completa è "inattiva" e non può più essere utilizzata. Questo è ciò che sta accadendo con clientSocket dal tuo codice.

Tuttavia, serverSocket è ancora aperto e accetta nuove connessioni. Rispetto a questo, il tuo server rimane sempre attivo.

Quindi, il codice mostrato è un semplice esempio di vaniglia di utilizzo di socket api, per la creazione di un server che attende connessioni incomming e invia le chiamate individuali (incomming) per essere elaborate.

L'unico "svantaggio" del codice è che eseguendo l'elaborazione nello stesso thread di accept(), il server non sarà in grado di rispondere alle nuove richieste fintanto che sta elaborando quella precedente. Per evitare questa elaborazione potrebbe essere delegato a un nuovo thread (come già sottolineato).

È possibile trovare ulteriori spiegazioni con la documentazione su accept(). ad es. è possibile leggere attraverso Socket API explanation

Si prega di riconoscere, questa non è direttamente una domanda Java.Java semplicemente envlopes e fornisce ai programmi Java le funzionalità del sistema operativo sottostante rispetto alla comunicazione socket. Questo è principalmente ciò che è documentato come parte dello standard Posix, in realtà questo è (rispetto ai socket) solo ripetendo (e aggregando) ciò che è stato specificato con vari RFC (vedi https://www.ietf.org/rfc.html per i dettagli di queste specifiche).

Con le prese che mantengono aperta una connessione (funzionante al massimo grado) dopo che un lato lo ha interrotto, non è possibile se il socket è impostato per utilizzare il protocollo TCP. C'è - solo per aggiungere per completezza - un meccanismo (arresto) per dichiarare che il lato di invio locale è inattivo mentre accetta ancora altri dati per l'input. (Ma suppongo che non stiamo parlando di quella caratteristica molto speciale.) Mantenere una connessione aperta quando il lato connesso si rifiuta di parlare causerebbe problemi logici e sarebbe anche un incubo per la gestione delle risorse. (Per quanto tempo il server manterrà le risorse?)

Il modello standard per l'utilizzo di TCP con un daemon (un processo che fornisce continuamente un servizio a una determinata porta), è mediante accept() come indicato sopra :

  • Il daemon sta eseguendo il ciclo su accept().
  • un cliente chiama connect() al server
  • accept() sta tornando una nuova connessione (dedicato) per comunicare con il cliente
  • client e server scambiare dati
  • un lato (client o server) sta terminando la connessione.

La strategia il server utilizza per l'esecuzione di diverse richieste (elaborazione sincrona uno dopo l'altro, richiesta di code, un thread per connnection è una decisione di progettazione separata completley. Dipende dal grado di parallelismo, le risorse disponibili, ecc

il server avrà due diversi "tipi" di prese in uso:.

  1. la presa si chiama accept() su è quella che il server è "in ascolto" per le connessioni incomming
    Questo soc ket è quello che è continuamente aperto.
  2. un socket restituito da accept() è per una connessione specifica.
    Questo è usato per comunicare con un cliente specifico e sta per "morire" alla fine dell'interazione.
Problemi correlati