Programmazione socket in Java: un tutorial

Questo tutorial è un'introduzione alla programmazione socket in Java, a partire da un semplice esempio client-server che dimostra le funzionalità di base dell'I / O Java. Verrai introdotto sia al java.io pacchetto originale  che a NIO, le java.nioAPI I / O ( ) non bloccanti introdotte in Java 1.4. Infine, vedrai un esempio che dimostra la rete Java implementata da Java 7 in avanti, in NIO.2.

La programmazione socket si riduce a due sistemi che comunicano tra loro. In genere, la comunicazione di rete è disponibile in due versioni: TCP (Transport Control Protocol) e UDP (User Datagram Protocol). TCP e UDP vengono utilizzati per scopi diversi ed entrambi hanno vincoli univoci:

  • TCP è un protocollo relativamente semplice e affidabile che consente a un client di stabilire una connessione a un server e ai due sistemi di comunicare. In TCP, ogni entità sa che i suoi payload di comunicazione sono stati ricevuti.
  • UDP è un protocollo senza connessione ed è utile per gli scenari in cui non è necessario che ogni pacchetto arrivi a destinazione, come lo streaming multimediale.

Per apprezzare la differenza tra TCP e UDP, considera cosa accadrebbe se tu fossi in streaming video dal tuo sito web preferito e si perdessero frame. Preferiresti che il client rallenti il ​​tuo film per ricevere i frame mancanti o preferiresti che il video continuasse a essere riprodotto? I protocolli di streaming video in genere sfruttano UDP. Poiché TCP garantisce la consegna, è il protocollo di scelta per HTTP, FTP, SMTP, POP3 e così via.

In questo tutorial, ti presento alla programmazione socket in Java. Presento una serie di esempi client-server che dimostrano le funzionalità del framework I / O Java originale, quindi passo gradualmente all'utilizzo delle funzionalità introdotte in NIO.2.

Socket Java della vecchia scuola

Nelle implementazioni precedenti a NIO, il codice socket del client Java TCP è gestito dalla java.net.Socketclasse. Il codice seguente apre una connessione a un server:

 Socket socket = nuovo Socket (server, porta); 

Una volta che la nostra socketistanza è connessa al server, possiamo iniziare a ottenere flussi di input e output al server. I flussi di input vengono utilizzati per leggere i dati dal server mentre i flussi di output vengono utilizzati per scrivere i dati sul server. Possiamo eseguire i seguenti metodi per ottenere flussi di input e output:

InputStream in = socket.getInputStream (); OutputStream out = socket.getOutputStream ();

Poiché si tratta di flussi ordinari, gli stessi che utilizzeremmo per leggere e scrivere su un file, possiamo convertirli nel formato che meglio si adatta al nostro caso d'uso. Ad esempio, potremmo racchiudere il OutputStreamcon un in PrintStreammodo da poter scrivere facilmente del testo con metodi come println(). Per un altro esempio, potremmo racchiudere il InputStreamcon a BufferedReader, tramite un InputStreamReader, per leggere facilmente il testo con metodi come readLine().

download Scarica il codice sorgente Codice sorgente per "Programmazione socket in Java: un tutorial". Creato da Steven Haines per JavaWorld.

Esempio di client socket Java

Analizziamo un breve esempio che esegue un HTTP GET su un server HTTP. HTTP è più sofisticato di quanto consentito dal nostro esempio, ma possiamo scrivere codice client per gestire il caso più semplice: richiedere una risorsa al server e il server restituisce la risposta e chiude il flusso. Questo caso richiede i seguenti passaggi:

  1. Crea un socket per il server web in ascolto sulla porta 80.
  2. Ottieni un PrintStreamal server e invia la richiesta GET PATH HTTP/1.0, dove si PATHtrova la risorsa richiesta sul server. Ad esempio, se volessimo aprire la radice di un sito Web, il percorso sarebbe /.
  3. Ottieni un InputStreamal server, avvolgilo con a BufferedReadere leggi la risposta riga per riga.

Il listato 1 mostra il codice sorgente per questo esempio.

Listato 1. SimpleSocketClientExample.java

pacchetto com.geekcap.javaworld.simplesocketclient; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.Socket; public class SimpleSocketClientExample {public static void main (String [] args) {if (args.length <2) {System.out.println ("Usage: SimpleSocketClientExample"); System.exit (0); } String server = args [0]; Percorso stringa = args [1]; System.out.println ("Caricamento dei contenuti dell'URL:" + server); prova {// Connetti al server Socket socket = new Socket (server, 80); // Crea flussi di input e output da cui leggere e scrivere nel server PrintStream out = new PrintStream (socket.getOutputStream ()); BufferedReader in = new BufferedReader (new InputStreamReader (socket.getInputStream ())); // Segui il protocollo HTTP di GET HTTP / 1.0 seguito da una riga vuota out.println ("GET" + percorso + "HTTP / 1.0"); out.println (); // Legge i dati dal server fino a quando non finiamo di leggere il documento String line = in.readLine (); while (riga! = null) {System.out.println (riga); riga = in.readLine (); } // Chiude i nostri flussi in.close (); out.close (); socket.close (); } catch (eccezione e) {e.printStackTrace (); }}}

Il Listato 1 accetta due argomenti della riga di comando: il server a cui connettersi (assumendo che ci stiamo connettendo al server sulla porta 80) e la risorsa da recuperare. Crea un Socketche punta al server e specifica esplicitamente la porta 80. Quindi esegue il comando:

OTTIENI PATH HTTP / 1.0 

Per esempio:

GET / HTTP / 1.0 

Cosa è appena successo?

Quando si recupera una pagina Web da un server Web, ad esempio www.google.com, il client HTTP utilizza i server DNS per trovare l'indirizzo del server: inizia chiedendo al server di dominio di primo livello il comdominio in cui si trova il server dei nomi di dominio autorevole per www.google.com. Quindi chiede a quel server del nome di dominio l'indirizzo IP (o gli indirizzi) per www.google.com. Successivamente, apre un socket su quel server sulla porta 80 (oppure, se vuoi definire una porta diversa, puoi farlo aggiungendo due punti seguiti dal numero di porta, ad esempio:. :8080) Infine, il client HTTP viene eseguito il metodo HTTP specificato, ad esempio GET, POST, PUT, DELETE, HEAD, o OPTI/ONS. Ogni metodo ha la propria sintassi. Come mostrato nei frammenti di codice sopra, il GETmetodo richiede un percorso seguito daHTTP/version numbere una riga vuota. Se avessimo voluto aggiungere intestazioni HTTP avremmo potuto farlo prima di inserire la nuova riga.

Nel Listato 1, abbiamo recuperato OutputStreame inserito in a in PrintStreammodo da poter eseguire più facilmente i nostri comandi basati su testo. Il nostro codice ha ottenuto un InputStream, lo ha racchiuso in un InputStreamReader, che lo ha convertito in a Reader, quindi lo ha racchiuso in a BufferedReader. Abbiamo usato il PrintStreamper eseguire il nostro GETmetodo e poi abbiamo usato il BufferedReaderper leggere la risposta riga per riga fino a quando non abbiamo ricevuto una nullrisposta, indicando che il socket era stato chiuso.

Ora esegui questa classe e passagli i seguenti argomenti:

java com.geekcap.javaworld.simplesocketclient.SimpleSocketClientExample www.javaworld.com / 

Dovresti vedere un output simile a quello che segue:

Caricamento dei contenuti dell'URL: www.javaworld.com HTTP / 1.1 200 OK Data: Sun, 21 Set 2014 22:20:13 GMT Server: Apache X-Gas_TTL: 10 Cache-Control: max-age = 10 X-GasHost: gas2 .usw X-Cooking-With: Gasoline-Local X-Gasoline-Age: 8 Content-Length: 168 Ultima modifica: Tue, 24 Jan 2012 00:09:09 GMT Etag: "60001b-a8-4b73af4bf3340" Content-Type : text / html Vary: Accept-Encoding Connection: chiudi la pagina di test della benzina

Successo

Questo output mostra una pagina di test sul sito Web di JavaWorld. Ha risposto che parla la versione HTTP 1.1 e la risposta è 200 OK.

Esempio di server socket Java

Abbiamo coperto il lato client e fortunatamente l'aspetto della comunicazione dal lato server è altrettanto semplice. Da una prospettiva semplicistica, il processo è il seguente:

  1. Crea un ServerSocket, specificando una porta su cui ascoltare.
  2. Richiamare il ServerSocket's accept()metodo per ascolto sulla porta configurata per una connessione client.
  3. Quando un client si connette al server, il accept()metodo restituisce un Sockettramite il quale il server può comunicare con il client. Questa è la stessa Socketclasse che abbiamo usato per il nostro client, quindi il processo è lo stesso: ottenere una InputStreamlettura dal client e una OutputStreamscrittura al client.
  4. Se il tuo server deve essere scalabile, dovrai passare il processo Socketa un altro thread in modo che il tuo server possa continuare ad ascoltare ulteriori connessioni.
  5. Chiamare il ServerSocket's accept()nuovo metodo per ascoltare un altro collegamento.

Come vedrai presto, la gestione di questo scenario da parte di NIO sarebbe leggermente diversa. Per ora, però, possiamo creare direttamente un ServerSocketpassandogli una porta su cui ascoltare (maggiori informazioni su ServerSocketFactorys nella prossima sezione):

 ServerSocket serverSocket = nuovo ServerSocket (porta); 

E ora possiamo accettare le connessioni in entrata tramite il accept()metodo:

Socket socket = serverSocket.accept (); // Gestisci la connessione ...

Programmazione multithread con socket Java

Il Listato 2, di seguito, mette insieme tutto il codice del server fino ad ora in un esempio leggermente più robusto che utilizza i thread per gestire più richieste. Il server mostrato è un server echo , il che significa che restituisce qualsiasi messaggio ricevuto.

Sebbene l'esempio nel Listato 2 non sia complicato, anticipa parte di ciò che verrà fuori nella prossima sezione su NIO. Presta particolare attenzione alla quantità di codice di threading che dobbiamo scrivere per costruire un server in grado di gestire più richieste simultanee.

Listato 2. SimpleSocketServer.java

pacchetto com.geekcap.javaworld.simplesocketclient; import java.io.BufferedReader; import java.io.I / OException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; la classe pubblica SimpleSocketServer estende Thread {private ServerSocket serverSocket; porta int privata; booleano privato in esecuzione = false; public SimpleSocketServer (int port) {this.port = port; } public void startServer () {try {serverSocket = new ServerSocket (port); this.start (); } catch (I / OException e) {e.printStackTrace (); }} public void stopServer () {running = false; this.interrupt (); } @Override public void run () {running = true; while (in esecuzione) {try {System.out.println ("Listening for a connection"); // Chiama accept () per ricevere la connessione successiva Socket socket = serverSocket.accept ();// Passa il socket al thread RequestHandler per l'elaborazione di RequestHandler requestHandler = new RequestHandler (socket); requestHandler.start (); } catch (I / OException e) {e.printStackTrace (); }}} public static void main (String [] args) {if (args.length == 0) {System.out.println ("Usage: SimpleSocketServer"); System.exit (0); } int port = Integer.parseInt (args [0]); System.out.println ("Avvia server sulla porta:" + porta); SimpleSocketServer server = nuovo SimpleSocketServer (porta); server.startServer (); // Arresto automatico in 1 minuto try {Thread.sleep (60000); } catch (eccezione e) {e.printStackTrace (); } server.stopServer (); }} la classe RequestHandler estende Thread {private Socket socket; RequestHandler (Socket socket) {this.socket = socket; } @Override public void run () {try {System.out.println ("Ricevuta una connessione"); // Ottieni flussi di input e output BufferedReader in = new BufferedReader (new InputStreamReader (socket.getInputStream ())); PrintWriter out = nuovo PrintWriter (socket.getOutputStream ()); // Scrive la nostra intestazione al client out.println ("Echo Server 1.0"); out.flush (); // Restituisce le righe al client finché il client non chiude la connessione o non si riceve una riga vuota. String line = in.readLine (); while (riga! = null && line.length ()> 0) {out.println ("Echo:" + riga); out.flush (); riga = in.readLine (); } // Chiude la nostra connessione in.close (); out.close (); socket.close (); System.out.println ("Connessione chiusa"); } catch (eccezione e) {e.printStackTrace (); }}}// Ottieni flussi di input e output BufferedReader in = new BufferedReader (new InputStreamReader (socket.getInputStream ())); PrintWriter out = nuovo PrintWriter (socket.getOutputStream ()); // Scrive la nostra intestazione al client out.println ("Echo Server 1.0"); out.flush (); // Restituisce le righe al client finché il client non chiude la connessione o non si riceve una riga vuota. String line = in.readLine (); while (riga! = null && line.length ()> 0) {out.println ("Echo:" + riga); out.flush (); riga = in.readLine (); } // Chiude la nostra connessione in.close (); out.close (); socket.close (); System.out.println ("Connessione chiusa"); } catch (eccezione e) {e.printStackTrace (); }}}// Ottieni flussi di input e output BufferedReader in = new BufferedReader (new InputStreamReader (socket.getInputStream ())); PrintWriter out = nuovo PrintWriter (socket.getOutputStream ()); // Scrive la nostra intestazione al client out.println ("Echo Server 1.0"); out.flush (); // Restituisce le righe al client finché il client non chiude la connessione o non si riceve una riga vuota. String line = in.readLine (); while (riga! = null && line.length ()> 0) {out.println ("Echo:" + riga); out.flush (); riga = in.readLine (); } // Chiude la nostra connessione in.close (); out.close (); socket.close (); System.out.println ("Connessione chiusa"); } catch (eccezione e) {e.printStackTrace (); }}}// Scrive la nostra intestazione al client out.println ("Echo Server 1.0"); out.flush (); // Restituisce le righe al client finché il client non chiude la connessione o non si riceve una riga vuota. String line = in.readLine (); while (riga! = null && line.length ()> 0) {out.println ("Echo:" + riga); out.flush (); riga = in.readLine (); } // Chiude la nostra connessione in.close (); out.close (); socket.close (); System.out.println ("Connessione chiusa"); } catch (eccezione e) {e.printStackTrace (); }}}// Scrive la nostra intestazione al client out.println ("Echo Server 1.0"); out.flush (); // Restituisce le righe al client finché il client non chiude la connessione o non si riceve una riga vuota. String line = in.readLine (); while (riga! = null && line.length ()> 0) {out.println ("Echo:" + riga); out.flush (); riga = in.readLine (); } // Chiude la nostra connessione in.close (); out.close (); socket.close (); System.out.println ("Connessione chiusa"); } catch (eccezione e) {e.printStackTrace (); }}}linea di lettura(); } // Chiude la nostra connessione in.close (); out.close (); socket.close (); System.out.println ("Connessione chiusa"); } catch (eccezione e) {e.printStackTrace (); }}}linea di lettura(); } // Chiude la nostra connessione in.close (); out.close (); socket.close (); System.out.println ("Connessione chiusa"); } catch (eccezione e) {e.printStackTrace (); }}}