NETWORKING Identificazione di una macchina

148
NETWORKING
Storicamente la programmazione di rete è
considerata complessa e soggetta ad errori. I
programmatori devono conoscere numerosi
dettagli sulle reti e qualche volta anche
sull’hardware. È necessario conoscere i vari
“livelli” delle reti, i diversi protocolli e
numerose funzioni in ogni diversa libreria di
rete riguardanti la connessione, il packing dei
dati, l’handshaking della comunicazione etc…
Tuttavia il concetto di programmazione in rete
non è difficile. È necessario conoscere da quale
computer proviene l’informazione e dove deve
giungere. È simile alla lettura e scrittura di file,
con la differenza che tali file anziché risiedere
sulla stessa macchina locale, possono risiedere
su una macchina remota. Java permette di
programmare in rete in questo modo. È infatti
possibile scambiare informazioni in rete nello
stesso modo in cui si opera sui file.
Identificazione di una macchina
La prima informazione necessaria per
scambio
di
informazioni
in
rete
lo
è
149
l’identificazione delle due macchine
devono essere messe in comunicazione.
che
Java nella gestione della comunicazione si basa
sulle tecniche di comunicazione usate da
Internet e quindi sui protocolli TCP/IP. In tale
ambiente l’identificazione di un calcolatore
avviene tramite due modi:
1. Il D.N.S. che identifica un qualunque
calcolatore collegato in rete con un nome di
dominio (es. java.sun.com)
2. L’indirizzo IP costituito da quattro cifre
(byte) separate da punti (es. 1.2.3.4).
In
entrambi
i
casi
Java
usa
una
rappresentazione interna sotto forma di un
numero a 32 bit incapsulato in un oggetto
InetAddress presente nella lbreria java.net.
Esiste
un
metodo
statico
static
InetAddress.getByName() che permette di
ottenere a partire dal nome di dominio di un
calcolatore il corrispondente indirizzo IP,
interrogando l’ISP (Internet Service Provider),
quando si è connessi ad Internet.
Esempio:
150
Stampare l’indirizzo IP di un calcolatore
connesso in rete TCP/IP, a partire dal suo nome
di dominio.
import java.net.*;
import java.io.*;
public class WhoAmI {
static public void main(String[] args) {
if (args.length != 1) {
System.err.println("Usage: WhoAmI
MachineName");
System.exit(1);
}
try {
InetAddress a=
InetAddress.getByName(args[0]);
System.out.println(a);
} catch (IOException e) {
System.out.println("Error " + e);
}
}
}
COMUNICAZIONE CLIENT/SERVER
I servizi Internet sono organizzati secondo una
architettura client/server. I programmi client
creano collegamenti con i server facendo
opportune richieste e i server rispondono
fornendo al cliente il servizio richiesto.
151
Esempio:
Browser WEB richiede al server WEB un file
HTML ed il server risponde fornendo lo
specifico file.
Client e Server comunicano tramite socket
stabilendo un collegamento. I client creano
socket client ed i server socket server.
Collegamento: sessione di comunicazione in
Internet che usa il TCP/IP.
Socket: punto terminale di una
comunicazione in Internet.
Socke
t
Client
Socke
t
Server
COMUNICAZIONE
TRA PROGRAMMI
Per attivare la comunicazione tra computer è
necessario gestire la comunicazione tra
programmi che risiedono su computer diversi.
Un
indirizzo
IP, infatti,
non
è
abbastanza
per
identificare
un
unico
server, poiché molti server
possono esistere su un’unica macchina. Ogni
macchina IP, contiene diverse Porte di
comunicazione. Una porta è un indirizzo (a 16
152
bit) all’interno di un computer associato ad un
particolare servizio (protocollo) offerto da una
specifica applicazione.
Alcune applicazioni tipiche di Internet hanno
indirizzi di protocollo prefissati.
PORTA
21
23
25
80
PROTOCOLLO
FTP
TELNET
Simple Mail Transfer
Protocol
Hypertext transfer protocol
Un socket è associato ad un indirizzo IP e ad
un indirizzo di porta.
JAVA.NET
È una libreria Java che fornisce le varie classi
che supportano la comunicazione client/server
basata su socket.
InetAddress
Rappresenta gli indirizzi Internet sia nella
forma decimale puntata che nella forma di
nomi di dominio. Fornisce 8 metodi che
operano sugli indirizzi:
getLocalHost()
(statico) restituisce un
oggetto
InetAddress
che
153
rappresenta l’indirizzo
computer locale.
del
getByName() 
(statico) restituisce un
oggetto InetAddress per uno
specifico computer (dato il
nome di dominio).
getAllByName()  (statico) restituisce un
array di tutti gli indirizzi
Internet associati ad un
particolare computer.
byte[] getAddress()  restituisce l’indirizzo
IP
numerico
del
computer identificato
da InetAddress (array
di 4 elementi)
String getHostName()  restituisce il nome
di
dominio
del
computer identificato
da InetAddress.
I metodi di cui sopra possono
l’eccezione UnknownHostException.
generare
154
Esercizio:
Il programa stampa tutti gli indirizzi IP associati ad un
nome di dominio utilizzando i metodi di cui sopra.
import java.net.*;
import java.io.*;
public class WhoAreYou {
static public void main(String[] args) {
try {
if (args.length !=1) {
System.out.println("Usage: java WhoAreYou
HostName");
return;
}
InetAddress host =
InetAddress.getByName(args[0]);
String hostName= host.getHostName();
byte ipAddress[] = host.getAddress();
System.out.println("Host name:"+
hostName);
System.out.print("IP address: ");
for (int i=0;i<ipAddress.length;++i)
System.out.print((ipAddress[i]
+256)%256+".");
System.out.println();
} catch (UnknownHostException ex) {
System.out.println("Unknown host");
return;
} }}
155
Socket
La classe socket implementa socket client
basati su collegamento (circuiti virtuali). Questi
socket
sono
utilizzati
per
sviluppare
applicazioni che utilizzano servizi forniti da
applicazioni server orientate al collegamento.
In Java vengono utilizzati dei socket per creare
la connessione con un’altra macchina e
scambiarsi
dati
utilizzando
le
classi
InputStream ed OutputStream come se la
connessione di rete così stabilita fosse un
semplice flusso di I/O.
La classe Socket possiede diversi costruttori e
metodi:
Socket(String host, int port)  costruttore
che crea un socket collegandolo ad uno
specifico indirizzo IP (nome di host) e ad una
porta.
void close()  metodo che chiude il socket.
InputStream getInputStream()  prende
dal socket il flusso di input da leggere.
OutputStream
getOutputStream()

prende un flusso di output da scrivere sul
socket.
156
getInetAddress(), getPort()  restituiscono
l’indirizzo IP e l’indirizzo di porta del computer
di destinazione al quale il socket è collegato.
getLocalPort()  restituisce l’indirizzo di
porta locale del computer sorgente associato
con il socket.
157
Esempio (Socket Client):
Il programma si connette ad un server e
semplicemente invia e legge linee di testo.
import java.net.*;
import java.io.*;
public class ClientSok {
static public void main(String[] args)
throws IOException {
//Passando null a getByName() si genera l'indirizzo locale
// valido anche in assenza di collegamento in rete
InetAddress addr=
InetAddress.getByName(null);
// Alternativamente si sarebbe potuto assegnare
direttamente:
//InetAddress addr= new
InetAddress.getByName("127.0.0.1");
// oppure:
//InetAddress addr= new
InetAddress.getByName("localhost");
System.out.println("addr: "+addr);
Socket socket= new Socket(addr,8080);
// si usa un try..finally per essere sicuri che qualunque cosa
// accada alla fine il socket vega chiuso
try {
System.out.println("socket = "+ socket);
InputStreamReader ir = new
InputStreamReader(socket.getInputStream());
158
BufferedReader in=new BufferedReader(ir);
// Il secondo parametro al costruttore di Printstream fa in
// modo che il flush venga eseguito automaticamente
PrintStream out = new
PrintStream(socket.getOutputStream(),tru
e);
for (int i=0; i<10; i++) {
out.println("Packet "+i);
String str= in.readLine();
System.out.println(str);
// Non è necessario out.flush(); per il motivo di cui sopra
}
out.println("END");
} finally {
System.out.println("closing...");
socket.close();
}
}
}
159
SocketServer
La classe ServerSocket implementa un socket
server TCP. Fornisce due costruttori che
specificano la porta sulla quale il server resta in
attesa delle richieste del client (è chiaro che
non serve specificare l’indirizzo IP, dato che il
server agisce sulla macchina locale). Un
parametro opzionale (count) può essere
specificato, per fornire il tempo di attesa del
collegamento in arrivo da parte del socket.
I metodi utilizzati sono i seguenti:
Socket accept()  serve per fare in modo
che il server attenda finchè il collegamento non
sia stabilito, restituisce un oggetto di tipo
Socket.
InetAddress getInetAddress()  restiruisce
l’indirizzo del computer al quale il socket è
collegato.
int getLocalPort()  restituisce il numero di
porta locale alla quale il server resta in attesa
del collegamento in arrivo.
void close()  metodo che chiude il socket.
Esempio (Socket Server):
160
Il
programma
crea
un
Server
che
semplicemente effettua l’eco di ciò che il client
gli invia.
import java.net.*;
import java.io.*;
// Scegliere una porta al di fuori dell'intervallo 1..1024
public class ServerSok {
public static final int PORT = 8080;
static public void main(String[] args)
throws IOException
{
ServerSocket s= new ServerSocket(PORT);
System.out.println("Started: " + s);
try {
Socket socket= s.accept();
try {
System.out.println("Connection
accepted: "+
socket);
InputStreamReader i = new
InputStreamReader
(socket.getInputStream());
BufferedReader in=new BufferedReader(i);
PrintStream out= new
PrintStream (socket.getOutputStream());
while (true) {
String str= in.readLine();
if (str.equals("END")) break;
161
System.out.println("Echoing: "+ str);
out.println(str);
out.flush();
}
} finally {
//chiude comunque i due socket...
System.out.println("closing...");
socket.close();
}
} finally { s.close(); }
}
}
162
Esempio:
Realizza un server in grado di servire le
richieste di più client in contemporanea.
import java.net.*;
import java.io.*;
public class MultiServerSok extends Thread
{
private Socket socket;
private InputStreamReader in;
private PrintStream out;
public MultiServerSok(Socket s) throws
IOException {
socket = s;
in = new
InputStreamReader(socket.getInputStream());
// Abilita autoflush
out= new
PrintStream(socket.getOutputStream(),
true);
// se qualcuna delle ulteriori chiamate genera
un'eccezione,
// il chiamante è responsabile per la chiusura del socket,
// altrimenti lo stesso thread lo chiuderà.
start(); //Chiama run()
}
163
public void run() {
try{
while (true) {
String str= in.readLine();
if (str.equals("END")) break;
System.out.println("Echoing: "+ str);
out.println(str);
}
System.out.println("closing...");
} catch (IOException ex) {}
finally {
try {
socket.close();
} catch (IOException ex) {}
}
}
}
public class MultiSock {
public static final int PORT = 8080;
static public void main(String[] args)
throws
IOException
{
ServerSocket s= new ServerSocket(PORT);
System.out.println("Server Started");
try {
while (true) {
Socket socket= s.accept();
try {
164
new MultiServerSok(socket);
} catch (IOException ex) {
// Se fallisce chiude il socket, altrimenti lo chiuderà il
thread.
socket.close();
}
}
} finally {s.close(); }
}
}
165
DATAGRAM
Le classi Socket e ServerSoket sono state
utilizzate per realizzare una comunicazione che
si basa sul protocollo TCP. Questo protocollo è
stato progettato per garantire l’affidabilità della
comunicazione. Esso infatti si preoccupa di
effettuare la ritrasmissione (automaticamente)
dei dati eventualmente perduti, prevede diversi
percosi per i dati nel caso in cui uno di essi non
funzionasse più, garantisce che i byte spediti
arrivino nello stesso ordine in cui sono stati
mandati. Tutti questi controlli però hanno un
costo in termini di rallentamento delle
prestazioni dei calcolatori predisposti ad
effettuarli.
Esiste un secondo protocollo chiamato UDP
(User Datagram Protocol) che non offre
l’affidabilità del TCP, ma che risulta molto
efficiente soprattutto quando per i dati che si
vogliono spedire è richiesta più la velocità nella
spedizione che la garanzia che tutti i pacchetti
spediti arrivino correttamente.
Java
prevede
per
implementare
la
comunicazione tramite UDP, due classi:
DatagramSocket e DatagramPachet.
166
DatagramSocket
La classe in oggetto prevede due costruttori
che servono per creare stabilire la connessione
sia dal lato del server che da quello del client.
In questo caso non si tratta di connessioni TCP,
infatti ad un server sulla stessa porta possono
arrivare più pacchetti mescolati insieme relativi
a diverse comunicazioni con client diversi. Per
questo motivo i dati inviati dovranno contenere
le informazioni relative al mittente e
destinatario della comunicazione oltre che
all’oggetto della comunicazione stessa.
Il costruttore predefinito per il server prevede
di specificare una porta di comunicazione sulla
quale il server stesso rimane in attesa di
pacchetti.
Il costruttore predefinito per il client non
prevede di specificare alcuna porta (la porta a
cui è destinato insieme all’indirizzo IP del
destinatario
devono
essere
specificati
all’interno del pacchetto).
DatagramPacket
Il pacchetto o datagramma spedito dovrà
contenere
le
informazioni
relative
al
destinatario, cioè indirizzo IP e porta di
comunicazione. Queste informazioni devono
167
essere esplicitamente inserite all’interno del
pacchetto nel modo seguente:
String dati = ”prova dati da inviare…”;
byte pck[] = new byte[256];
dati.getBytes(0,dati.length(),pck,0);1
DatagramPacket dgram= new
DatagramPacket(pck,256,destAddress,destP
ort);
L’indirizzo IP e il numero di porta del mittente
verranno automaticamente inseriti all’interno
del pacchetto da JAVA.
Il destinatario del datagramma lo riceverà in un
buffer di byte così definito:
byte pck[] = new byte[bufLen];
DatagramPacket dgram= new
DatagramPacket(pck,bufLen);
Da questo buffer potrà estrarre le informazioni
relative all’indirizzo IP e alla porta del mittente
tramite i metodi getAddress() e getPort().
1
Il metodo getBytes applicato ad una stringa prevede 4
parametri che indicano rispettivamente la posizione iniziale
e finale della stringa da copiare in un array di byte, il nome
dell’array di byte in cui copiare, la posizione iniziale
dell’array di byte a partire dalla quale copiare la stringa.
168
Per inviare e ricevere datagrammi si
utilizzeranno i metodi send() e receive() della
classe DatagramSocket che prevede come
unico
parametro
un
pacchetto
dati
appartenente alla classe DatagramPacket.
DatagramSocket
DatagramSocket();
socket=
new
DatagramPacket dp= new
DatagramPacket(buffer,buffer.legt
h());
socket.send(dp);
socket.receive(dp);
169
Esempio:
Un programma server effettua il servizio di
inviare l’ora e la data corrente a tutti i client
che ne facciano richiesta.
import java.net.*;
import java.io.*;
import java.util.*;
public class TimeServer {
public static final int PORT = 1711;
private byte[] buf= new byte[1000];
private DatagramPacket dgram = new
DatagramPacket(buf,
buf.length);
private DatagramSocket sok;
public TimeServer(){
try {
sok = new DatagramSocket(PORT);
System.out.println("Server started");
while(true) {
sok.receive(dgram);
String s = new
String(dgram.getData(),0,0,dgram.getLength()).tri
m();
s+= " from address "+dgram.getAddress()+
", port: "+ dgram.getPort();
System.out.println(s);
170
String time_s = new Date().toString();
time_s.getBytes(0,time_s.length(),buf,0);
DatagramPacket echo = new
DatagramPacket(buf,buf.length,dgram.getAddres
s(),
dgram.getPort());
sok.send(echo);
}
} catch (SocketException e){
System.out.println("Non riesco ad aprire il
Socket");
System.exit(1);
}
catch (IOException e) {
System.out.println("Errore di
comunicazione");
System.exit(1);
}
}
static public void main(String[] args) {
new TimeServer();
}
}
171
Esempio
Il programma seguente realizza 5 client che
effettuano richieste al server dell’esempio
precedente.
import java.net.*;
import java.io.*;
public class TimeClient {
private DatagramSocket sok;
private DatagramPacket dp;
private byte packetBuf[];
public TimeClient() {
try{
sok = new DatagramSocket();
InetAddress
addr=InetAddress.getByName(null);
String localHost=addr.getHostName();
for (int i=0;i<=5;i++)
{
packetBuf=new byte[256];
"time".getBytes(0,4,packetBuf,0);
dp= new
DatagramPacket(packetBuf,256,addr,TimeServer.PO
RT);
sok.send(dp);
172
System.out.println("Ho inviato una richiesta di
'time' a "+ localHost+ " alla porta "+
TimeServer.PORT);
sok.receive(dp);
InetAddress rc_addr=dp.getAddress();
int destPort= dp.getPort();
String destHost=rc_addr.getHostName().trim();
String data=new
String(dp.getData(),0).trim();
System.out.println("Ho ricevuto un datagram
da "+
destHost+ " alla porta "+
destPort);
System.out.println("Esso conteneva i seguenti
dati:“
+data);
System.out.println("******");
}
} catch (IOException e) {
System.out.println("Errore di IO");
System.exit(1);
}
}
static public void main(String[] args) {
TimeClient timeClient = new TimeClient();
}
}