3.2.4) Tipi base di Stream |
Le classi InputStream
e OutputStream
sono astratte, e da esse si derivano quelle che rappresentano
le connessioni a reali canali di comunicazione di vario tipo.
Ne esistono molte; noi parleremo di:
Si noti che SocketInputStream
e SocketOutputStream
sono classi private, per cui una connessione di rete viene gestita
per mezzo di InputStream
e OutputStream
.
3.2.5) Stream per accesso a file |
L'accesso a un file si ottiene con due tipi di Stream:
FileInputStream
per l'accesso in lettura;
FileOutputStream
per l'accesso in scrittura.
La classe FileInputStream
permette di leggere sequenzialmente
da un file (che deve già esistere).
FileInputStream
Costruttori
public FileInputStream(String fileName) throws IOException;
public FileInputStream(File fileName) throws IOException;
Sono i più importanti. La classeFile
rappresenta un nome di file in modo indipendente dalla piattaforma, ed è di solito utilizzata in congiunzione con unFileDialog
che permette all'utente la scelta di un file. Vedremo un esempio più avanti. Se si usa una stringa, essa può essere:
Metodi più importanti
Tutti quelli diInputStream
, più uno (getFD()
) che non vedremo.
La classe FileOutputStream
permette di scrivere sequenzialmente
su un file, che viene creato quando si istanzia lo stream. Se
esiste già un file con lo stesso nome, esso viene distrutto.
FileOutputStream
Costruttori
public FileOutputStream(String fileName) throws IOException;
public FileOutputStream (File fileName) throws IOException;
Sono i più importanti. Per la classeFile
vale quanto detto sopra.
Metodi più importanti
Tutti quelli diOutputStream
, più uno (getFD()
) che non vedremo.
Si noti che questi stream, oltre che eccezioni di I/O, possono
generare eccezioni di tipo SecurityException
(ad
esempio se un applet cerca di aprire uno stream verso il file
system locale).
Esempio 3 |
Applicazione che mostra in una TextArea
il contenuto
di un file di testo, e che salva il contenuto di tale TextArea
in un file.
Il codice è costituito da tre classi. La prima, BaseAppE3
,
è basata su quella dell'esempio 1 ed istanzia a comando
una delle altre due quando si preme un bottone. Se ne mostrano
qui solo i frammenti di codice rilevanti.
import java.awt.*; public class BaseAppE3 extends Frame { ...ecc. //-------------------------------------------------- void button1_Clicked(Event event) { baseRead = new BaseRead(textArea1, textArea2); baseRead.readFile(); } //-------------------------------------------------- void button2_Clicked(Event event) { baseWrite = new BaseWrite(textArea1, textArea2); baseWrite.writeFile(); } //-------------------------------------------------- |
Le altre due si occupano della lettura e scrittura sul file.
import java.awt.*; import java.io.*; public class BaseRead { TextArea commandArea, responseArea; DataInputStream is = null; //-------------------------------------------------- public BaseRead(TextArea commandArea, TextArea responseArea) { this.commandArea = commandArea; this.responseArea = responseArea; try { is=new DataInputStream(new FileInputStream(commandArea.getText())); } catch (Exception e) { responseArea.appendText("Exception" + "\n"); } } //-------------------------------------------------- public void readFile() { String inputLine; try { while ((inputLine = is.readLine()) != null) { responseArea.appendText(inputLine + "\n"); } } catch (IOException e) { responseArea.appendText("IO Exception" + "\n"); } finally { try { is.close(); } catch (IOException e) { responseArea.appendText("IO Exception" + "\n"); } } } } |
import java.awt.*; import java.io.*; public class BaseWrite { TextArea commandArea, responseArea; PrintStream os = null; //-------------------------------------------------- public BaseWrite(TextArea commandArea, TextArea responseArea) { this.commandArea = commandArea; this.responseArea = responseArea; try { os = new PrintStream(new FileOutputStream(commandArea.getText())); } catch (Exception e) { responseArea.appendText("Exception" + "\n"); } } //-------------------------------------------------- public void writeFile() { os.print(responseArea.getText()); os.close(); } } |
Se si vuole selezionare il file da leggere o da creare in modo
interattivo, si usa la classe FileDialog
, che serve
a entrambi gli scopi.
Il codice per aprire un file in lettura è tipicamente:
... FileDialog openDialog = new FileDialog(null, "Titolo", FileDialog.LOAD); openDialog.show(); if (openDialog.getFile() != null) { is = new DataInputStream( new FileInputStream( newFile(openDialog.getDirectory(), openDialog.getFile()))); } ...
E quello per creare un file in scrittura:
... FileDialog saveDialog = new FileDialog(null, "Titolo", FileDialog.SAVE); saveDialog.show(); if (saveDialog.getFile() != null) { os = new PrintStream( new FileOutputStream( new File(saveDialog.getDirectory(), saveDialog.getFile())); } ...
3.2.6) Stream per accesso alla memoria |
Esistono due tipi di stream che consentono di leggere e scrivere da/su buffer di memoria:
ByteArrayInputStream
ByteArrayOutputStream
Il ByteArrayInputStream
permette di leggere sequenzialmente
da un buffer (ossia da un array di byte) di memoria.
ByteArrayInputStream
Costruttori
A noi ne serve solo uno:
public ByteArrayInputStream (byte[] buf);
Crea unByteArrayInputStream
, attaccato all'arraybuf
, dal quale vengono poi letti i dati.
Metodi più importanti
Quelli di InputStream
. Da notare che:
EOF
si incontra quando si raggiunge la fine
dell'array;
reset()
ripristina lo stream, per cui
le successive letture ripartono dall'inizio dell'array.
Il ByteArrayOutputStream
permette di scrivere in
un buffer (array di byte) in memoria. Il buffer viene allocato
automaticamente e si espande secondo le necessità, sempre
automaticamente.
ByteArrayOutputStream
Costruttori
I più utilizzati sono:
public ByteArrayOutputStream();
Crea unByteArrayOutputStream
con una dimensione iniziale del buffer di 32 byte. Il buffer comunque cresce automaticamente secondo necessità.
public ByteArrayOutputStream (int size);
Crea unByteArrayOutputStream
con una dimensione iniziale disize
byte. E' utile quando si voglia massimizzare l'efficienza e si conosca in anticipo la taglia dei dati da scrivere.
Metodi più importanti
Oltre a quelli di OutputStream
ve ne sono alcuni
per accedere al buffer interno.
public void reset();
Ricomincia la scrittura dall'inizio, di fatto azzerando il buffer.
public int size();
Restituisce il numero di byte contenuti (cioè scritti dopo l'ultimareset()
) nel buffer.
public byte[] toByteArray();
Restituisce una copia dei byte contenuti nel buffer, che non viene resettato.
public String toString();
Restituisce una stringa che è una copia dei byte contenuti. L'array non viene resettato.
public writeTo(OutputStream out) throws IOException;
Scrive il contenuto del buffer sullo streamout
. Il buffer non viene resettato. E' più efficiente che chiamaretoByteArray()
e scrivere quest'ultimo suout
.
3.2.7) Stream per accesso a connessioni di rete |
Il dialogo di un'applicazione Java con una
peer entity attraverso la rete può avvenire sia appoggiandosi
a TCP che a UDP.
Poiché la modalità connessa è molto più
versatile ed interessante per i nostri scopi, ci occuperemo solo
di essa.
Come noto, la vita di una connessione TCP si articola in tre fasi:
Anche un'applicazione Java segue lo stesso percorso:
Socket
: per aprire una connessione con un server
in ascolto;
ServerSocket
: per mettersi in ascolto di richieste
di connessione;
Socket
precedentemente creato si ottengono:
InputStream
per ricevere i dati dalla peer entity;
OutputStream
per inviare i dati alla peer entity;
InputStream
;
OutputStream
;
Socket
(per ultimo, preferibilmente).
3.2.7.1) Classe Socket |
Questa classe rappresenta in Java l'estremità
locale di una connessione TCP. La creazione di un oggetto Socket
,
con opportuni parametri, stabilisce automaticamente una connessione
TCP con l'host remoto voluto.
Se la cosa non riesce, viene generata una eccezione (alcuni dei
motivi possibili sono: host irraggiungibile, host inesistente,
nessun processo in ascolto sul server).
Una volta che l'oggetto Socket
è creato, da
esso si possono ottenere i due stream (di input e output) necessari
per comunicare.
Costruttore
public Socket(String host, int port) throws IOException;
A noi basta questo (ce ne sono due).host
è un nome DNS o un indirizzo IP in dotted decimal notation, eport
è il numero di port sul quale deve esserci un processo server in ascolto.
Ad esempio:
...
mySocket = new Socket("cesare.dsi.uniroma1.it",
80);
mySocket = new Socket("151.100.17.25", 80);
...
Metodi più importanti
public InputStream getInputStream() throws IOException;
Restituisce unInputStream
per leggere i dati inviati dalla peer entity.
public OutputStream getOutputStream() throws IOException;
Restituisce unOutputStream
per inviare i dati inviati dalla peer entity.
public void close() throws IOException;
Chiude ilSocket
(e quindi questa estremità della connessione TCP).
public int getPort() throws IOException;
Restituisce il numero di port all'altra estremità della connessione.
public int getLocalPort() throws IOException;
Restituisce il numero di port di questa estremità della connessione.
Esempio 4 |
Applicazione che apre una connessione via Socket con un host e
su un port scelti per mezzo dei due rispettivi campi. Va usata
con protocolli ASCII,
cioè protocolli che prevedono lo scambio di sequenze di
caratteri ASCII.
L'applicazione permette di:
Si noti che, a seconda del numero di port specificato, si può
dialogare con server differenti. Ad esempio, usando il port 21
ci si connette con un server FTP, mentre con il port 80 (come
nell'esempio di figura 4-16) ci si collega ad un server HTTP.
Il codice è costituito da due classi. La prima, BaseAppE4
,
è basata su quella dell'esempio 1 e provvede ad istanziare
la seconda che ha il compito di attivare la connessione e di gestire
la comunicazione. Se ne mostrano qui solo i frammenti di codice
rilevanti.
import java.awt.*; public class BaseAppE4 extends Frame { ...ecc. //-------------------------------------------------- void button1_Clicked(Event event) { baseConn = new BaseConn(textField1.getText(), textField2.getText(), textArea1, textArea2); } //-------------------------------------------------- void button2_Clicked(Event event) { baseConn.send(); } //-------------------------------------------------- void button3_Clicked(Event event) { baseConn.receive(); } //-------------------------------------------------- void button4_Clicked(Event event) { baseConn.close(); } //-------------------------------------------------- |
La seconda si occupa della comunicazione.
import java.awt.*; import java.lang.*; import java.io.*; import java.net.*; public class BaseConn { TextArea commandArea, responseArea; Socket socket = null; PrintStream os = null; DataInputStream is = null; //-------------------------------------------------- public BaseConn(String host, String port, TextArea commandArea, TextArea responseArea) { this.commandArea = commandArea; this.responseArea = responseArea; try { socket = new Socket(host, Integer.parseInt(port)); os = new PrintStream(socket.getOutputStream()); is = new DataInputStream(socket.getInputStream()); responseArea.appendText("***Connection established" + "\n"); } catch (Exception e) { responseArea.appendText("Exception" + "\n"); } } //-------------------------------------------------- public void send() { os.println(commandArea.getText()); } //-------------------------------------------------- public void receive() { String inputLine; try { inputLine = is.readLine(); responseArea.appendText(inputLine + "\n"); } catch (IOException e) { responseArea.appendText("IO Exception" + "\n"); } } //-------------------------------------------------- public void close() { try { is.close(); os.close(); socket.close(); responseArea.appendText("***Connection closed" + "\n"); } catch (IOException e) { responseArea.appendText("IO Exception" + "\n"); } } } |
3.2.7.2) Classe ServerSocket |
Questa classe costituisce il meccanismo con
cui un programma Java agisce da server, e cioè accetta
richieste di connessioni provenienti dalla rete.
La creazione di un ServerSocket
, seguita da una opportuna
istruzione di "ascolto", fa si che l'applicazione si
metta in attesa di una richiesta di connessione.
Quando essa arriva, il ServerSocket
crea automaticamente
un Socket
che rappresenta l'estremità locale
della connessione appena stabilita. Da tale Socket
si derivano gli stream che permettono la comunicazione.
ServerSocket
Costruttori
public ServerSocket(int port) throws IOException;
A noi basta questo (ce ne sono due).port
è il numero del port sul quale ilServerSocket
si mette in ascolto.
Metodi più importanti
public Socket accept() throws IOException;
Questo metodo blocca il chiamante finché qualcuno non cerca di connettersi. Quando questo succede, il metodo ritorna restituendo unSocket
che rappresenta l'estremità locale della connessione.
public void close() throws IOException;
Chiude ilServerSocket
(e quindi non ci sarà più nessuno in ascolto su quel port).
public int getLocalPort() throws IOException;
Restituisce il numero di port su cui ilServerSocket
è in ascolto.
Esempio 5 |
Semplice Server che:
Il codice è il seguente.
import java.io.*; import java.net.*; public class SimpleServer { //-------------------------------------------------- public static void main(String args[]) { ServerSocket server; Socket client; String inputLine; DataInputStream is = null; PrintStream os = null; try { server = new ServerSocket(5000); System.out.println("Accepting one connection..."); client = server.accept(); server.close(); System.out.println("Connection from one client accepted."); is = new DataInputStream(client.getInputStream()); os = new PrintStream(client.getOutputStream()); os.println("From SimpleServer: Welcome!"); while ((inputLine = is.readLine()) != null) { System.out.println("Received: " + inputLine); os.println("From SimpleServer: " + inputLine); } is.close(); os.close(); client.close(); System.out.println("Connection closed."); } catch (IOException e) { e.printStackTrace(); } } } |
Sia il client che il server visti precedentemente hanno pesanti limitazioni, derivanti dal fatto che essi consistono di un unico flusso di elaborazione:
Per superare queste limitazioni la soluzione più potente
e versatile è ricorrere al multithreading,
cioè alla gestione di multipli flussi di elaborazione all'interno
dell'applicazione.