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:


Figura 3-12: Tipi base di stream

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:

La classe FileInputStream permette di leggere sequenzialmente da un file (che deve già esistere).


Figura 3-13: FileInputStream

Costruttori

public FileInputStream(String fileName) throws IOException;

public FileInputStream(File fileName) throws IOException;

Sono i più importanti. La classe File rappresenta un nome di file in modo indipendente dalla piattaforma, ed è di solito utilizzata in congiunzione con un FileDialog 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 di InputStream, 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.


Figura 3-14: FileOutputStream

Costruttori

public FileOutputStream(String fileName) throws IOException;

public FileOutputStream (File fileName) throws IOException;

Sono i più importanti. Per la classe File vale quanto detto sopra.

Metodi più importanti

Tutti quelli di OutputStream, 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.


Figura 3-15: Interfaccia utente dell'esempio 3

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:

Il ByteArrayInputStream permette di leggere sequenzialmente da un buffer (ossia da un array di byte) di memoria.


Figura 3-16: ByteArrayInputStream

Costruttori

A noi ne serve solo uno:

public ByteArrayInputStream (byte[] buf);

Crea un ByteArrayInputStream, attaccato all'array buf, dal quale vengono poi letti i dati.

Metodi più importanti

Quelli di InputStream. Da notare che:

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.


Figura 3-17: ByteArrayOutputStream

Costruttori

I più utilizzati sono:

public ByteArrayOutputStream();

Crea un ByteArrayOutputStream con una dimensione iniziale del buffer di 32 byte. Il buffer comunque cresce automaticamente secondo necessità.

public ByteArrayOutputStream (int size);

Crea un ByteArrayOutputStream con una dimensione iniziale di size 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'ultima reset()) 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 stream out. Il buffer non viene resettato. E' più efficiente che chiamare toByteArray() e scrivere quest'ultimo su out.

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:

  1. apertura della connessione;
  2. dialogo per mezzo della connessione;
  3. chiusura della connessione;

Anche un'applicazione Java segue lo stesso percorso:

  1. apertura della connessione. Esistono per questo due classi:
  2. dialogo per mezzo della connessione. Con opportune istruzioni, dal Socket precedentemente creato si ottengono:
  3. chiusura della connessione. Con opportune istruzioni, si chiudono:


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, e port è 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 un InputStream per leggere i dati inviati dalla peer entity.

public OutputStream getOutputStream() throws IOException;

Restituisce un OutputStream per inviare i dati inviati dalla peer entity.

public void close() throws IOException;

Chiude il Socket (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.


Figura 3-18: Interfaccia utente dell'esempio 4

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.


Figura 3-19: Connessione a un ServerSocket

Costruttori

public ServerSocket(int port) throws IOException;

A noi basta questo (ce ne sono due). port è il numero del port sul quale il ServerSocket 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 un Socket che rappresenta l'estremità locale della connessione.

public void close() throws IOException;

Chiude il ServerSocket (e quindi non ci sarà più nessuno in ascolto su quel port).

public int getLocalPort() throws IOException;

Restituisce il numero di port su cui il ServerSocket è 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.


Torna al sommario | Vai avanti