Scrivere codice che viene eseguito su un determinato dispositivo è molto soddisfacente. Ma scrivere codice che viene eseguito su più dispositivi che comunicano tra loro è semplicemente affermativo. Questo articolo ti insegnerà come connettere e scambiare messaggi sulla rete utilizzando il protocollo di controllo della trasmissione (TCP).
In questo articolo, configurerai un'applicazione che collegherà il tuo computer a se stesso e, in sostanza, lo farà impazzire - parlerà da solo. Imparerai anche la differenza tra i due flussi più utilizzati per il networking in Java e come funzionano.
Flussi di dati e oggetti
Prima di immergersi nel codice, è necessario distinguere la differenza tra i due flussi utilizzati nell'articolo.
Flussi di dati
I flussi di dati elaborano stringhe e tipi di dati primitivi. I dati inviati tramite flussi di dati devono essere serializzati e deserializzati manualmente, il che rende più difficile il trasferimento di dati complessi. Tuttavia, i flussi di dati possono comunicare con server e client scritti in linguaggi diversi da Java. I flussi grezzi sono simili ai flussi di dati in questo aspetto, ma i flussi di dati assicurano che i dati siano formattati in un modo indipendente dalla piattaforma, il che è vantaggioso perché entrambe le parti saranno in grado di leggere i dati inviati.
Flussi di oggetti
I flussi di oggetti elaborano tipi di dati primitivi e oggetti che implementano
serializzabile
interfaccia. I dati inviati su flussi di oggetti vengono serializzati e deserializzati automaticamente, il che semplifica il trasferimento di dati complessi. Tuttavia, i flussi di oggetti possono comunicare solo con server e client scritti in Java. Anche,
ObjectOutputStream
al momento dell'inizializzazione, invia un'intestazione al
InputStream
dell'altra parte che, al momento dell'inizializzazione, blocca l'esecuzione fino alla ricezione dell'intestazione.
Passi
Passaggio 1. Crea una classe
Crea una classe e chiamala come preferisci. In questo articolo, sarà chiamato
NetworkAppEsempio
public class NetworkAppExample { }
Passaggio 2. Creare un metodo principale
Crea un metodo principale e dichiara che potrebbe generare eccezioni di
Eccezione
tipo e qualsiasi sottoclasse di esso - tutte le eccezioni. Questa è considerata una cattiva pratica, ma è accettabile per gli esempi barebone.
public class NetworkAppExample { public static void main(String args) genera un'eccezione { } }
Passaggio 3. Dichiarare l'indirizzo del server
Questo esempio utilizzerà l'indirizzo host locale e un numero di porta arbitrario. Il numero di porta deve essere compreso in un intervallo compreso tra 0 e 65535 (incluso). Tuttavia, i numeri di porta da evitare vanno da 0 a 1023 (inclusi) perché sono porte di sistema riservate.
public class NetworkAppExample { public static void main(String args) genera un'eccezione { String host = "localhost"; porta int = 10430; } }
Passaggio 4. Creare un server
Il server è vincolato all'indirizzo e alla porta e ascolta le connessioni in entrata. A Giava,
ServerSocket
rappresenta l'endpoint lato server e la sua funzione accetta nuove connessioni.
ServerSocket
non ha flussi per la lettura e l'invio dei dati perché non rappresenta la connessione tra un server e un client.
import java.net. InetAddress; import java.net. ServerSocket; public class NetworkAppExample { public static void main(String args) genera un'eccezione { String host = "localhost"; porta int = 10430; ServerSocket server = nuovo ServerSocket(port, 50, InetAddress.getByName(host)); } }
Passaggio 5. Inizio del server di registrazione
Ai fini della registrazione, stampare sulla console che il server è stato avviato.
import java.net. InetAddress; import java.net. ServerSocket; public class NetworkAppExample { public static void main(String args) genera un'eccezione { String host = "localhost"; porta int = 10430; ServerSocket server = nuovo ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Server avviato."); } }
Passaggio 6. Creare un cliente
Il client è legato all'indirizzo e alla porta di un server e ascolta i pacchetti (messaggi) dopo che è stata stabilita la connessione. A Giava,
Presa
rappresenta un endpoint lato client connesso al server o una connessione (dal server) al client e viene utilizzato per comunicare con la parte dall'altra parte.
import java.net. InetAddress; import java.net. ServerSocket; import java.net. Socket; public class NetworkAppExample { public static void main(String args) genera un'eccezione { String host = "localhost"; porta int = 10430; ServerSocket server = nuovo ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Server avviato."); Socket client = nuovo Socket (host, porta); } }
Passaggio 7. Registrare il tentativo di connessione
Ai fini della registrazione, stampare sulla console che è stata tentata la connessione.
import java.net. InetAddress; import java.net. ServerSocket; import java.net. Socket; public class NetworkAppExample { public static void main(String args) genera un'eccezione { String host = "localhost"; porta int = 10430; ServerSocket server = nuovo ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Server avviato."); Socket client = nuovo Socket (host, porta); System.out.println("Connessione al server…"); } }
Passaggio 8. Stabilire la connessione
I client non si connetteranno mai a meno che il server non ascolti e accetti, in altre parole, stabilisca connessioni. In Java, le connessioni vengono stabilite utilizzando
accettare()
metodo di
ServerSocket
classe. Il metodo bloccherà l'esecuzione finché un client non si connette.
import java.net. InetAddress; import java.net. ServerSocket; import java.net. Socket; public class NetworkAppExample { public static void main(String args) genera un'eccezione { String host = "localhost"; porta int = 10430; ServerSocket server = nuovo ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Server avviato."); Socket client = nuovo Socket (host, porta); System.out.println("Connessione al server…"); Connessione socket = server.accept(); } }
Passaggio 9. Registrare la connessione stabilita
Ai fini della registrazione, stampare sulla console che è stata stabilita la connessione tra server e client.
import java.net. InetAddress; import java.net. ServerSocket; import java.net. Socket; public class NetworkAppExample { public static void main(String args) genera un'eccezione { String host = "localhost"; porta int = 10430; ServerSocket server = nuovo ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Server avviato."); Socket client = nuovo Socket (host, porta); System.out.println("Connessione al server…"); Connessione socket = server.accept(); System.out.println("Connessione stabilita."); } }
Passaggio 10. Preparare i flussi di comunicazione
La comunicazione avviene su flussi e, in questa applicazione, i flussi grezzi di (connessione da) server (a client) e client devono essere concatenati a flussi di dati o oggetti. Ricorda, entrambe le parti devono utilizzare lo stesso tipo di flusso.
-
Flussi di dati
import java.io. DataInputStream; import java.io. DataOutputStream; import java.net. InetAddress; import java.net. ServerSocket; import java.net. Socket; public class NetworkAppExample { public static void main(String args) genera un'eccezione { String host = "localhost"; porta int = 10430; ServerSocket server = nuovo ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Server avviato."); Socket client = nuovo Socket (host, porta); System.out.println("Connessione al server…"); Connessione socket = server.accept(); System.out.println("Connessione stabilita."); DataOutputStream clientOut = new DataOutputStream(client.getOutputStream()); DataInputStream clientIn = new DataInputStream(client.getInputStream()); DataOutputStream serverOut = new DataOutputStream(connection.getOutputStream()); DataInputStream serverIn = new DataInputStream(connection.getInputStream()); } }
-
Flussi di oggetti
Quando vengono utilizzati più flussi di oggetti, i flussi di input devono essere inizializzati nello stesso ordine dei flussi di output perché
ObjectOutputStream
invia un'intestazione all'altra parte e
ObjectInputStream
blocca l'esecuzione finché non legge l'intestazione.
import java.io. ObjectInputStream; import java.io. ObjectOutputStream; import java.net. InetAddress; import java.net. ServerSocket; import java.net. Socket; public class NetworkAppExample { public static void main(String args) genera un'eccezione { String host = "localhost"; porta int = 10430; ServerSocket server = nuovo ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Server avviato."); Socket client = nuovo Socket (host, porta); System.out.println("Connessione al server…"); Connessione socket = server.accept(); System.out.println("Connessione stabilita."); ObjectOutputStream clientOut = new ObjectOutputStream(client.getOutputStream()); ObjectOutputStream serverOut = new ObjectOutputStream(connection.getOutputStream()); ObjectInputStream clientIn = new ObjectInputStream(client.getInputStream()); ObjectInputStream serverIn = new ObjectInputStream(connection.getInputStream()); } }
L'ordine come specificato nel codice sopra potrebbe essere più facile da ricordare: prima inizializza i flussi di output, quindi i flussi di input nello stesso ordine. Tuttavia, un altro ordine per l'inizializzazione dei flussi di oggetti è il seguente:
ObjectOutputStream clientOut = new ObjectOutputStream(client.getOutputStream()); ObjectInputStream serverIn = new ObjectInputStream(connection.getInputStream()); ObjectOutputStream serverOut = new ObjectOutputStream(connection.getOutputStream()); ObjectInputStream clientIn = new ObjectInputStream(client.getInputStream());
Passaggio 11. Registrare che la comunicazione è pronta
Ai fini della registrazione, stampare sulla console che la comunicazione è pronta.
// codice omesso import java.net. InetAddress; import java.net. ServerSocket; import java.net. Socket; public class NetworkAppExample { public static void main(String args) genera un'eccezione { String host = "localhost"; porta int = 10430; ServerSocket server = nuovo ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Server avviato."); Socket client = nuovo Socket (host, porta); System.out.println("Connessione al server…"); Connessione socket = server.accept(); System.out.println("Connessione stabilita."); // codice omesso System.out.println("La comunicazione è pronta."); } }
Passaggio 12. Crea un messaggio
In questa applicazione,
Ciao mondo
il testo verrà inviato al server come
byte
o
Corda
. Dichiarare una variabile del tipo che dipende dal flusso utilizzato. Utilizzo
byte
per flussi di dati e
Corda
per flussi di oggetti.
-
Flussi di dati
Utilizzando i flussi di dati, la serializzazione viene eseguita convertendo gli oggetti in tipi di dati primitivi o a
Corda
. In questo caso,
Corda
viene convertito in
byte
invece di scrivere usando
writeBytes()
metodo per mostrare come sarebbe fatto con altri oggetti, come immagini o altri file.
import java.io. DataInputStream; import java.io. DataOutputStream; import java.net. InetAddress; import java.net. ServerSocket; import java.net. Socket; public class NetworkAppExample { public static void main(String args) genera un'eccezione { String host = "localhost"; porta int = 10430; ServerSocket server = nuovo ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Server avviato."); Socket client = nuovo Socket (host, porta); System.out.println("Connessione al server…"); Connessione socket = server.accept(); System.out.println("Connessione stabilita."); DataOutputStream clientOut = new DataOutputStream(client.getOutputStream()); DataInputStream clientIn = new DataInputStream(client.getInputStream()); DataOutputStream serverOut = new DataOutputStream(connection.getOutputStream()); DataInputStream serverIn = new DataInputStream(connection.getInputStream()); System.out.println("La comunicazione è pronta."); byte messageOut = "Hello World".getBytes(); } }
-
Flussi di oggetti
import java.io. ObjectInputStream; import java.io. ObjectOutputStream; import java.net. InetAddress; import java.net. ServerSocket; import java.net. Socket; public class NetworkAppExample { public static void main(String args) genera un'eccezione { String host = "localhost"; porta int = 10430; ServerSocket server = nuovo ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Server avviato."); Socket client = nuovo Socket (host, porta); System.out.println("Connessione al server…"); Connessione socket = server.accept(); System.out.println("Connessione stabilita."); ObjectOutputStream clientOut = new ObjectOutputStream(client.getOutputStream()); ObjectOutputStream serverOut = new ObjectOutputStream(connection.getOutputStream()); ObjectInputStream clientIn = new ObjectInputStream(client.getInputStream()); ObjectInputStream serverIn = new ObjectInputStream(connection.getInputStream()); System.out.println("La comunicazione è pronta."); String messageOut = "Hello World"; } }
Passaggio 13. Invia il messaggio
Scrivi i dati nel flusso di output e scarica il flusso per assicurarti che i dati siano stati scritti interamente.
-
Flussi di dati
La lunghezza di un messaggio deve essere inviata prima in modo che l'altra parte sappia quanti byte deve leggere. Dopo che la lunghezza è stata inviata come tipo intero primitivo, è possibile inviare i byte.
import java.io. DataInputStream; import java.io. DataOutputStream; import java.net. InetAddress; import java.net. ServerSocket; import java.net. Socket; public class NetworkAppExample { public static void main(String args) genera un'eccezione { String host = "localhost"; porta int = 10430; ServerSocket server = nuovo ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Server avviato."); Socket client = nuovo Socket (host, porta); System.out.println("Connessione al server…"); Connessione socket = server.accept(); System.out.println("Connessione stabilita."); DataOutputStream clientOut = new DataOutputStream(client.getOutputStream()); DataInputStream clientIn = new DataInputStream(client.getInputStream()); DataOutputStream serverOut = new DataOutputStream(connection.getOutputStream()); DataInputStream serverIn = new DataInputStream(connection.getInputStream()); System.out.println("La comunicazione è pronta."); byte messageOut = "Hello World".getBytes(); clientOut.writeInt(messageOut.length); clientOut.write(messageOut); clientOut.flush(); } }
-
Flussi di oggetti
import java.io. ObjectInputStream; import java.io. ObjectOutputStream; import java.net. InetAddress; import java.net. ServerSocket; import java.net. Socket; public class NetworkAppExample { public static void main(String args) genera un'eccezione { String host = "localhost"; porta int = 10430; ServerSocket server = nuovo ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Server avviato."); Socket client = nuovo Socket (host, porta); System.out.println("Connessione al server…"); Connessione socket = server.accept(); System.out.println("Connessione stabilita."); ObjectOutputStream clientOut = new ObjectOutputStream(client.getOutputStream()); ObjectOutputStream serverOut = new ObjectOutputStream(connection.getOutputStream()); ObjectInputStream clientIn = new ObjectInputStream(client.getInputStream()); ObjectInputStream serverIn = new ObjectInputStream(connection.getInputStream()); System.out.println("La comunicazione è pronta."); String messageOut = "Hello World"; clientOut.writeObject(messageOut); clientOut.flush(); } }
Passaggio 14. Registra il messaggio inviato
Ai fini della registrazione, stampare sulla console che il messaggio è stato inviato.
-
Flussi di dati
import java.io. DataInputStream; import java.io. DataOutputStream; import java.net. InetAddress; import java.net. ServerSocket; import java.net. Socket; public class NetworkAppExample { public static void main(String args) genera un'eccezione { String host = "localhost"; porta int = 10430; ServerSocket server = nuovo ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Server avviato."); Socket client = nuovo Socket (host, porta); System.out.println("Connessione al server…"); Connessione socket = server.accept(); System.out.println("Connessione stabilita."); DataOutputStream clientOut = new DataOutputStream(client.getOutputStream()); DataInputStream clientIn = new DataInputStream(client.getInputStream()); DataOutputStream serverOut = new DataOutputStream(connection.getOutputStream()); DataInputStream serverIn = new DataInputStream(connection.getInputStream()); System.out.println("La comunicazione è pronta."); byte messageOut = "Hello World".getBytes(); clientOut.writeInt(messageOut.length); clientOut.write(messageOut); clientOut.flush(); System.out.println("Messaggio inviato al server: " + new String(messageOut)); } }
-
Flussi di oggetti
import java.io. ObjectInputStream; import java.io. ObjectOutputStream; import java.net. InetAddress; import java.net. ServerSocket; import java.net. Socket; public class NetworkAppExample { public static void main(String args) genera un'eccezione { String host = "localhost"; porta int = 10430; ServerSocket server = nuovo ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Server avviato."); Socket client = nuovo Socket (host, porta); System.out.println("Connessione al server…"); Connessione socket = server.accept(); System.out.println("Connessione stabilita."); ObjectOutputStream clientOut = new ObjectOutputStream(client.getOutputStream()); ObjectOutputStream serverOut = new ObjectOutputStream(connection.getOutputStream()); ObjectInputStream clientIn = new ObjectInputStream(client.getInputStream()); ObjectInputStream serverIn = new ObjectInputStream(connection.getInputStream()); System.out.println("La comunicazione è pronta."); String messageOut = "Hello World"; clientOut.writeObject(messageOut); clientOut.flush(); System.out.println("Messaggio inviato al server: " + messageOut); } }
Passaggio 15. Leggi il messaggio
Leggere i dati dal flusso di input e convertirli. Poiché conosciamo esattamente il tipo di dati inviati, creeremo un
Corda
a partire dal
byte
o cast
Oggetto
a
Corda
senza controllo, a seconda del flusso utilizzato.
-
Flussi di dati
Poiché la lunghezza è stata inviata prima e i byte dopo, la lettura deve essere eseguita nello stesso ordine. Nel caso in cui la lunghezza sia zero, non c'è nulla da leggere. L'oggetto viene deserializzato quando i byte vengono riconvertiti in un'istanza, in questo caso, di
Corda
import java.io. DataInputStream; import java.io. DataOutputStream; import java.net. InetAddress; import java.net. ServerSocket; import java.net. Socket; public class NetworkAppExample { public static void main(String args) genera un'eccezione { String host = "localhost"; porta int = 10430; ServerSocket server = nuovo ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Server avviato."); Socket client = nuovo Socket (host, porta); System.out.println("Connessione al server…"); Connessione socket = server.accept(); System.out.println("Connessione stabilita."); DataOutputStream clientOut = new DataOutputStream(client.getOutputStream()); DataInputStream clientIn = new DataInputStream(client.getInputStream()); DataOutputStream serverOut = new DataOutputStream(connection.getOutputStream()); DataInputStream serverIn = new DataInputStream(connection.getInputStream()); System.out.println("La comunicazione è pronta."); byte messageOut = "Hello World".getBytes(); clientOut.writeInt(messageOut.length); clientOut.write(messageOut); clientOut.flush(); System.out.println("Messaggio inviato al server: " + new String(messageOut)); int lunghezza = serverIn.readInt(); if (lunghezza > 0) { byte messageIn = nuovo byte[lunghezza]; serverIn.readFully(messageIn, 0, messageIn.length); } } }
-
Flussi di oggetti
import java.io. ObjectInputStream; import java.io. ObjectOutputStream; import java.net. InetAddress; import java.net. ServerSocket; import java.net. Socket; public class NetworkAppExample { public static void main(String args) genera un'eccezione { String host = "localhost"; porta int = 10430; ServerSocket server = nuovo ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Server avviato."); Socket client = nuovo Socket (host, porta); System.out.println("Connessione al server…"); Connessione socket = server.accept(); System.out.println("Connessione stabilita."); ObjectOutputStream clientOut = new ObjectOutputStream(client.getOutputStream()); ObjectOutputStream serverOut = new ObjectOutputStream(connection.getOutputStream()); ObjectInputStream clientIn = new ObjectInputStream(client.getInputStream()); ObjectInputStream serverIn = new ObjectInputStream(connection.getInputStream()); System.out.println("La comunicazione è pronta."); String messageOut = "Hello World"; clientOut.writeObject(messageOut); clientOut.flush(); System.out.println("Messaggio inviato al server: " + messageOut); String messageIn = (String) serverIn.readObject(); } }
Passaggio 16. Registra il messaggio letto
Ai fini della registrazione, stampare sulla console che il messaggio è stato ricevuto e stamparne il contenuto.
-
Flussi di dati
import java.io. DataInputStream; import java.io. DataOutputStream; import java.net. InetAddress; import java.net. ServerSocket; import java.net. Socket; public class NetworkAppExample { public static void main(String args) genera un'eccezione { String host = "localhost"; porta int = 10430; ServerSocket server = nuovo ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Server avviato."); Socket client = nuovo Socket (host, porta); System.out.println("Connessione al server…"); Connessione socket = server.accept(); System.out.println("Connessione stabilita."); DataOutputStream clientOut = new DataOutputStream(client.getOutputStream()); DataInputStream clientIn = new DataInputStream(client.getInputStream()); DataOutputStream serverOut = new DataOutputStream(connection.getOutputStream()); DataInputStream serverIn = new DataInputStream(connection.getInputStream()); System.out.println("La comunicazione è pronta."); byte messageOut = "Hello World".getBytes(); clientOut.writeInt(messageOut.length); clientOut.write(messageOut); clientOut.flush(); System.out.println("Messaggio inviato al server: " + new String(messageOut)); int lunghezza = serverIn.readInt(); if (lunghezza > 0) { byte messageIn = nuovo byte[lunghezza]; serverIn.readFully(messageIn, 0, messageIn.length); System.out.println("Messaggio ricevuto dal client: " + new String(messageIn)); } } }
-
Flussi di oggetti
import java.io. ObjectInputStream; import java.io. ObjectOutputStream; import java.net. InetAddress; import java.net. ServerSocket; import java.net. Socket; public class NetworkAppExample { public static void main(String args) genera un'eccezione { String host = "localhost"; porta int = 10430; ServerSocket server = nuovo ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Server avviato."); Socket client = nuovo Socket (host, porta); System.out.println("Connessione al server…"); Connessione socket = server.accept(); System.out.println("Connessione stabilita."); ObjectOutputStream clientOut = new ObjectOutputStream(client.getOutputStream()); ObjectOutputStream serverOut = new ObjectOutputStream(connection.getOutputStream()); ObjectInputStream clientIn = new ObjectInputStream(client.getInputStream()); ObjectInputStream serverIn = new ObjectInputStream(connection.getInputStream()); System.out.println("La comunicazione è pronta."); String messageOut = "Hello World"; clientOut.writeObject(messageOut); clientOut.flush(); System.out.println("Messaggio inviato al server: " + messageOut); String messageIn = (String) serverIn.readObject(); System.out.println("Messaggio ricevuto dal cliente: " + messageIn); } }
Passaggio 17. Scollegare le connessioni
La connessione viene disconnessa quando una delle parti chiude i propri flussi. In Java, chiudendo il flusso di output, vengono chiusi anche il socket associato e il flusso di input. Una volta che una parte dall'altra parte scopre che la connessione è morta, deve chiudere anche il flusso di output per evitare perdite di memoria.
// codice omesso import java.net. InetAddress; import java.net. ServerSocket; import java.net. Socket; public class NetworkAppExample { public static void main(String args) genera un'eccezione { String host = "localhost"; porta int = 10430; ServerSocket server = nuovo ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Server avviato."); Socket client = nuovo Socket (host, porta); System.out.println("Connessione al server…"); Connessione socket = server.accept(); System.out.println("Connessione stabilita."); // codice omesso System.out.println("La comunicazione è pronta."); // codice omesso clientOut.close(); serverOut.close(); } }
Passaggio 18. Disconnessione del registro
Ai fini della registrazione, le connessioni di stampa sulla console sono state disconnesse.
// codice omesso import java.net. InetAddress; import java.net. ServerSocket; import java.net. Socket; public class NetworkAppExample { public static void main(String args) genera un'eccezione { String host = "localhost"; porta int = 10430; ServerSocket server = nuovo ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Server avviato."); Socket client = nuovo Socket (host, porta); System.out.println("Connessione al server…"); Connessione socket = server.accept(); System.out.println("Connessione stabilita."); // codice omesso System.out.println("La comunicazione è pronta."); // codice omesso clientOut.close(); serverOut.close(); System.out.println("Connessioni chiuse."); } }
Passaggio 19. Termina il server
Le connessioni sono disconnesse, ma il server è ancora attivo e funzionante. Come
ServerSocket
non è associato ad alcun flusso, deve essere chiuso esplicitamente chiamando
chiudere()
metodo.
// codice omesso import java.net. InetAddress; import java.net. ServerSocket; import java.net. Socket; public class NetworkAppExample { public static void main(String args) genera un'eccezione { String host = "localhost"; porta int = 10430; ServerSocket server = nuovo ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Server avviato."); Socket client = nuovo Socket (host, porta); System.out.println("Connessione al server…"); Connessione socket = server.accept(); System.out.println("Connessione stabilita."); // codice omesso System.out.println("La comunicazione è pronta."); // codice omesso clientOut.close(); serverOut.close(); System.out.println("Connessioni chiuse."); server.close(); } }
Passaggio 20. Terminazione del server di registro
Ai fini della registrazione, la stampa sul server della console è stata terminata.
// codice omesso import java.net. InetAddress; import java.net. ServerSocket; import java.net. Socket; public class NetworkAppExample { public static void main(String args) genera un'eccezione { String host = "localhost"; porta int = 10430; ServerSocket server = nuovo ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Server avviato."); Socket client = nuovo Socket (host, porta); System.out.println("Connessione al server…"); Connessione socket = server.accept(); System.out.println("Connessione stabilita."); // codice omesso System.out.println("La comunicazione è pronta."); // codice omesso clientOut.close(); serverOut.close(); System.out.println("Connessioni chiuse."); server.close(); System.out.println("Server terminato."); } }
Passaggio 21. Compila ed esegui
La registrazione ci ha permesso di sapere se l'applicazione ha avuto successo o meno. Uscita prevista:
Server avviato. Connessione al server… Connessione stabilita. La comunicazione è pronta. Messaggio inviato al server: Hello World Messaggio ricevuto dal client: Hello World Connessioni chiuse. Server terminato.
Nel caso in cui il tuo output non sia come quello sopra, il che è improbabile che accada, ci sono alcune soluzioni:
-
Se l'uscita si ferma sulla linea
Connessione stabilita.
e vengono utilizzati i flussi di oggetti, svuota ciascuno
ObjectOutputStream
- subito dopo l'inizializzazione perché le intestazioni, per qualche motivo, non sono state inviate.
-
Se l'output viene stampato
java.net. BindException: indirizzo già in uso
- scegli un numero di porta diverso perché quello specificato è già utilizzato.
Suggerimenti
- La connessione a un server su una rete diversa viene eseguita collegandosi all'indirizzo IP esterno di un dispositivo che esegue il server che dispone di una porta di inoltro.
- La connessione a un server sulla stessa rete viene eseguita collegandosi all'indirizzo IP privato di un dispositivo che esegue il server o inoltrando una porta e connettendosi all'indirizzo IP esterno del dispositivo.
- Esistono software, come Hamachi, che consentono di connettersi al server su una rete diversa senza inoltrare una porta, ma richiedono l'installazione del software su entrambi i dispositivi.
Esempi
Le applicazioni di rete che utilizzano il blocco di input/output devono utilizzare i thread. Gli esempi seguenti mostrano un server minimalista e un'implementazione client con thread. Il codice di rete è essenzialmente lo stesso dell'articolo, tranne per il fatto che alcuni frammenti sono stati sincronizzati, spostati nei thread e gestite le eccezioni.
Server.java
import java.io. IOException; import java.net. InetAddress; import java.net. ServerSocket; import java.net. SocketException; import java.net. UnknownHostException; import java.util. ArrayList; import java.util. Collections; import java.util. List; /** * La classe {@code Server} rappresenta un punto finale del server in una rete. {@code Server} una volta associato a un determinato indirizzo IP * e porta, stabilisce connessioni con i client ed è in grado di comunicare con loro o disconnetterli. *
* Questa classe è thread-safe. * * @version 1.0 * @see Client * @see Connection */ public class Server implementa Runnable { private ServerSocket server; lista privata
connessioni; thread privato; private final Object connectionLock = new Object(); /** * Costruisce un {@code Server} che interagisce con i client sul nome host e sulla porta specificati con la * lunghezza massima richiesta specificata di una coda di client in entrata. * * @param host Indirizzo host da utilizzare. * @param port Numero di porta da utilizzare. * @param backlog Lunghezza massima richiesta della coda dei client in entrata. * @throws NetworkException Se si verifica un errore durante l'avvio di un server. */ public Server(String host, int port, int backlog) genera NetworkException { try { server = new ServerSocket(port, backlog, InetAddress.getByName(host)); } catch (UnknownHostException e) { throw new NetworkException("Impossibile risolvere il nome host: " + host, e); } catch (IllegalArgumentException e) { throw new NetworkException("Il numero di porta deve essere compreso tra 0 e 65535 (incluso): " + port); } catch (IOException e) { throw new NetworkException("Impossibile avviare il server.", e); } connessioni = Collections.synchronizedList(new ArrayList()); thread = nuovo Thread (questo); thread.start(); } /** * Costruisce un {@code Server} che interagisce con i client sul nome host e sulla porta specificati. * * @param host Indirizzo host da associare. * @param port Numero di porta da associare. * @throws NetworkException Se si verificano errori durante l'avvio di un server. */ public Server(String host, int port) genera NetworkException { this(host, port, 50); } /** * Ascolta, accetta e registra le connessioni in entrata dai client. */ @Override public void run() { while (!server.isClosed()) { try { connection.add(new Connection(server.accept())); } catch (SocketException e) { if (!e.getMessage().equals("Socket chiuso")) { e.printStackTrace(); } } catch (NetworkException | IOException e) { e.printStackTrace(); } } } /** * Invia i dati a tutti i clienti registrati. * * @param data Dati da inviare. * @throws IllegalStateException Se si tenta di scrivere dati quando il server è offline. * @throws IllegalArgumentException Se i dati da inviare sono null. */ public void broadcast(Object data) { if (server.isClosed()) { throw new IllegalStateException("Dati non inviati, il server non è in linea."); } if (data == null) { throw new IllegalArgumentException("dati nulli"); } sincronizzato (connectionsLock) { for (Connection connection: connection) { try { connection.send(data); System.out.println("Dati inviati al cliente con successo."); } catch (NetworkException e) { e.printStackTrace(); } } } } /** * Invia un messaggio di disconnessione e disconnette il client specificato. * * @param connection Client da disconnettere. * @throws NetworkException Se si verifica un errore durante la chiusura della connessione. */ public void disconnect (Connection connection) genera NetworkException { if (connections.remove(connection)) { connection.close(); } } /** * Invia un messaggio di disconnessione a tutti i client, li disconnette e termina il server. */ public void close() lancia NetworkException { sincronizzato (connectionsLock) { for (Connection connection: connection) { try { connection.close(); } catch (NetworkException e) { e.printStackTrace(); } } } connection.clear(); prova { server.close(); } catch (IOException e) { throw new NetworkException("Errore durante la chiusura del server."); } infine { thread.interrupt(); } } /** * Restituisce se il server è online o meno. * * @return True se il server è online. Falso, altrimenti. */ public boolean isOnline() { return !server.isClosed(); } /** * Restituisce un array di client registrati. */ public Connection getConnections() { sincronizzato (connectionsLock) { return connection.toArray(new Connection[connections.size()]); } } }
Client.java
import java.io. IOException; import java.net. Socket; import java.net. UnknownHostException; /** * La classe {@code Client} rappresenta un endpoint client in una rete. {@code Client}, una volta connesso a un determinato * server, è garantito che sarà in grado di comunicare solo con il server. Il fatto che altri client ricevano o meno i dati * dipende dall'implementazione del server. *
* Questa classe è thread-safe. * * @version 1.0 * @see Server * @see Connection */ public class Client { private Connection connection; /** * Costruisce un {@code Client} connesso al server sull'host e sulla porta specificati. * * @param host Indirizzo host da associare. * @param port Numero di porta da associare. * @throws NetworkException Se si verifica un errore durante l'avvio di un server. */ public Client(String host, int port) genera NetworkException { try { connection = new Connection(new Socket(host, port)); } catch (UnknownHostException e) { throw new NetworkException("Impossibile risolvere il nome host: " + host, e); } catch (IllegalArgumentException e) { throw new NetworkException("Il numero di porta deve essere compreso tra 0 e 65535 (incluso): " + port); } catch (IOException e) { throw new NetworkException("Impossibile avviare il server.", e); } } /** * Invia i dati all'altra parte. * * @param data Dati da inviare. * @throws NetworkException Se la scrittura nel flusso di output fallisce. * @throws IllegalStateException Se si tenta di scrivere dati alla chiusura della connessione. * @throws IllegalArgumentException Se i dati da inviare sono null. * @throws UnsupportedOperationException Se si tenta di inviare un tipo di dati non supportato. */ public void send (dati oggetto) genera NetworkException { connection.send (dati); } /** * Invia un messaggio di disconnessione e chiude la connessione con il server. */ public void close() lancia NetworkException { connection.close(); } /** * Restituisce se il client è connesso o meno al server. * * @return True se il client è connesso. Falso, altrimenti. */ public boolean isOnline() { return connection.isConnected(); } /** * Restituisce l'istanza {@link Connection} del client. */ connessione pubblica getConnection() { connessione di ritorno; } }
Connessione.java
import java.io. DataInputStream; import java.io. DataOutputStream; import java.io. IOException; import java.net. Socket; import java.net. SocketException; /** * La classe {@code Connection} rappresenta o una connessione da server a client o un client end-point in una rete * {@code Connection}, una volta connesso, è in grado di scambiare dati con altre parti, a seconda su un server * implementazione. *
* Questa classe è thread-safe. * * @version 1.0 * @see Server * @see Client */ public class Connection implementa Runnable { private Socket socket; uscita DataOutputStream privata; DataInputStream privato in; thread privato; oggetto finale privato writeLock = new Object(); oggetto finale privato readLock = new Object(); /** * Costruisce {@code Connection} utilizzando i flussi di un {@link Socket} specificato. * * @param socket Socket da cui prelevare i flussi.*/ public Connection(Socket socket) lancia NetworkException { if (socket == null) { throw new IllegalArgumentException("null socket"); } this.socket = socket; try { out = new DataOutputStream(socket.getOutputStream()); } catch (IOException e) { throw new NetworkException("Impossibile accedere al flusso di output.", e); } try { in = new DataInputStream(socket.getInputStream()); } catch (IOException e) { throw new NetworkException("Impossibile accedere al flusso di input.", e); } thread = nuovo Thread (questo); thread.start(); } /** * Legge i messaggi mentre la connessione con l'altra parte è attiva. */ @Override public void run() { while (!socket.isClosed()) { try { int identificatore; byte byte; sincronizzato (readLock) { identificatore = in.readInt(); int lunghezza = in.readInt(); if (lunghezza > 0) { byte = nuovo byte[lunghezza]; in.readFully(byte, 0, byte.lunghezza); } else { continua; } } switch (identificatore) { case Identifier. INTERNAL: String command = new String(bytes); if (command.equals("disconnect")) { if (!socket.isClosed()) { System.out.println("Pacchetto di disconnessione ricevuto."); prova {chiudi(); } catch (NetworkException e) { return; } } } rottura; case Identifier. TEXT: System.out.println("Messaggio ricevuto: " + new String(bytes)); rottura; default: System.out.println("Dati ricevuti non riconosciuti."); } } catch (SocketException e) { if (!e.getMessage().equals("Socket chiuso")) { e.printStackTrace(); } } catch (IOException e) { e.printStackTrace(); } } } /** * Invia i dati all'altra parte. * * @param data Dati da inviare. * @throws NetworkException Se la scrittura nel flusso di output fallisce. * @throws IllegalStateException Se si tenta di scrivere dati alla chiusura della connessione. * @throws IllegalArgumentException Se i dati da inviare sono null. * @throws UnsupportedOperationException Se si tenta di inviare un tipo di dati non supportato. */ public void send(Object data) throws NetworkException { if (socket.isClosed()) { throw new IllegalStateException("Dati non inviati, connessione chiusa."); } if (data == null) { throw new IllegalArgumentException("dati nulli"); } int identificatore; byte byte; if (istanza di dati di String) { identificatore = Identificatore. TEXT; byte = ((Stringa) dati).getBytes(); } else { throw new UnsupportedOperationException("Tipo di dati non supportato: " + data.getClass()); } try { sincronizzato (writeLock) { out.writeInt(identificatore); out.writeInt(bytes.length); out.write(byte); out.flush(); } } catch (IOException e) { throw new NetworkException("Impossibile inviare i dati.", e); } } /** * Invia un messaggio di disconnessione e chiude la connessione con l'altra parte. */ public void close() lancia NetworkException { if (socket.isClosed()) { throw new IllegalStateException("La connessione è già chiusa."); } prova { byte messaggio = "disconnetti".getBytes(); sincronizzato (writeLock) { out.writeInt(Identifier. INTERNAL); out.writeInt(messaggio.lunghezza); out.write(messaggio); out.flush(); } } catch (IOException e) { System.out.println("Impossibile inviare il messaggio di disconnessione."); } try { sincronizzato (writeLock) { out.close(); } } catch (IOException e) { throw new NetworkException("Errore durante la chiusura della connessione.", e); } infine { thread.interrupt(); } } /** * Restituisce se la connessione con l'altra parte è attiva o meno. * * @return True se la connessione è attiva. Falso, altrimenti. */ public boolean isConnected() { return !socket.isClosed(); } }
Identificatore.java
/** * La classe {@code Identifier} contiene le costanti utilizzate da {@link Connection} per serializzare e deserializzazione dei dati * inviati sulla rete. * * @version 1.0 * @see Connection */ public final class Identifier { /** * Identificatore per i messaggi interni. */ public static final int INTERNAL = 1; /** * Identificatore per i messaggi testuali. */ public static final int TEXT = 2; }
NetworkException.java
/** * La classe {@code NetworkException} indica un errore relativo alla rete. */ public class NetworkException estende l'eccezione { /** * Costruisce un {@code NetworkException} con {@code null} come messaggio. */ public NetworkException() { } /** * Costruisce un {@code NetworkException} con il messaggio specificato. * * @param message Un messaggio per descrivere l'errore. */ public NetworkException(String message) { super(message); } /** * Costruisce un {@code NetworkException} con il messaggio e la causa specificati. * * @param message Un messaggio per descrivere l'errore. * @param causa Una causa di errore. */ public NetworkException(String message, Throwable cause) { super(message, cause); } /** * Costruisce un {@code NetworkException} con la causa specificata. * * @param causa Una causa di errore. */ public NetworkException(Throwable cause) { super(causa); } }
Esempio di utilizzo.java
/** * La classe {@code UsageExample} mostra l'utilizzo di {@link Server} e {@link Client}. Questo esempio utilizza * {@link Thread#sleep(long)} per garantire che ogni segmento venga eseguito perché l'avvio e la chiusura rapidi fanno sì che alcuni * segmenti non vengano eseguiti. * * @version 1.0 * @see Server * @see Client */ public class UsageExample { public static void main(String args) genera un'eccezione { String host = "localhost"; porta int = 10430; Server server = nuovo Server (host, porta); Client client = nuovo Client (host, porta); Thread.sleep(100L); client.send("Ciao."); server.broadcast("Ehi, amico!"); Thread.sleep(100L); server.disconnect(server.getConnections()[0]); // o client.close() per disconnettersi dal lato client server.close(); } }