Lezione 7 - Come Java comunica su Internet


Le prime 3 applicazioni in questa lezione sono state prese dai materiali del seminario tenuto al Cern da Raúl Ramos-Pollán.Allo stesso indirizzo al Cern e' disponibile una copia zippata dello stesso materiale, mentre la S accanto al titolo di ogni applicazione punta a una copia locale dello stesso materiale.
Invece alcune delle applicazioni che seguono sono prese dal corso Physics Simulation with Java
Java puo' comunicare su Internet attraverso 2 diversi meccanismi:
  1. Un meccanismo a basso livello detto in gergo socket:una sorta di collegamento attraverso il quale due programmi si scambiano dati.
  2. Un meccanismo ad alto livello detto Remote Method Invocation(RMI) che permette a un programma Java di richiamare i metodi di un oggetto che si trova in un'altro programma Java che gira su un computer remoto.
Qui vedremo solo la comunicazione via socket.

La comunicazione tra computer si basa su una serie di protocolli disposti in 7 livelli. Considerando solo i protocolli usati da Java per la comunicazione via Internet possiamo limitarci ai 3 livelli superiori:

LivelloNome protocolliFunzione protocolli
ApplicazioneHTTP,ftp,telnet,smtp,ping...applicazione ad alto livello
TrasportoTCP,UDP,...riassemblaggio pacchetti
Rete IP,..trasferimento pacchetti

Ogni protocollo di livello superiore ha bisogno dei livelli inferiori per funzionare.Cosi' su Internet ogni comunicazione avviene basandosi sul cosiddetto protocollo TCP/IP che prevede che ogni nodo ha un suo indirizzo IP numerico. Quando viene inviato ad esempio un mail (livello superiore) questo viene diviso in pacchetti e su ognuno di questi e' indicato l'indirizzo del destinatario. Ogni pacchetto viene inviato per conto suo e istradato dai router. All'arrivo il messaggio viene ricomposto.
In gergo i pacchetti di IP sono detti datagrams e sono formati da un header contenente indirizzi di mittente e destinatario e da un data payload che contiene fino a 65K bytes.Uno speciale programma traceroute(tracert su PC) permette di seguire il cammino di un pacchetto dal mittente al destinatario. Se si indica il nome del computer invece dell'indirizzo numerico,dei nodi speciali di Internet, i Domain Name Server provvedono a ricavare il numero corrispondente.

tracert www.yahoo.com
Tracing route to www.yahoo.com [204.71.200.74]
over a maximum of 30 hops:

  1     1 ms     1 ms     1 ms  cisco.ba.infn.it [192.135.10.2]
  2     2 ms     2 ms     4 ms  rc-infnba.ba.garr.net [193.206.137.77]
  3    10 ms     7 ms     9 ms  na-ba-1.garr.net [193.206.134.121]
  4   161 ms   164 ms   162 ms  garr-neaples.ny.dante.net [212.1.200.105]
  5   158 ms   162 ms   166 ms  500.POS2-2.GW9.NYC4.ALTER.NET [157.130.19.21]
  6   160 ms   161 ms     *     110.ATM3-0.XR2.NYC4.ALTER.NET [152.63.21.198]
  7   162 ms   161 ms   160 ms  188.ATM7-0.XR2.NYC1.ALTER.NET [146.188.178.101]

  8   161 ms   166 ms     *     194.ATM10-0-0.BR1.NYC1.ALTER.NET [146.188.177.14
9]
  9   173 ms   178 ms   181 ms  s5-0-1.ar2.JFK.gblx.net [206.132.150.129]
 10   183 ms     *        *     pos3-1-155M.cr1.JFK.gblx.net [206.132.253.97]
 11     *        *      524 ms  pos6-0-622M.cr2.SNV.gblx.net [206.132.151.14]
 12   443 ms   428 ms   431 ms  pos1-0-2488M.hr8.SNV.gblx.net [206.132.254.41]
 13   443 ms   441 ms   444 ms  208.178.22.58
 14   397 ms   412 ms   426 ms  www.yahoo.com [204.71.200.74]

Trace complete.
Al livello di trasporto abbiamo 2 differenti protocolli TCP(Transmission Control Protocol) e UDP(User Datagram Protocol).Questi protocolli si occupano di riassemblare il messaggio sul computer del destinatario. TCP garantisce il corretto ordine e percio' provvede a farsi ritrasmettere i pacchetti mancanti assicurando quindi una connessione affidabile. Viceversa UDP non garantisce il corretto ordine ne si assicura che tutti i pacchetti siano arrivati. UDP viene usato nei casi in cui la perdita di qualche pacchetto e' accettabile:trasmissioni audio o video. Quasi tutti i protocolli dell'ultimo livello (HTTP,telnet,ftp) usano TCP, solo qualcuno come ping usa UDP.

Con Java potete:

Java usa i protocolli TCP o UDP(quindi di livello "trasporto") per trasferire dati.Per i collegamenti con server Web viene usato il protocollo di livello "applicazione" HTTP che a sua volta si basa su TCP.
Quando comunicate con un programma su un altro computer indicate il numero di port (in italiano porta) per individuare il programma col quale volete dialogare. Questo numero indica la particolare "frequenza" alla quale si comunica.Di solito le frequenze piu' basse sono fissate per i servizi fondamentali (es:http 80) per cui e' meglio usare frequenze superiori a 1023. Ad esempio, su alboot(una macchina Unix) potete sapere quali frequenze sono assegnate e quali sono in uso effettivo dando i comandi:
more /etc/services
more /etc/inetd.conf
Per comunicare attraverso una certa frequenza si crea un socket.Nel caso del protocollo TCP si puo' aprire una connessione attraverso un socket e tenerla aperta finche' tutti i dati sono stati trasmessi. Questo permette l'uso con TCP delle normali catene di stream usate per l'I/O dai files.
Viceversa con UDP i pacchetti sono trasmessi uno a uno attraverso un Datagram socket e non e' possibile aprire ne' connessioni ne' stream.
La cosa piu' facile e' leggere da un documento Web:questa avviene col protocollo HTTP che basandosi su TCP permette di usare uno Stream .
Un programma in grado di comunicare con piu' programmi nello stesso momento si chiama server. Un server in Java si realizza di solito sfruttando la multiprogrammazione dei Thread.Il programma che dialoga col server viene chiamato client ed e' molto piu' semplice da scrivere del server, perche' deve solo inviare la richiesta e attendere la risposta.
In ogni caso,sia che un programma comunichi con 1 o n programmi, la comunicazione avviene attraverso un socket che in Java e' gestito dagli oggetti:
  1. ServerSocket - canale usato da un server TCP per ascoltare le richieste
  2. Socket - canale usato da client e server TCP per soddisfare le richieste
  3. DatagramSocket - canale usato da programmi che comunicano via UDP



Applicazione 1:Client e Server TCP . S

    file EchoClient.java

import java.io.*;
import java.net.*;

public class EchoClient {
      public static void main(String[] args) throws IOException {

        Socket echoSocket = null;
        PrintWriter out = null;
        BufferedReader in = null;

        try {
            echoSocket = new Socket(args[0], Integer.parseInt(args[1]));
            out = new PrintWriter(echoSocket.getOutputStream(), true);
            in = new BufferedReader(new InputStreamReader(echoSocket.getInputStream()));
        } catch (UnknownHostException e) {
            System.err.println("Don't know about host: "+args[0]);
            System.exit(1);
        } catch (IOException e) {
            System.err.println("Couldn't get I/O for the connection to: "+args[0]);
            System.exit(1);
        }

        System.out.println("Server welcome is: "+in.readLine());

        out.println("Hello how are you");
        System.out.println("Server responded: "+in.readLine());

        out.println("Some other stuff");
        System.out.println("Server now responded: "+in.readLine());

        out.close();
        in.close();
        echoSocket.close();
    }
}
Questo programma presenta lo schema tipico di un client TCP:

    file MyServer.java

import java.net.*;
import java.io.*;
import java.util.*;

public class MyServer {
    public static void main(String[] args) throws IOException {

        ServerSocket serverSocket = null;
        try {
            serverSocket = new ServerSocket(Integer.parseInt(args[0]));
        } catch (IOException e) {
            System.err.println("Could not listen on port.");
            System.exit(1);
        }
  
      while (true) {
        Socket clientSocket = null;
        try {
            System.out.println("Server listnening");
            clientSocket = serverSocket.accept();
        } catch (IOException e) {
            System.err.println("Accept failed.");
            System.exit(1);
        }

        PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
        BufferedReader in = new BufferedReader(
                                new InputStreamReader(
                                clientSocket.getInputStream()));
        String inputLine, outputLine;

        out.println("Welcome to the echo server.");

        while ((inputLine = in.readLine()) != null) {
             if (inputLine.equals("Bye.")) break;
             Date now = new Date(System.currentTimeMillis());
             out.println(now+": "+inputLine);
        }
        out.close();
        in.close();
        clientSocket.close();
      }
    }
}
Ed ecco lo schema tipico di un server TCP che si mette in attesa di connessione .


Applicazione 2:Client e Server UDP .S

    file DataClient.java

import java.io.*;
import java.net.*;
import java.util.*;

public class DataClient {
    public static void main(String[] args) throws IOException {

        // get a datagram socket
        DatagramSocket socket = new DatagramSocket();

        // send request
        byte[] buf = new byte[256];
        String hello = "Hello how re you?";
        buf = hello.getBytes();
        
        InetAddress address = InetAddress.getByName(args[0]);
        DatagramPacket packet = new DatagramPacket(buf, buf.length, address, Integer.parseInt(args[1]));
        System.out.println("Sending packet to: "+address);
        socket.send(packet);

        // get response
        byte[] rbuf = new byte[256];
        packet = new DatagramPacket(rbuf, rbuf.length);
        socket.receive(packet);

        // display response
        String received = new String(packet.getData());
        System.out.println("From server : " + received);

        socket.close();
    }
}
Ed ecco lo schema di un client UDP:

    file DataServer.java

import java.net.*;
import java.util.*;

public class DataServer {
    public static void main(String args[]) {

        while (true) {
            try {
                DatagramSocket socket = new DatagramSocket(Integer.parseInt(args[0]));
                byte[] inbuf = new byte[256];

                    // receive request
                DatagramPacket packet = new DatagramPacket(inbuf, inbuf.length);
                System.out.println("Waiting to receive packet");
                socket.receive(packet);

                byte[] outbuf = new byte[256];

                    // figure out response
                Date now = new Date(System.currentTimeMillis());
                String outs = new String(inbuf);
                outs = now.toString() + " " + outs;
                System.out.println("Replying to "+packet.getAddress());
                outbuf = outs.getBytes();

                    // send the response to the client at "address" and "port"
                InetAddress address = packet.getAddress();
                int port = packet.getPort();
                packet = new DatagramPacket(outbuf, outbuf.length, address, port);
                socket.send(packet);
                socket.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

Il server UDP e' identico al client, solo che qui le operazioni di ricevere e inviare sono scambiate.


Applicazione 3: Leggere e scrivere da un URL.S

    file FetchURL.java

import java.net.*;
import java.io.*;

class FetchURL {

   public static void main (String[] args) {

      try {
         URL cernHome = new URL ("http://www.cern.ch");
         URLConnection cernHomeConnection = cernHome.openConnection();

         BufferedReader in = new BufferedReader( new InputStreamReader (
                   cernHomeConnection.getInputStream()));
         String inputLine;

         while ( (inputLine=in.readLine())!=null) {
              System.out.println(inputLine);
         }
         in.close();
      } catch (MalformedURLException e) {
         System.out.println("Malformed URL Exception "+e);
      } catch (IOException e ) {
         System.out.println("IO Exception "+e);
      }
   }
}
Questo e' un client Web (cioe' http) e lo schema da usare e' il seguente:

    file WriteURL.java

import java.io.*;
import java.net.*;

public class WriteURL {
    public static void main(String[] args) throws Exception {

        if (args.length != 1) {
            System.err.println("Usage:  java Reverse string_to_reverse");
            System.exit(1);
        }

        String stringToReverse = URLEncoder.encode(args[0]);

        URL url = new URL("http://java.sun.com/cgi-bin/backwards");
        URLConnection connection = url.openConnection();
        connection.setDoOutput(true);

        PrintWriter out = new PrintWriter(connection.getOutputStream());
        out.println("string=" + stringToReverse);
        out.close();

        BufferedReader in = new BufferedReader(
                                new InputStreamReader(
                                connection.getInputStream()));
        String inputLine;

        while ((inputLine = in.readLine()) != null)
            System.out.println(inputLine);

        in.close();
    }
}
Java imita il meccanismo di invio di dati da un modulo col cosiddetto metodo di POST. L'effetto e' lo stesso che se aveste cliccato sul tasto di invio in questo modulo. (Potete provare questo programma con la lavagna elettronica del corso Java/C++ che ha come indirizzo "http://www.ba.infn.it/zitobin/nph-guestchat.pl" inviando la stringa "comment=commento").
Sul Web esiste la possibilita' di collegare a un link un programma da far eseguire sul computer dove gira il server. Questo programma si chiama cgi script dal nome del protocollo usato(Common Gateway Interface). Questo protocollo prevede 2 metodi per trasferire dati dal Web al programma:
  1. Metodo GET: i dati sono scritti dopo l'url separati dal carattere ?
  2. Metodo POST: i dati sono inviati a parte subito dopo l'invio della URL.
L'implementazione del metodo 1 in Java e' immediato:basta scrivere l'url completa coi dati. Invece per il metodo 2 che comporta l'invio di un file separato, si procede come in questo esempio creando una catena di stream di output e collegandola alla connessione. Tutto cio' che si scrivera' su questa catena, sara' interpretato dal server Web come dati per il programma CGI.


Applicazione 4:Scrivete un programma che legge una lista di URL da un file Ascii e controlla se sono online dando,in caso di problemi, un messaggio di errore.S

    file CheckURL.java


import java.net.*;
import java.io.*;

class CheckURL {
   static BufferedReader myInput;
    static  FileReader myFile;  
   public static void main (String[] args) {

                     try{
     String nomeurl;
     myFile = new  FileReader("list.txt");
     myInput = new BufferedReader(myFile);
          while ( myInput.ready() ){
 
                      nomeurl=myInput.readLine();
      try {
         URL theURL = new URL (nomeurl);
         System.out.println("Testing "+nomeurl);
      try {
         URLConnection con=  theURL.openConnection();
            con.getContent();
            String temp=con.getHeaderField(0);
         System.out.println("Header "+temp);
            int fileNotFound=temp.indexOf("Not Found");
            if ( fileNotFound >=0 )
          System.out.println("***********Bad link***************");
            }catch(IOException e){  
         System.out.println("***********Bad link***************");
            }
      } catch (MalformedURLException e) {
         System.out.println("Malformed URL Exception "+e);
             }
   }
                    }catch(Exception e2){}   
}
}
Questo programma va fatto girare con :
java -Dsun.net.client.defaultReadTimeout=60000 -Dsun.net.client.defaultConnectTimeout=60000 CheckURL
per evitare che rimanga in attesa senza fine su qualcuno degli indirizzi. Qui trovate un spiegazione piu' dettagliata sul controllo degli indirizzi Web.


Applicazione 5: Scrivete un server Web.S

    file TinyHttpd.java

// From Exploring Java, 2nd.Ed., by P. Niemeyer & J. Peck, O'Reilly Pub.
import java.net.*;
import java.io.*;
import java.util.*;

public class TinyHttpd {
  public static void main( String argv[] ) throws IOException {

    // Get the port number from the command line.
    // Create a ServerSocket object to watch that port for clients
    ServerSocket ss = new ServerSocket( Integer.parseInt(argv[0]) );
    System.out.println("starting...");

    // Here we loop indefinitely, just waiting for clients to connect
    while ( true ) {

      // accept() does not return until a client requests a connection
      // Now that a client has arrived, create an instance of our special
      // thread subclass to respond to it.
      new TinyHttpdConnection( ss.accept() ).start();
      System.out.println("new connection");

    } // Now loop back around and wait for next client to be accepted.
  }
}

class TinyHttpdConnection extends Thread {
  Socket client;

   // Pass the socket as a argument to the constructor
  TinyHttpdConnection ( Socket client ) throws SocketException {
    this.client = client;

    // Set the thread priority down so that the ServerSocket
    // will be responsive to new clients.
    setPriority( NORM_PRIORITY - 1 );
  }

  public void run() {
    try {
      // Use the client socket to obtain an input stream from it.
      // For text input we wrap an InputStreamReader around
      // the raw input stream and set ASCII character encoding.
      // Finally, use a BufferReader wrapper to obtain
      // buffering and higher order read methods.
      BufferedReader in = new BufferedReader(
        new InputStreamReader(client.getInputStream(), "8859_1") );

      // Now get an output stream to the client.
      OutputStream out = client.getOutputStream();

      // For text output we wrap an OutputStreamWriter around
      // the raw output stream and set ASCII character encoding.
      // Finally, we use a PrintWriter wrapper to obtain its
      // higher level output methods.
      // Open in append mode.
      PrintWriter pout = new PrintWriter(
        new OutputStreamWriter(out, "8859_1"), true );


      // First read the request line from the client
      String request = in.readLine();
      System.out.println( "Request: "+request );

      // Check if HTTP/1.0 or later. If so, add MIME header
      boolean sendMIME = false;
      if ( request.indexOf("HTTP/") != -1) sendMIME = true;

      // Use aStringTokenizer to examine the request text.
      StringTokenizer st = new StringTokenizer( request );

      // Check that the request has a minimun number of words
      // and that the first word is the GET command.
      if ( (st.countTokens() >= 2) && st.nextToken().equals("GET") ) {

       // Ignore the leading "/" on the file name.
        if ( (request = st.nextToken()).startsWith("/") )
          request = request.substring( 1 );

        // If no file name is there, use index.html default.
        if ( request.endsWith("/") || request.equals("") )
          request = request + "index.html";

        // Check if the file is hypertext or plain text
        String ContentType;
        if (request.endsWith(".html") || request.endsWith(".htm")) {
           ContentType = "text/html";
        }
        else {
          ContentType = "text/plain";
        }
        // Now read the file from the disk and write it to the
        // output stream to the client.
        try {

          // Open a stream to the file. Remember that by this
          // all the text but the file name has been stripped
          // from the "request" string.
          FileInputStream fis = new FileInputStream ( request );

          if( sendMIME) {
            // Follow example in "Java Network Programming", chp.8
            PrintStream os = new PrintStream(out);
            os.print("HTTP/1.0 200 OK\r\n");
            Date now = new Date();
            os.print("Date: " + now + "\r\n");
            os.print("Server: TinyHttpd 1.0\r\n");
            os.print("Content-length: " + fis.available() + "\r\n");
            os.print("Content-type: " + ContentType + "\r\n\r\n");
          }


          // Creat a byte array to hold the file.
          byte [] data = new byte [ fis.available() ];

          fis.read( data );  // Read file into the byte array
          out.write( data ); // Write it to client output stream
          out.flush();       // Remember to flush output buffer
          fis.close();

        } catch ( FileNotFoundException e ) {
          // If no such file, then send the famous 404 message.
          pout.println( "404 Object Not Found" ); }
      } else
        pout.println( "400 Bad Request" );
      client.close();
    } catch ( IOException e ) {
      System.out.println( "I/O error " + e ); }
  // On return from run() the thread process will stop.
  }
}


Un server Web non e' altro che un server TCP come quello dell'Applicazione 1 che risponde a richieste del tipo:
GET /index.html HTTP 1.0
inviando il file richiesto Questo server e' scritto pero' in modo da poter rispondere a piu' richieste contemporaneamente. Per far questo, crea un nuovo Thread per ogni richiesta. L'oggetto Thread provvede a creare il socket per comunicare col cliente, a creare la catena di stream e ad inviare il file attraverso di essa.Quando l'invio del file e' terminato, il socket viene chiuso e il Thread termina l'esecuzione.


Applicazione 6: Scrivete un'applicazione che si collega alla porta 37 per mostrare la data su un computer remoto .S

    file DateAtHost.java

import java.net.*;
import java.io.*;

public class DateAtHost extends java.util.Date {
  static int timePort = 37;
  static final long offset = 2208988800L;   //  Seconds from century to 
                                            //   Jan 1, 1970 00:00 GMT

  public DateAtHost( String host ) throws IOException {
   this( host, timePort );
  }

  public DateAtHost( String host, int port ) throws IOException {

    Socket sock = new Socket( host, port );
    DataInputStream din = new DataInputStream(sock.getInputStream() );
    int time = din.readInt();
    sock.close();
    setTime( (((1L << 32) + time) - offset) * 1000 ) ;
  }

  // Example usage: java DateAtHost helio.ora.com
  public static void main (String [] args) {
    try {
      System.out.println( new DateAtHost( args[0] ) );
    } catch (UnknownHostException e){
      System.out.println("Host " + args[0] +" is unknown."); 
    } catch (IOException e){
      System.out.println("IO Exception"); 
    }
  }
}



Qui abbiamo un Client TCP che si collega alla porta 37 di un computer remoto. Se questo ha un servizio di data funzionante, il programma riceve il suo valore che viene stampato. Ad esempio :
java DateAtHost helio.ora.com
Wed Feb 23 20:13:50 GMT+03:30 2000
(Per trovare dei server che danno il tempo , cerca "time server 37").

Anche il programma di simulazione alla base del corso, si basa su un server TCP che e' in attesa di richieste da parte di client, e ad ogni richiesta invia il risultato dell ultime misure effettuate.


Applicazione 7: Scrivete un'applicazione che simula un'esperimento per il calcolo della costante di gravita' g.L'applicazione ha un'interfaccia grafica che permette di pilotare e seguire la simulazione in corso. Essa e' al tempo stesso un server TPC che puo' inviare gli ultimi dati simulati a un client che ne faccia richiesta.Il client e' un applet che permette di collegarsi al server e di mostrare i dati ricevuti.S
Questa e' l'applicazione che fa da server dei dati sperimentali.Si tratta di un frame che contiene un applet. Quest'ultimo, oltre ad altri oggetti grafici, contiene una canvas e un panel. Nella canvas a sinistra viene mostrata la simulazione dell'esperimento, nel panel a destra un istogramma delle misure fatte.Altri oggetti grafici usati sono il cerchio colorato in rosso e lo stesso istogramma.

L'applet oltre ad avere finestre per dare il nome del computer e l'username, ha una textarea per mostrare lo stato della connessione. Quindi 2 istogrammi che rappresentano i dati in arrivo.
Per far partire il server si da l'istruzione

java -cp Esperimento.jar DataMonitor.DataServer
e la chiamata all'applet client va fatta qui.

Per costruire il programma di simulazione vengono usate 12 classi raggruppate in package. Questa divisione in packages si ottiene con una particolare istruzione package posta all'inizio.Le classi vengono organizzate in cartelle di nome identico ai package dando l'istruzione:
javac -d ./ DataMonitor/*.java FOExperiment/*.java ThirdParty/BarChart/*.java
.Possono anche venir compresse (zippate) in un'unico file che rispetti questa struttura.Questi files Java archive(.jar) sono la maniera migliore di distribuire i programmi Java.
In questo caso,per creare il file jar, si usa il comando:
jar -cvf Esperimento.jar DataMonitor/*.class FOExperiment/*.class ThirdParty/BarChart/*.class
e la chiamata all'applet client va modificata nel seguente modo:
<applet code=DataMonitor.DataClient.class  archive=Esperimento.jar width=600 height=500></applet>

Ecco la struttura dei pacchetti:


Riferendoci all'aspetto grafico dei due programmi che sono alla base del server di dati sperimentali e del client, possiamo capire la funzione delle varie classi che hanno quasi tutte una rappresentazione grafica.
Cominciamo dal server: la classe principale DataServer e' un frame che contiene al suo interno l'applet FallingObject.Questo a sua volta contiene la Canvas:BoxShoot per simulare il fenomeno di misura a sinistra e il Panel G_Detector per mostrare i dati simulati a destra. L'oggetto che cade e' realizzato dall'oggetto Circle che a sua volta eredita una classe astratta objectToDraw. G_Detector contiene al suo interno un oggetto Histogram per rappresentare i dati raccolti.
Invece il client e' l'applet DataClient che contiene al suo interno un oggetto DataChart sopra e un oggetto Histogram sotto.
DataServer e DataClient comunicano tra di loro attraverso 2 Thread DataSender e DataReader. Infine BarChart e NumericSet sono usate da Histogram e DataChart per rappresentare il disegno dei canali e il vettore dei numeri dell'istogramma.
INDIETRO a Imparate Java in una settimana
INDIETRO a Seminario su Java
Maintained by : info@zitogiuseppe.com
Ultimo aggiornamento: