Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
vuoi
o PayPal
tutte le volte che vuoi
I SOCKET TCP
I socket sono il primo strumento di programmazione distribuita che useremo. I socket TCP esistono da molto tempo, ma vengono utilizzati ancora oggi per le nuove tecnologie di comunicazione. Se pensiamo a una lampadina che si può accendere da remoto, tra le poche funzionalità che fornisce c'è sicuramente quella di aprire un socket TCP. I socket sono alla base di Java RMI, la quale è alla base di Java EE. Esistono anche i socket UDP ma noi utilizzeremo solo quelli TCP. È con questi che riusciamo ad invocare metodi di oggetti che si trovano su altre macchine posizionate fisicamente lontane. Java mette a disposizione i socket nel package java.net dove vengono definite classi per trattare gli indirizzi di rete come la classe InetAddress ed altre classi simili che gestiscono IPv4 e IPv6. I socket sono un'astrazione che permette allo sviluppatore di vedere una comunicazione su rete come un semplice stream di dati. TCP (Transmission Control Protocol) è un protocollo di comunicazione affidabile, orientato alla connessione, che garantisce la consegna dei dati in ordine e senza errori.
server. Il client è colui che inizia la comunicazione, mentre il server è colui che ascolta e risponde alle richieste del client. Per stabilire una connessione TCP, il client invia una richiesta di connessione al server, che risponde accettando o rifiutando la connessione. Una volta stabilita la connessione, i dati possono essere inviati in entrambe le direzioni. Nel caso di UDP, invece, non c'è una fase di connessione. Il client invia semplicemente i dati al server, senza aspettare una risposta. Non c'è garanzia che i dati inviati arrivino a destinazione, né che arrivino nell'ordine corretto. La scelta tra TCP e UDP dipende dalle esigenze dell'applicazione. TCP è più adatto per applicazioni che richiedono una connessione affidabile, come ad esempio il trasferimento di file o la navigazione web. UDP è più adatto per applicazioni in cui la velocità è prioritaria rispetto all'affidabilità, come ad esempio lo streaming video o i giochi online. I socket TCP e UDP sono identificati da un indirizzo IP e un numero di porta. L'indirizzo IP identifica il computer, mentre il numero di porta identifica il processo all'interno del computer. I socket permettono di stabilire una comunicazione bidirezionale tra due punti della rete.Il server è in esecuzione e attende che qualcuno si connetta.
Il client per potersi connettere deve conoscere l'indirizzo IP del server e il numero di porta. Quando il client richiede la connessione, include nel messaggio anche il proprio indirizzo IP e il proprio numero di porta così che il server possa sapere a chi rispondere.
Quando il server riceve una richiesta di connessione, la accetta e crea un nuovo Thread nel quale verrà creato un socket bidirezionale che verrà usato per rispondere.
Il server crea un Thread per ogni connessione che riceve, così che può sia restare in ascolto per le nuove connessioni, sia rispondere ai client già connessi.
Il package java.net offre due classi per i socket: la classe ServerSocket e la classe Socket.
La classe ServerSocket implementa un socket di connessione che attende richieste da parte di client, quando ne riceve una assegna un socket alla connessione bidirezionale, restituendo il Socket
Il client-server è un modello di comunicazione asimmetrico, in cui c'è un client e un server. In alcuni sistemi, tutti gli utenti possono agire sia come client che come server. Tuttavia, è necessario identificare chi fornisce determinate funzionalità. In una rete peer-to-peer, viene scelto il nodo "più importante" in base a vari criteri.
La comunicazione tra client e server avviene attraverso la scrittura e la lettura di stream (flussi) associati al socket. Questo permette una facile interazione per la trasmissione di istanze di classi Java (oggetti) tra client e server, utilizzando un meccanismo di serializzazione.
Gli stream di I/O sono un'utile astrazione fornita da Java per trattare una sequenza di dati che può provenire da diverse fonti o essere diretta a diverse destinazioni.
entità come file, periferiche, memoria e socket. Gli stream sono presenti in varie forme: la gerarchia delle classi di stream nel packagejava.io
offre una nota disponibilità di strumenti per il programmatore. Possiamo esaminare una parte della gerarchia che parte dalla classe astratta Inputstream
. Pag. 54 a 201. La sottoclasse più importante che useremo è ObjectInputStream
che offre il meccanismo di deserializzazione di un oggetto precedentemente serializzato con ObjectOutputstream
. Gli oggetti che possono essere trasmessi su questo tipo di stream devono implementare l'interfaccia Serializable
o l'interfaccia Externalizable
(che fornisce un meccanismo per definire una serializzazione proprietaria, non affidandosi a quella fornita dal linguaggio). I tipi primitivi possono essere letti con gli appositi metodi, come readByte()
o readFloat()
. Possiamo prendere l'input stream da un oggetto socket in questo modo: ServerSocket server = new
ServerSocket server = new ServerSocket(6000);
Socket client = server.accept();
System.out.println("Connessione accettata, attendo comandi...");
ObjectInputStream inStream = new ObjectInputStream(client.getInputStream());
ObjectOutputStream outStream = new ObjectOutputStream(client.getOutputStream());
// Possiamo usare inStream per leggere i dati in arrivo, e outStream per inviare dati.
// Possiamo chiudere uno stream con close e possiamo forzare l'invio di un pacchetto con flush.
// Sappiamo che il protocollo TCP ottimizza l'invio dei dati, per cui se si vuole inviare un solo byte, TCP aspetta che
// vengano aggiunte altre cose e poi le invia. Se non vogliamo che venga fatta questa ottimizzazione possiamo usare
// flush per forzare l'invio dei dati.
// Possiamo creare un file zip con un inflater.
// Possiamo quindi inviare un oggetto scrivendo
outStream.writeObject(new Object());
// e possiamo leggerlo scrivendo
try {
Object obj = inStream.readObject();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Una ClassNotFoundException
. Ovviamente client e server devono mettersi d'accordo sul tipo di oggetto, se invio un oggetto di tipo Cliente, l'altro dovrà leggere un oggetto di tipo Cliente.
Socket client = server.accept();
e Object obj = inStream.readObject();
sono operazioni bloccanti. Pag. 55 a 201
Esempio di comunicazione Client-Server
Client
public class Client {
private int porta;
private String host;
public Client(int porta, String host) {
this.porta = porta;
this.host = host;
}
public void start() {
ObjectOutputStream out;
ObjectInputStream in;
Socket socket = null;
try {
socket = new Socket(this.host, this.porta);
out = new ObjectOutputStream(socket.getOutputStream());
in = new ObjectInputStream(socket.getInputStream());
out.writeObject("Mario");
String msg = (String) in.readObject();
System.out.println("[CLIENT] Ho letto: " + msg);
socket.close();
} catch (IOException e) {
System.err.println(e.getMessage());
} catch (ClassNotFoundException ex) {
// Gestione dell'eccezione
}
}
}
{System.err.println("Classe non trovata " + ex.getMessage());} catch (Throwable t) {System.out.println("Lanciata throwable: " + t.getMessage());} finally {if (socket != null && !socket.isClosed()) {try { socket.close();} catch (IOException e) {System.err.println("Errore chiusura socket: "+ e.getMessage());}}}}}
Pag. 56 a 201
Server
public class Server { private int porta; public Server(int porta, String host) { this.porta = porta; } public void start() { ObjectOutputStream out; ObjectInputStream in; ServerSocket serverSocket = null; Socket socket = null; try { serverSocket = new ServerSocket(this.porta); System.out.println("[SERVER] Server partito, attendo connessioni..."); socket = serverSocket.accept(); System.out.println("[SERVER] Connessione accettata, attendo comandi..."); out = new ObjectOutputStream(socket.getOutputStream()); in = new ObjectInputStream(socket.getInputStream()); String nome = (String)
in.readObject(); System.out.println("[SERVER] Ho letto: " + nome); out.writeObject("Ciao " + nome); socket.close(); serverSocket.close(); } catch (IOException e) { System.err.println("IOException: " + e.getMessage()); } catch (ClassNotFoundException ex) { System.err.println("Classe non trovata " + ex.getMessage()); } catch (Throwable t) { System.out.println("Lanciata throwable: " + t.getMessage()); } finally { if (socket != null && !socket.isClosed()) { try { socket.close(); } catch (IOException e) { System.err.println("Errore chiusura socket: "+ e.getMessage()); } } if (serverSocket != null && !socket.isClosed()) { try { serverSocket.close(); } catch (IOException e) { System.err.println("Errore chiusura socket: "+ e.getMessage()); } } }
Pag. 57 a 201
Main
public class Starter { private static final int PORTA = 9004; private static final String HOST = "localhost"; public static void main(String[] args) { Client client
<code>new Client(PORTA, HOST);Server server = new Server(PORTA, HOST);new Thread(new Runnable() {public void run() {server.start();}}).start();new Thread(new Runnable() {public void run() {client.start();}}).start();}}</code>
Ovviamente client e server devono essere eseguiti su Thread separati perché eseguono entrambi operazioni bloccanti. Se avviamo prima il server senza usare i Thread, il server rimarrebbe in attesa di connessioni all’infinito.
Nel momento in cui scriviamo <code>socket = new Socket(this.host, this.porta);</code> abbiamo già creato la connessione.
Pag. 58 a 201
Esercizio
Realizziamo un server che mantiene una lista di dati di tipo RecordRegistro.
La classe RecordRegistro conterrà un nome e un indirizzo.
Scriviamo la classe RecordRegistro
<code>public class RecordRegistro implements Serializable {
private static final long serialVersionUID = 6854751512422545867L;
private String nome;
private String indirizzo;
public RecordRegistro(String n, String i) {
this.nome = n;
this.indirizzo = i;
}
}</code>
i; }public String getNome() { return nome; }public void setNome(String nome) { this.nome = nome; }public String getIndirizzo() { return indirizzo; }public void setIndirizzo(String indirizzo) { this.indirizzo = indirizzo; }}
Notiamo che questa classe implementa Serializable. Questa interfaccia è necessaria per poter spedire la classe sulla rete. Questa interfaccia ci permette di scompattare un file in un flusso di dati che può essere salvato su un file (o spedito sulla rete). Il processo contrario, la deserializzazione ci permette di convertire