Skip to content

8. Programmazione TCP-IP

8.1. Informazioni generali

8.1.1. I protocolli di Internet

In questa sede forniamo un'introduzione ai protocolli di comunicazione di Internet, noti anche come suite di protocolli TCP/IP (Transfer Control Protocol / Internet Protocol), dal nome dei due protocolli principali. È opportuno che il lettore abbia una comprensione generale del funzionamento delle reti e, in particolare, dei protocolli TCP/IP prima di affrontare la realizzazione di applicazioni distribuite.

Il testo che segue è una traduzione parziale di un testo tratto dal documento "Lan Workplace for Dos - Administrator's Guide" di NOVELL, un documento risalente agli inizi degli anni '90.


Il concetto generale di creazione di una rete di computer eterogenei deriva dalle ricerche condotte dalla DARPA (Defense Advanced Research Projects Agency) negli Stati Uniti. La DARPA ha sviluppato la suite di protocolli nota come TCP/IP, che consente a macchine eterogenee di comunicare tra loro. Questi protocolli sono stati testati su una rete denominata ARPAnet, rete che in seguito è diventata la rete INTERNET. I protocolli TCP/IP definiscono formati e regole di trasmissione e ricezione indipendenti dall’organizzazione delle reti e dall’hardware utilizzato.

La rete progettata dal DARPA e gestita dai protocolli TCP/IP è una rete a commutazione di pacchetti. Una rete di questo tipo trasmette le informazioni sulla rete in piccoli frammenti chiamati pacchetti. Pertanto, se un computer trasmette un file di grandi dimensioni, quest’ultimo verrà suddiviso in piccoli frammenti che saranno inviati sulla rete per essere ricomposti a destinazione. TCP/IP definisce il formato di questi pacchetti, ovvero:

  • origine del pacchetto
  • destinazione
  • lunghezza
  • tipo

8.1.2. Il modello OSI

I protocolli TCP/IP seguono approssimativamente il modello di rete aperta denominato OSI (Open Systems Interconnection Reference Model) definito dall'ISO (International Standards Organisation). Questo modello descrive una rete ideale in cui la comunicazione tra macchine può essere rappresentata da un modello a sette livelli:

Image

Ogni livello riceve servizi dal livello sottostante e fornisce i propri al livello superiore. Supponiamo che due applicazioni situate su macchine diverse, A e B, vogliano comunicare: lo fanno a livello del livello Application. Non hanno bisogno di conoscere tutti i dettagli del funzionamento della rete: ogni applicazione trasmette le informazioni che desidera inviare al livello sottostante, ovvero il livello Présentation. L’applicazione deve quindi conoscere solo le regole di interfaccia con il livello Présentation.

Una volta che l'informazione si trova nel livello Présentation, viene trasferita, secondo altre regole, al livello Session e così via, fino a quando l'informazione non raggiunge il supporto fisico e viene trasmessa fisicamente al dispositivo di destinazione. A quel punto, subirà il processo inverso rispetto a quello a cui è stata sottoposta sul computer mittente.

Ad ogni livello, il processo mittente incaricato di inviare le informazioni le invia a un processo ricevente sull’altra macchina appartenente allo stesso livello. Lo fa secondo determinate regole che vengono definite protocollo di livello. Si ottiene quindi il seguente schema di comunicazione finale:

Image

Il ruolo dei diversi livelli è il seguente:

Physique
Garantisce la trasmissione dei bit su un supporto fisico. In questo livello si trovano apparecchiature terminali per l’elaborazione dei dati (E.T.T.D), quali terminali o computer, nonché apparecchiature di terminazione dei circuiti dati (E.T.C.D), quali modulatori/demodulatori, multiplexer e concentratori. I punti di interesse a questo livello sono:
. la scelta della codifica delle informazioni (analogica o digitale)
. la scelta della modalità di trasmissione (sincrona o asincrona).
Liaison de données
Nasconde le caratteristiche fisiche del livello fisico. Rileva e corregge gli errori di trasmissione.
Réseau
Gestisce il percorso che devono seguire le informazioni inviate sulla rete. Questo processo è denominato routage: determinare il percorso che un’informazione deve seguire per arrivare al destinatario.
Transport
Consente la comunicazione tra due applicazioni, mentre i livelli precedenti permettevano solo la comunicazione tra macchine. Un servizio fornito da questo livello può essere il multiplexing: il livello di trasporto potrà utilizzare una stessa connessione di rete (da macchina a macchina) per trasmettere informazioni appartenenti a più applicazioni.
Session
In questo livello si trovano servizi che consentono a un’applicazione di aprire e mantenere una sessione di lavoro su una macchina remota.
Présentation
Il suo scopo è uniformare la rappresentazione dei dati sulle diverse macchine. Pertanto, i dati provenienti da una macchina A saranno “formattati” dal livello Présentation della macchina A secondo un formato standard prima di essere inviati sulla rete. Una volta giunti al livello Présentation del computer destinatario B, che li riconoscerà grazie al loro formato standard, verranno rielaborati in modo diverso affinché l’applicazione del computer B possa riconoscerli.
Application
A questo livello si trovano le applicazioni generalmente vicine all’utente, come la posta elettronica o il trasferimento di file.

8.1.3. Il modello TCP/IP

Il modello OSI è un modello ideale mai realizzato. La suite di protocolli TCP/IP se ne avvicina nella forma seguente:

Image

Livello fisico

Nelle reti locali si utilizzano generalmente le tecnologie Ethernet o Token-Ring. In questa sede ci limiteremo a presentare la tecnologia Ethernet.

Ethernet

È il nome dato a una tecnologia di reti locali a commutazione di pacchetti inventata da PARC Xerox all’inizio degli anni ’70 e standardizzata da Xerox, Intel e Digital Equipment nel 1978. La rete è costituita fisicamente da un cavo coassiale di circa 1,27 cm di diametro e di lunghezza non superiore a 500 m. Può essere estesa tramite répéteurs, ma due dispositivi non possono essere separati da più di due ripetitori. Il cavo è passivo: tutti i componenti attivi si trovano sulle apparecchiature collegate al cavo. Ciascuna apparecchiatura è collegata al cavo tramite una scheda di accesso alla rete che comprende:

  • un trasmettitore (transceiver) che rileva la presenza di segnali sul cavo e converte i segnali analogici in segnali digitali e viceversa.
  • un accoppiatore che riceve i segnali digitali dal trasmettitore e li trasmette al computer per l’elaborazione o viceversa.

Le caratteristiche principali della tecnologia Ethernet sono le seguenti:

  • Capacità di 10 megabit al secondo.
  • Topologia a bus: tutte le macchine sono collegate allo stesso cavo

Image

  • Rete broadcast: un dispositivo che trasmette invia le informazioni sul cavo indicando l’indirizzo del dispositivo destinatario. Tutti i dispositivi collegati ricevono quindi queste informazioni e solo quello a cui sono destinate le conserva.
  • Il metodo di accesso è il seguente: il trasmettitore che desidera trasmettere ascolta il cavo e rileva così la presenza o meno di un'onda portante, la cui presenza indicherebbe che è in corso una trasmissione. Si tratta della tecnica CSMA (Carrier Sense Multiple Access). In assenza di portante, un trasmettitore può decidere di trasmettere a sua volta. Possono essere diversi i trasmettitori a prendere questa decisione. I segnali emessi si sovrappongono: si parla allora di collisione. Il trasmettitore rileva questa situazione: mentre trasmette sul cavo, ascolta ciò che effettivamente vi transita. Se rileva che l’informazione che transita sul cavo non è quella che ha trasmesso, ne deduce che vi è una collisione e interromperà la trasmissione. Gli altri trasmettitori che stavano trasmettendo faranno lo stesso. Ciascuno riprenderà la propria trasmissione dopo un intervallo di tempo casuale che dipende da ciascun trasmettitore. Questa tecnica è denominata CD (Collision Detect). Il metodo di accesso è quindi denominato CSMA/CD.
  • un indirizzamento a 48 bit. Ogni macchina ha un indirizzo, qui denominato indirizzo fisico, che è riportato sulla scheda che la collega al cavo. Questo indirizzo è denominato indirizzo Ethernet della macchina.

Livello di rete

A questo livello troviamo i protocolli IP, ICMP, ARP e RARP.

IP (Internet Protocol)
Trasmette pacchetti tra due nodi della rete
ICMP
(Internet Control Message Protocol)
ICMP gestisce la comunicazione tra il programma del protocollo IP di una macchina e quello di un'altra macchina. Si tratta quindi di un protocollo di scambio di messaggi all'interno dello stesso protocollo IP.
ARP
(Address Resolution Protocol)
effettua la corrispondenza tra l'indirizzo Internet della macchina e l'indirizzo fisico della macchina
RARP
(Reverse Address Resolution Protocol)
effettua la corrispondenza tra l'indirizzo fisico del computer e l'indirizzo Internet del computer

Livelli di trasporto/sessione

In questo livello si trovano i seguenti protocolli:

TCP (Transmission Control Protocol)
Garantisce una trasmissione affidabile delle informazioni tra due client
UDP (User Datagram Protocol)
Garantisce una trasmissione non affidabile di informazioni tra due client

Livelli Applicazione/Presentazione/Sessione

Qui si trovano diversi protocolli:

TELNET
Emulatore di terminale che consente a una macchina A di connettersi a una macchina B come terminale
FTP (File Transfer Protocol)
consente il trasferimento di file
TFTP (Trivial File Transfer Protocol)
consente il trasferimento di file
SMTP (Simple Mail Transfer protocol)
consente lo scambio di messaggi tra gli utenti della rete
DNS (Domain Name System)
converte un nome di computer in un indirizzo Internet del computer
XDR (eXternal Data Representation)
creato da Sun MicroSystems, specifica una rappresentazione standard dei dati, indipendente dal tipo di macchina
RPC(Remote Procedures Call)
definito anch’esso da Sun, è un protocollo di comunicazione tra applicazioni remote, indipendente dal livello di trasporto. Questo protocollo è importante: solleva il programmatore dalla necessità di conoscere i dettagli del livello di trasporto e rende le applicazioni portabili. Questo protocollo si basa sul protocollo XDR
NFS (Network File System)
, anch’esso definito da Sun; questo protocollo consente a una macchina di “vedere” il sistema di file di un’altra macchina. Si basa sul precedente protocollo RPC

8.1.4. Funzionamento dei protocolli Internet

Le applicazioni sviluppate nell’ambiente TCP/IP utilizzano generalmente diversi protocolli di tale ambiente. Un programma applicativo comunica con il livello più alto dei protocolli. Quest’ultimo trasmette le informazioni al livello sottostante e così via fino a raggiungere il supporto fisico. A quel punto, le informazioni vengono trasferite fisicamente al dispositivo destinatario, dove attraverseranno nuovamente gli stessi livelli, questa volta in senso inverso, fino a raggiungere l’applicazione destinataria delle informazioni inviate. Lo schema seguente illustra il percorso delle informazioni:

Image

Facciamo un esempio: l’applicazione FTP, definita a livello del livello Application e che consente il trasferimento di file tra macchine.

  • L’applicazione fornisce una sequenza di byte da trasmettere al livello transport.
  • Il livello transport suddivide questa sequenza di byte in segments e TCP, aggiungendo all’inizio di ciascun segmento il numero che lo identifica. I segmenti vengono passati al livello di rete gestito dal protocollo IP.
  • Il livello IP crea un pacchetto che incapsula il segmento TCP ricevuto. All’inizio di questo pacchetto, inserisce gli indirizzi Internet del computer di origine e di destinazione. Determina inoltre l’indirizzo fisico del computer di destinazione. Il tutto viene trasmesso al livello di collegamento dati e collegamento fisico, ovvero alla scheda di rete che collega il computer alla rete fisica.
  • A quel punto, il pacchetto IP viene a sua volta incapsulato in una trama fisica e inviato al destinatario tramite il cavo.
  • Sul computer destinatario, il livello di collegamento dati e collegamento fisico esegue l’operazione inversa: incapsula il pacchetto IP dalla trama fisica e lo passa al livello IP.
  • Il livello IP verifica che il pacchetto sia corretto: calcola una somma, in base ai bit ricevuti (checksum), somma che deve ritrovare nell’intestazione del pacchetto. In caso contrario, il pacchetto viene scartato.
  • Se il pacchetto viene dichiarato corretto, il livello IP decapsula il segmento TCP in esso contenuto e lo trasmette al livello superiore transport.
  • Il livello transport, ovvero il livello TCP nel nostro esempio, esamina il numero del segmento per ripristinare il corretto ordine dei segmenti.
  • Calcola inoltre un checksum per il segmento TCP. Se risulta corretto, il livello TCP invia una conferma di ricezione alla macchina di origine; in caso contrario, il segmento TCP viene rifiutato.
  • Al livello TCP non resta che trasmettere la parte dati del segmento all’applicazione destinataria di tali dati nel livello superiore.

8.1.5. I problemi di indirizzamento in Internet

Un nodo di una rete può essere un computer, una stampante intelligente, un server di file, in pratica qualsiasi dispositivo in grado di comunicare tramite i protocolli TCP/IP. Ogni nodo ha un indirizzo fisico il cui formato dipende dal tipo di rete. Su una rete Ethernet, l’indirizzo fisico è codificato su 6 byte. Un indirizzo di una rete X25 è un numero di 14 cifre.

L’indirizzo Internet di un nodo è un indirizzo logico: è indipendente dall’hardware e dalla rete utilizzata. Si tratta di un indirizzo di 4 byte che identifica sia una rete locale sia un nodo di tale rete. L’indirizzo Internet è solitamente rappresentato sotto forma di 4 numeri, corrispondenti ai valori dei 4 byte, separati da un punto. Pertanto, l’indirizzo del computer Lagaffe della Facoltà di Scienze di Angers è 193.49.144.1 e quello del computer Liny è 193.49.144.9. Ne consegue che l’indirizzo Internet della rete locale è 193.49.144.0. Su questa rete possono esserci fino a 254 nodi.

Poiché gli indirizzi Internet o gli indirizzi IP sono indipendenti dalla rete, un computer di una rete A può comunicare con un computer di una rete B senza preoccuparsi del tipo di rete su cui si trova: è sufficiente che conosca il proprio indirizzo IP. Il protocollo IP di ciascuna rete si occupa di effettuare la conversione tra indirizzo IP e indirizzo fisico, in entrambe le direzioni.

Gli indirizzi IP devono essere tutti diversi. La loro distribuzione è affidata a organismi ufficiali. Infatti, questi organismi assegnano un indirizzo alle reti locali, ad esempio 193.49.144.0 per la rete della Facoltà di Scienze di Angers. L’amministratore di questa rete può quindi assegnare gli indirizzi IP da 193.49.144.1 a 193.49.144.254 a sua discrezione. Questo indirizzo è generalmente registrato in un file specifico su ogni macchina collegata alla rete.

8.1.5.1. Le classi di indirizzi IP

Un indirizzo IP è una sequenza di 4 byte, spesso indicata come I1.I2.I3.I4, che contiene in realtà due indirizzi:

  • l'indirizzo della rete
  • l'indirizzo di un nodo di tale rete

A seconda della dimensione di questi due campi, gli indirizzi IP si dividono in 3 classi: classi A, B e C.

Classe A

L'indirizzo IP: I1.I2.I3.I4 ha la forma R1.N1.N2.N3 dove

R1 è l'indirizzo di rete

N1.N2.N3 è l'indirizzo di un computer in tale rete

Più precisamente, la forma di un indirizzo di classe A IP è la seguente:

Image

L'indirizzo di rete è a 7 bit e l'indirizzo del nodo a 24 bit. Si possono quindi avere 127 reti di classe A, ciascuna delle quali può contenere fino a 224 nodi.

Classe B

In questo caso, l’indirizzo IP: I1.I2.I3.I4 ha la forma R1.R2.N1.N2 dove

R1.R2 è l’indirizzo della rete

N1.N2 è l'indirizzo di un computer in questa rete

Più precisamente, la forma di un indirizzo di classe B IP è la seguente:

Image

L'indirizzo di rete è composto da 2 byte (14 bit esatti), così come quello del nodo. Si possono quindi avere 2¹⁴ reti di classe B, ciascuna delle quali può contenere fino a 2¹⁶ nodi.

Classe C

In questa classe, l’indirizzo IP: I1.I2.I3.I4 ha la forma R1.R2.R3.N1 dove

R1.R2.R3 è l’indirizzo della rete

N1 è l'indirizzo di un computer in questa rete

Più precisamente, la forma di un indirizzo di classe C IP è la seguente:

Image

L'indirizzo di rete è composto da 3 byte (meno 3 bit) e l'indirizzo del nodo da 1 byte. Si possono quindi avere 221 reti di classe C contenenti fino a 256 nodi.

Poiché l’indirizzo del computer Lagaffe della Facoltà di Scienze di Angers è 193.49.144.1, si nota che l’occhetto di peso maggiore è pari a 193, ovvero 11000001 in binario. Ne consegue che la rete è di classe C.

Indirizzi riservati

. Alcuni indirizzi IP sono indirizzi di rete piuttosto che indirizzi di nodi all’interno della rete. Si tratta di quelli in cui l'indirizzo del nodo è impostato a 0. Pertanto, l'indirizzo 193.49.144.0 è l'indirizzo IP della rete della Facoltà di Scienze di Angers. Di conseguenza, nessun nodo di una rete può avere l'indirizzo zero.

. Quando in un indirizzo IP l'indirizzo del nodo è composto esclusivamente da 1, si tratta di un indirizzo di broadcast: tale indirizzo indica tutti i nodi della rete.

. In una rete di classe C, che teoricamente consente 2⁸ = 256 nodi, se si escludono i due indirizzi vietati, rimangono solo 254 indirizzi consentiti.

8.1.5.2. I protocolli di conversione Indirizzo Internet <--> Indirizzo fisico

Abbiamo visto che durante la trasmissione di informazioni da una macchina all’altra, queste, attraversando il livello IP, venivano incapsulate in pacchetti. Questi hanno la seguente forma:

Image

Il pacchetto IP contiene quindi gli indirizzi Internet del computer di origine e di destinazione. Quando questo pacchetto viene trasmesso al livello incaricato di inviarlo sulla rete fisica, vengono aggiunte altre informazioni per formare la trama fisica che verrà infine inviata sulla rete. Ad esempio, il formato di una trama su una rete Ethernet è il seguente:

Image

Nella trama finale sono presenti gli indirizzi fisici dei computer di origine e di destinazione. Come vengono ottenuti?

Il dispositivo mittente, conoscendo l’indirizzo IP del dispositivo con cui desidera comunicare, ottiene l’indirizzo fisico di quest’ultimo utilizzando un protocollo specifico chiamato ARP (Address Resolution Protocol).

  • Invia un pacchetto di un tipo speciale denominato pacchetto ARP contenente l’indirizzo IP del computer di cui si sta cercando l’indirizzo fisico. Ha inoltre provveduto a inserirvi il proprio indirizzo IP e il proprio indirizzo fisico.
  • Questo pacchetto viene inviato a tutti i nodi della rete.
  • Questi ultimi riconoscono la natura speciale del pacchetto. Il nodo che riconosce il proprio indirizzo IP nel pacchetto risponde inviando al mittente del pacchetto il proprio indirizzo fisico. Come è possibile? Ha trovato nel pacchetto gli indirizzi IP e l’indirizzo fisico del mittente.
  • Il mittente riceve quindi l’indirizzo fisico che stava cercando. Lo memorizza in modo da poterlo utilizzare in seguito, qualora debbano essere inviati altri pacchetti allo stesso destinatario.

L’indirizzo IP di una macchina è normalmente registrato in uno dei suoi file, che può quindi consultare per conoscerlo. Questo indirizzo può essere modificato: basta modificare il file. L’indirizzo fisico, invece, è registrato nella memoria della scheda di rete e non può essere modificato.

Quando un amministratore desidera riorganizzare la propria rete, può trovarsi nella necessità di modificare gli indirizzi IP di tutti i nodi e quindi di modificare i diversi file di configurazione dei vari nodi. Ciò può risultare noioso e fonte di errori se il numero di macchine è elevato. Un metodo consiste nel non assegnare alcun indirizzo IP alle macchine: si inserisce quindi un codice speciale nel file in cui la macchina dovrebbe trovare il proprio indirizzo IP. Scoprendo di non avere un indirizzo IP, la macchina lo richiede secondo un protocollo denominato RARP (Reverse Address Resolution Protocol). Invia quindi sulla rete un pacchetto speciale chiamato pacchetto RARP, analogo al precedente pacchetto ARP, nel quale inserisce il proprio indirizzo fisico. Questo pacchetto viene inviato a tutti i nodi, che riconoscono quindi un pacchetto RARP. Uno di essi, denominato server RARP, possiede un file che riporta la corrispondenza tra indirizzo fisico e indirizzo IP di tutti i nodi. Risponde quindi al mittente del pacchetto RARP, rinviandogli il suo indirizzo IP. Un amministratore che desideri riconfigurare la propria rete deve quindi semplicemente modificare il file di corrispondenze del server RARP. Quest’ultimo deve normalmente avere un indirizzo fisso IP che deve poter conoscere senza dover utilizzare lui stesso il protocollo RARP.

8.1.6. Il livello di rete denominato livello IP di Internet

Il protocollo IP (Internet Protocol) definisce la forma che i pacchetti devono assumere e il modo in cui devono essere gestiti durante la loro trasmissione o ricezione. Questo particolare tipo di pacchetto è chiamato datagramma IP. Lo abbiamo già presentato:

Image

L’aspetto importante è che, oltre ai dati da trasmettere, il datagramma IP contiene gli indirizzi Internet del computer di origine e di destinazione. In questo modo il computer destinatario sa chi gli sta inviando un messaggio.

A differenza di una trama di rete, la cui lunghezza è determinata dalle caratteristiche fisiche della rete su cui transita, la lunghezza del datagramma IP è fissata dal software e sarà quindi la stessa su reti fisiche diverse. Abbiamo visto che, scendendo dal livello di rete al livello fisico, il datagramma IP veniva incapsulato in una trama fisica. Abbiamo fornito l’esempio della trama fisica di una rete Ethernet:

Image

Le frame fisiche circolano da nodo a nodo verso la loro destinazione, che potrebbe non trovarsi sulla stessa rete fisica del dispositivo mittente. Il pacchetto IP può quindi essere incapsulato in successione in diverse trame fisiche a livello dei nodi che fungono da collegamento tra due reti di tipo diverso. È anche possibile che il pacchetto IP sia troppo grande per essere incapsulato in una trama fisica. Il software IP del nodo in cui si verifica questo problema scompone quindi il pacchetto IP in fragments secondo regole precise; ciascuno di essi viene poi inviato sulla rete fisica. Verranno ricomposti solo alla loro destinazione finale.

8.1.6.1. Il routing

L’instradamento è il metodo utilizzato per convogliare i pacchetti IP verso la loro destinazione. Esistono due metodi: l’instradamento diretto e l’instradamento indiretto.

Routing diretto

Il routing diretto indica l’inoltro di un pacchetto IP direttamente dal mittente al destinatario all’interno della stessa rete:

  • Il computer mittente di un datagramma IP dispone dell’indirizzo IP del destinatario.
  • Ottiene l'indirizzo fisico di quest'ultimo tramite il protocollo ARP o dalle proprie tabelle, se tale indirizzo è già stato acquisito.
  • Invia il pacchetto sulla rete a tale indirizzo fisico.

Routing indiretto

Per instradamento indiretto si intende l'inoltro di un pacchetto IP verso una destinazione situata su una rete diversa da quella a cui appartiene il mittente. In questo caso, le parti relative all'indirizzo di rete degli indirizzi IP dei computer di origine e di destinazione sono diverse. Il computer di origine ne prende atto. Invia quindi il pacchetto a un nodo speciale chiamato router (router), nodo che collega una rete locale alle altre reti e di cui trova l’indirizzo IP nelle proprie tabelle, indirizzo ottenuto inizialmente da un file, da una memoria permanente o tramite informazioni che circolano sulla rete.

Un router è collegato a due reti e possiede un indirizzo IP all’interno di queste due reti.

Image

Nel nostro esempio sopra riportato:

  • La rete n. 1 ha l’indirizzo Internet 193.49.144.0 e la rete n. 2 l’indirizzo 193.49.145.0.
  • All’interno della rete n. 1, il router ha l’indirizzo 193.49.144.6 e l’indirizzo 193.49.145.3 all’interno della rete n. 2.

Il router ha il compito di convertire il pacchetto IP che riceve, contenuto in una trama fisica tipica della rete n. 1, in una trama fisica in grado di circolare sulla rete n. 2. Se l’indirizzo IP del destinatario del pacchetto si trova nella rete n. 2, il router gli invierà il pacchetto direttamente; in caso contrario, lo invierà a un altro router, collegando la rete n. 2 a una rete n. 3 e così via.

8.1.6.2. Messaggi di errore e di controllo

Sempre nel livello di rete, quindi allo stesso livello del protocollo IP, esiste il protocollo ICMP (Internet Control Message Protocol). Serve a inviare messaggi sul funzionamento interno della rete: nodi in avaria, congestione a livello di un router, ecc... I messaggi ICMP vengono incapsulati in pacchetti IP e inviati sulla rete. I livelli IP dei diversi nodi intraprendono le azioni appropriate in base ai messaggi ICMP che ricevono. In questo modo, un'applicazione non si accorge mai di questi problemi specifici della rete.

Un nodo utilizzerà le informazioni ICMP per aggiornare le proprie tabelle di routing.

8.1.7. Il livello di trasporto: i protocolli UDP e TCP

8.1.7.1. Il protocollo UDP: User Datagram Protocol

Il protocollo UDP consente uno scambio non affidabile di dati tra due punti, ovvero non garantisce il corretto instradamento di un pacchetto alla sua destinazione. L’applicazione, se lo desidera, può gestire autonomamente questa situazione, ad esempio attendendo, dopo l’invio di un messaggio, una conferma di ricezione prima di inviare quello successivo.

Finora, a livello di rete, abbiamo parlato di indirizzi IP delle macchine. Tuttavia, su una macchina possono coesistere contemporaneamente diversi processi, tutti in grado di comunicare tra loro. È quindi necessario indicare, al momento dell’invio di un messaggio, non solo l’indirizzo IP del computer destinatario, ma anche il “nome” del processo destinatario. Questo nome è in realtà un numero, chiamato numero di porta. Alcuni numeri sono riservati alle applicazioni standard: la porta 69 per l’applicazione tftp (trivial file transfer protocol), ad esempio.

I pacchetti gestiti dal protocollo UDP sono chiamati anche datagrammi. Hanno la seguente forma:

Image

Questi datagrammi saranno incapsulati in pacchetti IP, quindi in frame fisici.

8.1.7.2. Il protocollo TCP: Transfer Control Protocol

Per garantire comunicazioni sicure, il protocollo UDP non è sufficiente: lo sviluppatore di applicazioni deve elaborare autonomamente un protocollo che gli consenta di verificare il corretto instradamento dei pacchetti.

Il protocollo TCP (Transfer Control Protocol) evita questi problemi. Le sue caratteristiche sono le seguenti:

  • Il processo che intende trasmettere stabilisce innanzitutto una connessione con il processo destinatario delle informazioni che sta per inviare. Tale connessione avviene tra una porta del computer mittente e una porta del computer ricevente. Tra le due porte viene così creato un percorso virtuale che sarà riservato esclusivamente ai due processi che hanno stabilito la connessione.
  • Tutti i pacchetti inviati dal processo sorgente seguono questo percorso virtuale e arrivano nell’ordine in cui sono stati inviati, cosa che non era garantita nel protocollo UDP poiché i pacchetti potevano seguire percorsi diversi.
  • Le informazioni trasmesse hanno un carattere continuo. Il processo mittente invia le informazioni al proprio ritmo. Queste non vengono necessariamente inviate immediatamente: il protocollo TCP attende di averne una quantità sufficiente per inviarle. Vengono memorizzate in una struttura denominata segmento TCP. Una volta riempito, questo segmento verrà trasmesso al livello IP, dove verrà incapsulato in un pacchetto IP.
  • Ogni segmento inviato dal protocollo TCP è numerato. Il protocollo TCP destinatario verifica di ricevere correttamente i segmenti in sequenza. Per ogni segmento ricevuto correttamente, invia una conferma di ricezione al mittente.
  • Quando quest’ultimo lo riceve, lo comunica al processo mittente. Quest’ultimo può quindi sapere che un segmento è arrivato a destinazione, cosa che non era possibile con il protocollo UDP.
  • Se, trascorso un certo tempo, il protocollo TCP che ha trasmesso un segmento non riceve una conferma di ricezione, ritrasmette il segmento in questione, garantendo così la qualità del servizio di inoltro delle informazioni.
  • Il circuito virtuale stabilito tra i due processi che comunicano è full-duplex: ciò significa che le informazioni possono transitare in entrambe le direzioni. In questo modo il processo di destinazione può inviare conferme di ricezione anche mentre il processo di origine continua a inviare informazioni. Ciò consente, ad esempio, al protocollo sorgente TCP di inviare più segmenti senza attendere la conferma di ricezione. Se dopo un certo tempo si accorge di non aver ricevuto la conferma di ricezione di un determinato segmento n. n, riprenderà la trasmissione dei segmenti da quel punto.

8.1.8. Il livello Applicazioni

Al di sopra dei protocolli UDP e TCP, esistono vari protocolli standard:

TELNET

Questo protocollo consente a un utente di un computer A della rete di connettersi a un computer B (spesso denominato computer host). TELNET emula sul computer A un terminale cosiddetto universale. L'utente si comporta quindi come se disponesse di un terminale collegato al computer B. Telnet si basa sul protocollo TCP.

FTP: (File Transfer Protocol)

Questo protocollo consente lo scambio di file tra due macchine remote, nonché operazioni sui file quali, ad esempio, la creazione di directory. Si basa sul protocollo TCP.

TFTP: (Trivial File Transfer Control)

Questo protocollo è una variante di FTP. Si basa sul protocollo UDP ed è meno sofisticato rispetto a FTP.

DNS: (Domain Name System)

Quando un utente desidera scambiare file con un computer remoto, ad esempio tramite FTP, deve conoscere l’indirizzo Internet di tale computer. Ad esempio, per eseguire FTP sul computer Lagaffe dell’Università di Angers, bisognerebbe avviare FTP come segue: FTP 193.49.144.1

Ciò richiede la presenza di una directory che metta in corrispondenza macchina <--> indirizzo IP. Probabilmente in questa directory le macchine sarebbero designate con nomi simbolici quali:

macchina DPX2/320 dell’Università di Angers

macchina Sun dell'Università di Angers ISERPA

È evidente che sarebbe più pratico indicare una macchina con un nome piuttosto che con il suo indirizzo IP. Si pone quindi il problema dell’unicità del nome: ci sono milioni di macchine interconnesse. Si potrebbe immaginare che un organismo centralizzato attribuisca i nomi. Sarebbe senza dubbio piuttosto oneroso. Il controllo dei nomi è stato infatti distribuito in domini. Ogni dominio è gestito da un organismo generalmente molto snello che gode di piena libertà nella scelta dei nomi dei computer. Così, i computer in Francia appartengono al dominio «fr», gestito dall’Inria di Parigi. Per semplificare ulteriormente le cose, il controllo viene ulteriormente distribuito: all’interno del dominio «fr» vengono creati altri domini. Ad esempio, l’Università di Angers appartiene al dominio «univ-Angers». Il servizio che gestisce questo dominio ha piena libertà di denominare i computer della rete dell’Università di Angers. Per il momento questo dominio non è stato suddiviso. Ma in una grande università con molti computer in rete, potrebbe esserlo.

Il computer DPX2/320 dell’Università di Angers è stata denominata Lagaffe, mentre una PC e una 486DX50 sono state denominate liny. Come si fa a fare riferimento a queste macchine dall’esterno? Specificando la gerarchia dei domini a cui appartengono. Pertanto, il nome completo della macchina Lagaffe sarà:

        Lagaffe.univ-Angers.fr

All’interno dei domini è possibile utilizzare nomi relativi. Pertanto, all’interno del dominio fr e al di fuori del dominio univ-Angers, la macchina Lagaffe potrà essere indicata come

        Lagaffe.univ-Angers

Infine, all’interno del dominio univ-Angers, potrà essere indicata semplicemente come

        Lagaffe

Un'applicazione può quindi fare riferimento a una macchina tramite il suo nome. In definitiva, è comunque necessario ottenere l'indirizzo Internet di tale macchina. Come avviene ciò? Supponiamo di voler comunicare da una macchina A con una macchina B.

  • Se la macchina B appartiene allo stesso dominio della macchina A, probabilmente il suo indirizzo IP si troverà in un file della macchina A.
  • Altrimenti, il computer A troverà in un altro file, o nello stesso di prima, un elenco di alcuni server dei nomi con i relativi indirizzi IP. Un server dei nomi ha il compito di stabilire la corrispondenza tra il nome di un computer e il suo indirizzo IP. Il computer A invierà una richiesta speciale al primo server dei nomi della sua lista, denominata richiesta DNS, che include quindi il nome del computer ricercato. Se il server interpellato possiede tale nome nei propri registri, invierà al computer A l’indirizzo IP corrispondente. In caso contrario, anche il server troverà nei propri file un elenco di server dei nomi che può interpellare. Lo farà quindi. In questo modo verrà interpellato un certo numero di server dei nomi, non in modo casuale ma in modo da ridurre al minimo le richieste. Se la macchina viene infine individuata, la risposta tornerà alla macchina A.

XDR: (Rappresentazione dei dati eXternal)

Creato da sun MicroSystems, questo protocollo specifica una rappresentazione standard dei dati, indipendente dalle macchine.

RPC: (Remote Procedure Call)

Definito anch’esso da Sun, è un protocollo di comunicazione tra applicazioni remote, indipendente dal livello di trasporto. Questo protocollo è importante: solleva il programmatore dalla necessità di conoscere i dettagli del livello di trasporto e rende le applicazioni portabili. Questo protocollo si basa sul protocollo XDR

NFS: Network File System

Sempre definito da Sun, questo protocollo consente a una macchina di «vedere» il sistema di file di un’altra macchina. Si basa sul precedente protocollo RPC.

8.1.9. Conclusione

In questa introduzione abbiamo presentato alcune linee generali dei protocolli Internet. Per approfondire questo argomento, si consiglia la lettura dell'eccellente libro di Douglas Comer:

Titolo: TCP/IP: Architettura, protocolli, applicazioni.

Autore: Douglas COMER

Editore: InterEditions

8.2. Gestione degli indirizzi di rete in Java

8.2.1. Definizione

Ogni macchina presente su Internet è identificata da un indirizzo o da un nome univoco. Queste due entità sono gestite in Java dalla classe InetAddress, di cui riportiamo alcuni metodi:

byte [] getAddress()
restituisce i 4 byte dell'indirizzo IP dell'istanza InetAddress corrente
String getHostAddress()
restituisce l'indirizzo IP dell'istanza corrente InetAddress
String getHostName()
fornisce il nome Internet dell'istanza corrente InetAddress
String toString()
fornisce l'identità (indirizzo IP) e il nome Internet dell'istanza corrente InetAddress
InetAddress getByName(String Host)
crea l'istanza InetAddress del computer indicato da Host. Genera un'eccezione se Host è sconosciuto. Host può essere il nome Internet di un computer o il suo indirizzo IP nella forma I1.I2.I3.I4
InetAddress getLocalHost()
crea l'istanza InetAddress del computer su cui è in esecuzione il programma contenente questa istruzione.

8.2.2. Alcuni esempi

8.2.2.1. Identificare la macchina locale


import java.net.*;

public class localhost{
  public static void main (String arg[]){
    try{
      InetAddress adresse=InetAddress.getLocalHost();
    byte[] IP=adresse.getAddress();
    System.out.print("IP=");
    int i;
    for(i=0;i<IP.length-1;i++) System.out.print(IP[i]+".");
    System.out.println(IP[i]);
      System.out.println("adresse="+adresse.getHostAddress());
    System.out.println("nom="+adresse.getHostName());
    System.out.println("identité="+adresse);
    } catch (UnknownHostException e){
      System.out.println ("Erreur getLocalHost : "+e);
    }// fine try
  }// fine main
}// fine della classe

I risultati dell'esecuzione sono i seguenti:

IP=127.0.0.1
adresse=127.0.0.1
nom=tahe
identité=tahe/127.0.0.1

Ogni macchina ha un indirizzo interno IP, ovvero 127.0.0.1. Quando un programma utilizza questo indirizzo di rete, fa riferimento alla macchina su cui è in esecuzione. Il vantaggio di questo indirizzo è che non richiede una scheda di rete. È quindi possibile testare programmi di rete senza essere connessi a una rete. Un altro modo per indicare il computer locale è utilizzare il nome «localhost».

8.2.2.2. Identificare un computer qualsiasi


import java.net.*;

public class getbyname{
  public static void main (String arg[]){
    String nomMachine;
    // si recupera l'argomento
    if(arg.length==0) 
      nomMachine="localhost";
    else nomMachine=arg[0];
    // si tenta di ottenere l'indirizzo della macchina
    try{
      InetAddress adresse=InetAddress.getByName(nomMachine);
      System.out.println("IP : "+  adresse.getHostAddress());
      System.out.println("nom : "+ adresse.getHostName());
      System.out.println("identité : "+ adresse);
    } catch (UnknownHostException e){
      System.out.println ("Erreur getByName : "+e);
    }// fine try
  }// fine main
}// fine classe

Con la chiamata java getbyname, si ottengono i seguenti risultati:

IP : 127.0.0.1
nom : localhost
identité : localhost/127.0.0.1

Con la chiamata java getbyname shiva.istia.univ-angers.fr, si ottiene:

IP : 193.52.43.5
nom : shiva.istia.univ-angers.fr
identité : shiva.istia.univ-angers.fr/193.52.43.5

Con la chiamata Java getbyname www.ibm.com, si ottiene:

IP : 204.146.18.33
nom : www.ibm.com
identité : www.ibm.com/204.146.18.33

8.3. Comunicazioni TCP-IP

8.3.1. Informazioni generali

Image

Quando un'applicazione AppA su un computer A vuole comunicare con un'applicazione AppB su un computer B in Internet, deve conoscere diverse informazioni:

  • l’indirizzo IP o il nome del computer B
  • il numero della porta su cui opera l’applicazione AppB. Infatti, il computer B può ospitare numerose applicazioni che operano su Internet. Quando riceve informazioni provenienti dalla rete, deve sapere a quale applicazione sono destinate tali informazioni. Le applicazioni del computer B accedono alla rete tramite interfacce chiamate anche porte di comunicazione. Questa informazione è contenuta nel pacchetto ricevuto dal computer B affinché possa essere consegnato all’applicazione corretta.
  • I protocolli di comunicazione supportati dal computer B. Nel nostro studio utilizzeremo esclusivamente i protocolli TCP-IP.
  • il protocollo di dialogo accettato dall’applicazione AppB. Infatti, i computer A e B «comunicheranno» tra loro. Ciò che si scambieranno sarà incapsulato nei protocolli TCP-IP. Tuttavia, quando, alla fine della catena, l’applicazione AppB riceverà le informazioni inviate dall’applicazione AppA, dovrà essere in grado di interpretarle. Ciò è analogo alla situazione in cui due persone, A e B, comunicano al telefono: il loro dialogo viene trasportato dal telefono. La voce viene codificata sotto forma di segnali dal telefono A, trasportata attraverso le linee telefoniche, arriva al telefono B per essere decodificata. La persona B sente quindi le parole. È qui che entra in gioco il concetto di protocollo di dialogo: se A parla francese e B non capisce questa lingua, A e B non potranno dialogare in modo efficace.

Pertanto, le due applicazioni che comunicano tra loro devono concordare sul tipo di dialogo che adotteranno. Ad esempio, il dialogo con un servizio ftp non è lo stesso di quello con un servizio pop: questi due servizi non accettano gli stessi comandi. Hanno un protocollo di comunicazione diverso.

8.3.2. Le caratteristiche del protocollo TCP

In questa sede esamineremo solo le comunicazioni di rete che utilizzano il protocollo di trasporto TCP. Ricordiamo qui le caratteristiche di questo protocollo:

  • Il processo che intende trasmettere stabilisce innanzitutto una connessione con il processo destinatario delle informazioni che sta per inviare. Questa connessione avviene tra una porta del computer mittente e una porta del computer ricevente. Tra le due porte viene così creato un percorso virtuale che sarà riservato esclusivamente ai due processi che hanno stabilito la connessione.
  • Tutti i pacchetti inviati dal processo sorgente seguono questo percorso virtuale e arrivano nell’ordine in cui sono stati inviati
  • Le informazioni trasmesse hanno un carattere continuo. Il processo mittente invia le informazioni al proprio ritmo. Queste non vengono necessariamente inviate immediatamente: il protocollo TCP attende di averne una quantità sufficiente per inviarle. Vengono memorizzate in una struttura denominata segmento TCP. Una volta riempito, questo segmento verrà trasmesso al livello IP, dove verrà incapsulato in un pacchetto IP.
  • Ogni segmento inviato dal protocollo TCP è numerato. Il protocollo TCP destinatario verifica di ricevere i segmenti in sequenza. Per ogni segmento ricevuto correttamente, invia una conferma di ricezione al mittente.
  • Quando quest’ultimo lo riceve, ne dà comunicazione al processo mittente. Quest’ultimo può quindi sapere che un segmento è giunto a destinazione.
  • Se, trascorso un certo tempo, il protocollo TCP che ha trasmesso un segmento non riceve una conferma di ricezione, ritrasmette il segmento in questione, garantendo così la qualità del servizio di inoltro delle informazioni.
  • Il circuito virtuale stabilito tra i due processi che comunicano è full-duplex: ciò significa che le informazioni possono transitare in entrambe le direzioni. In questo modo il processo di destinazione può inviare conferme di ricezione anche mentre il processo di origine continua a inviare informazioni. Ciò consente, ad esempio, al protocollo sorgente TCP di inviare più segmenti senza attendere la conferma di ricezione. Se dopo un certo tempo si accorge di non aver ricevuto la conferma di ricezione di un determinato segmento n. n, riprenderà la trasmissione dei segmenti da quel punto.

8.3.3. Il rapporto client-server

Spesso la comunicazione su Internet è asimmetrica: il computer A avvia una connessione per richiedere un servizio al computer B, specificando che desidera aprire una connessione con il servizio SB1 del computer B. Quest’ultimo accetta o rifiuta. Se accetta, la macchina A può inviare le proprie richieste al servizio SB1. Queste devono essere conformi al protocollo di dialogo compreso dal servizio SB1. Si instaura così un dialogo domanda-risposta tra la macchina A, detta macchina client, e la macchina B, detta macchina server. Uno dei due partner chiuderà la connessione.

8.3.4. Architettura di un client

L’architettura di un programma di rete che richiede i servizi di un’applicazione server sarà la seguente:

ouvrir la connexion avec le service SB1 de la machine B
si réussite alors
        tant que ce n'est pas fini
            préparer une demande
            l'émettre vers la machine B
            attendre et récupérer la réponse
            la traiter
        fin tant que
finsi

8.3.5. Architettura di un server

L'architettura di un programma che offre servizi sarà la seguente:

ouvrir le service sur la machine locale
tant que le service est ouvert
        se mettre à l'écoute des demandes de connexion sur un port dit port d'écoute
        lorsqu'il y a une demande, la faire traiter par une autre tâche sur un autre port dit port de service
fin tant que

Il programma server gestisce in modo diverso la richiesta di connessione iniziale di un client rispetto alle sue successive richieste volte a ottenere un servizio. Il programma non fornisce direttamente il servizio. Se lo facesse, durante la durata del servizio non sarebbe più in ascolto delle richieste di connessione e i client non verrebbero quindi serviti. Proceda quindi in altro modo: non appena una richiesta di connessione viene ricevuta sulla porta di ascolto e poi accettata, il server crea un’attività incaricata di fornire il servizio richiesto dal cliente. Tale servizio viene fornito su un’altra porta del server, denominata porta di servizio. In questo modo è possibile servire più clienti contemporaneamente.

Un'attività di servizio avrà la seguente struttura:

tant que le service n'a pas été rendu totalement
        attendre une demande sur le port de service
        lorsqu'il y en a une, élaborer la réponse
        transmettre la réponse via le port de service
fin tant que
libérer le port de service

8.3.6. La classe Socket

8.3.6.1. Définition

Lo strumento di base utilizzato dai programmi che comunicano su Internet è il socket. Questo termine inglese significa "presa di corrente". In questo contesto, il suo significato è esteso a "presa di rete". Affinché un’applicazione possa inviare e ricevere informazioni sulla rete Internet, ha bisogno di una presa di rete, una socket. Questo strumento è stato inizialmente creato nelle versioni Unix dell’Università di Berkeley. Da allora è stato portato su tutti i sistemi Unix e nell’ambiente Windows. È presente anche sulle macchine virtuali Java in due forme: la classe Socket per le applicazioni client e la classe ServerSocket per le applicazioni server. Di seguito illustriamo alcuni dei costruttori e dei metodi della classe Socket:

public Socket(String host, int port)
apre una connessione remota con la porta port del computer host
public int getLocalPort()
restituisce il numero della porta locale utilizzata dal socket
  
public int getPort()
restituisce il numero della porta remota a cui è connesso il socket
  
public InetAddress getLocalAdress()
restituisce l'indirizzo locale InetAddress a cui è associato il socket
  
public InetAddress getInetAdress()
restituisce l'indirizzo remoto InetAddress a cui è collegato il socket
  
public InputStream getInputStream()
crea un flusso di input che consente di leggere i dati inviati dal partner remoto
  
public OutputStream getOutputStream()
restituisce un flusso di uscita che consente di inviare dati al partner remoto
  
public void shutdownInput()
chiude il flusso di ingresso del socket
  
public void shutdownOutput()
chiude il flusso di uscita del socket
  
public void close()
chiude il socket e i relativi flussi di I/O
  
public String toString()
restituisce una stringa di caratteri che "rappresenta" il socket
 

8.3.6.2. Apertura di una connessione con un server

Abbiamo visto che affinché un computer A possa aprire una connessione con un servizio di un computer B, sono necessarie due informazioni:

  • l'indirizzo IP o il nome del computer B
  • il numero di porta su cui opera il servizio desiderato

Il costruttore

    public Socket(String  host, int  port);

crea un socket e lo connette al computer host sulla porta port. Questo costruttore genera un'eccezione in diversi casi:

  • indirizzo errato
  • porta errata
  • richiesta rifiutata

Dobbiamo gestire questa eccezione:


    Socket  sClient=null;
    try{
        sClient=new Socket(host,port);
    } catch(Exception e){
        // la connessione non è riuscita - si gestisce l'errore
        ….
    }

Se la richiesta di connessione va a buon fine, al client viene assegnata localmente una porta per comunicare con la macchina B. Una volta stabilita la connessione, è possibile conoscere tale porta tramite il metodo:

public int getLocalPort();

Se la connessione va a buon fine, abbiamo visto che, dal canto suo, il server affida il servizio a un altro processo che opera su una cosiddetta porta di servizio. Il numero di questa porta può essere ottenuto tramite il metodo:

public int getPort();

8.3.6.3. Invio di informazioni sulla rete

È possibile ottenere un flusso di scrittura sul socket e quindi sulla rete tramite il metodo:

public OutputStream getOutputStream();

Tutto ciò che verrà inviato in questo flusso verrà ricevuto sulla porta di servizio del server. Molte applicazioni presentano un dialogo sotto forma di righe di testo terminate da un'interruzione di riga. Pertanto, il metodo println risulta molto pratico in questi casi. Si trasforma quindi il flusso di uscita OutputStream in un flusso PrintWriter che utilizza il metodo println. La scrittura può generare un'eccezione.

8.3.6.4. Lettura delle informazioni provenienti dalla rete

È possibile ottenere un flusso di lettura delle informazioni in arrivo sul socket con il metodo:

public InputStream getInputStream();

Tutto ciò che verrà letto in questo flusso proviene dalla porta di servizio del server. Per le applicazioni con un dialogo sotto forma di righe di testo terminate da un'interruzione di riga, è preferibile utilizzare il metodo readLine. A tal fine, si trasforma il flusso di input InputStream in un flusso BufferedReader, che dispone del metodo readLine(). La lettura può generare un'eccezione.

8.3.6.5. Chiusura della connessione

Si effettua con il metodo:

public void close();

Il metodo può generare un'eccezione. Le risorse utilizzate, in particolare la porta di rete, vengono liberate.

8.3.6.6. L'architettura del client

Ora disponiamo degli elementi necessari per descrivere l'architettura di base di un client Internet:


    Socket  sClient=null;
    try{
            // ci si connette al servizio in esecuzione sulla porta P del computer M
        sClient=new Socket(M,P);

        // si creano i flussi di ingresso-uscita del socket client
        BufferedReader in=new BufferedReader(new InputStreamReader(sClient.getInputStream()));
        PrintWriter out=new PrintWriter(sClient.getOutputStream(),true);

        // ciclo richiesta-risposta
        boolean  fini=false;
        String demande;
        String réponse;
        while (! fini){
            // si prepara la richiesta
            demande=…
            // la si invia
            out.println(demande);
            // si legge la risposta
            réponse=in.readLine();
            // elaborazione della risposta

        }
        // è terminato
        sClient.close();
    } catch(Exception e){
        // si gestisce l'eccezione
        ….
    }

Per non complicare l'esempio, non abbiamo cercato di gestire i diversi tipi di eccezione generati dal costruttore Socket o dai metodi readline, getInputStream, getOutputStream e close. Tutto è stato raggruppato in un'unica eccezione.

8.3.7. La classe ServerSocket

8.3.7.1. Définition

Questa classe è destinata alla gestione dei socket lato server. Di seguito illustriamo alcuni dei costruttori e dei metodi di questa classe:

public ServerSocket(int port)
crea un socket di ascolto sulla porta port
public ServerSocket(int port, int count)
idem, ma imposta in count la dimensione della coda e in c.a.d il numero massimo di connessioni client messe in attesa se il server è occupato al momento dell’arrivo della connessione client.
public int getLocalPort()
restituisce il numero della porta di ascolto utilizzata dal socket
public InetAddress getInetAdress()
restituisce l'indirizzo locale InetAddress a cui è associato il socket
public Socket accept()
mette il server in attesa di una connessione (operazione bloccante). All'arrivo di una connessione client, restituisce un socket tramite il quale verrà fornito il servizio al client.
public void close()
chiude il socket e i relativi flussi di I/O
public String toString()
restituisce una stringa di caratteri che "rappresenta" il socket
public void close()
chiude il socket di servizio e libera le risorse ad esso associate

8.3.7.2. Apertura del servizio

Si effettua con i due costruttori:

public ServerSocket(int  port);    
public ServerSocket(int  port, int  count);

port è la porta di ascolto del servizio: quella a cui i client inviano le loro richieste di connessione. count è la dimensione massima della coda del servizio (50 per impostazione predefinita), che memorizza le richieste di connessione dei client a cui il server non ha ancora risposto. Quando la coda è piena, le richieste di connessione in arrivo vengono respinte. Entrambe le istruzioni generano un'eccezione.

8.3.7.3. Accettazione di una richiesta di connessione

Quando un client invia una richiesta di connessione sulla porta di ascolto del servizio, quest’ultimo la accetta con il metodo:

    public Socket accept();

Questo metodo restituisce un'istanza di Socket: si tratta del socket di servizio, attraverso il quale verrà fornito il servizio, il più delle volte da un'altra attività. Il metodo può generare un'eccezione.

8.3.7.4. Lettura/scrittura tramite il socket di servizio

Poiché il socket di servizio è un'istanza della classe Socket, si rimanda alle sezioni precedenti in cui è stato trattato questo argomento.

8.3.7.5. Identificare il cliente

Una volta ottenuto il socket di servizio, il cliente può essere identificato con il metodo

    public InetAddress getInetAddress()

della classe Socket. Si avrà così accesso all'indirizzo IP e al nome del cliente.

8.3.7.6. Chiudere il servizio

Ciò avviene tramite il metodo

    public void close();

della classe ServerSocket. Ciò libera le risorse occupate, in particolare la porta di ascolto. Il metodo può generare un'eccezione.

8.3.7.7. Architettura di base di un server

Da quanto detto, è possibile descrivere la struttura di base di un server:


SocketServer sEcoute=null;
try{
    // avvio del servizio
    int portEcoute=…
    int maxConnexions=…
    sEcoute=new ServerSocket(portEcoute,maxConnexions);

    // elaborazione delle richieste di connessione
    boolean fini=false;
    Socket sService=null;
    while( ! fini){
        // attesa e accettazione di una richiesta
        sService=sEcoute.accept();

        // il servizio viene fornito da un'altra attività a cui viene passato il socket di servizio
        new Service(sService).start();

        // si torna in attesa delle richieste di connessione
    }
    // È tutto: si chiude il servizio
    sEcoute.close();
} catch (Exception e){
    // si gestisce l'eccezione

}

La classe Service è un thread che potrebbe avere il seguente aspetto:


public class Service extends Thread{

    Socket sService;        // il socket di servizio

    // costruttore
    public Service(Socket S){
        sService=S;
    }

// esecuzione
public void run(){
    try{
        // si creano i flussi di ingresso-uscita
    BufferedReader in=new BufferedReader(new InputStreamReader(sService.getInputStream()));
    PrinttWriter out=new PrintWriter(sService.getOutputStream(),true);

    // ciclo richiesta-risposta
    boolean  fini=false;
    String demande;
    String réponse;
    while (! fini){
        // si legge la richiesta
        demande=in.readLine();

        // la si elabora 


        // si prepara la risposta
        réponse=…

        // si invia
        out.println(réponse);
    }
    // operazione completata
    sService.close();
    } catch(Exception e){
    // si gestisce l'eccezione
    ….
    }// try
} // run

8.4. Applicazioni

8.4.1. Server echo

Ci proponiamo di scrivere un server echo che verrà avviato da una finestra DOS tramite il comando:

    java serveurEcho port

Il server opera sulla porta specificata come parametro. Si limita a rinviare al client la richiesta che quest’ultimo gli ha inviato, accompagnata dalla sua identità (IP+nome). Accetta 2 connessioni nella sua lista d’attesa. Abbiamo qui tutti gli elementi costitutivi di un server TCP. Il programma è il seguente:

// chiamata: serveurEcho porta
// server di eco
// restituisce al client la riga che questi gli ha inviato


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

public class serveurEcho{
    public final static String syntaxe="Syntaxe : serveurEcho port";
    public final static int nbConnexions=2;

    // programma principale
    public static void main (String arg[]){

     // c'è un argomento
     if(arg.length != 1)
        erreur(syntaxe,1);

     // questo argomento deve essere un numero intero >0
     int port=0;
     boolean erreurPort=false;
     Exception E=null;
     try{
        port=Integer.parseInt(arg[0]);
     }catch(Exception e){
            E=e;
            erreurPort=true;
     }
     erreurPort=erreurPort || port <=0;
     if(erreurPort)
        erreur(syntaxe+"\n"+"Port incorrect ("+E+")",2);

     // si crea il socket di ascolto
     ServerSocket ecoute=null;
     try{
        ecoute=new ServerSocket(port,nbConnexions);
     } catch (Exception e){
        erreur("Erreur lors de la création de la socket d'écoute ("+e+")",3);
     }

     // monitoraggio
     System.out.println("Serveur d'écho lancé sur le port " + port);

     // ciclo di servizio
     boolean serviceFini=false;
     Socket service=null;
     while (! serviceFini){
         // attesa di un client
        try{
            service=ecoute.accept();
        } catch (IOException e){
                erreur("Erreur lors de l'acceptation d'une connexion ("+e+")",4);
        }

         // si identifica la connessione
        try{
            System.out.println("Client ["+identifie(service.getInetAddress())+","+
            service.getPort()+"] connecté au serveur [" + identifie (InetAddress.getLocalHost())
            + "," + service.getLocalPort() + "]");
        } catch (Exception e) {
            erreur("identification liaison",1);
        }


         // il servizio è gestito da un altro processo
        new traiteClientEcho(service).start();
     }// fine while
    }// fine main

// visualizzazione degli errori
    public static void erreur(String msg, int exitCode){
        System.err.println(msg);
        System.exit(exitCode);
    }

    // identifica
    private static String identifie(InetAddress Host){
        // identificazione dell'host
        String ipHost=Host.getHostAddress();
        String nomHost=Host.getHostName();
        String idHost;
        if (nomHost == null) idHost=ipHost;
            else idHost=ipHost+","+nomHost;
        return idHost;
    }

}// fine classe


// fornisce il servizio a un client del server di eco

class traiteClientEcho extends Thread{

    private Socket service;            // socket di servizio
    private BufferedReader in;        // flusso in entrata
    private PrintWriter out;            // flusso di uscita

     // costruttore
    public traiteClientEcho(Socket service){
        this.service=service;
    }

     // metodo run
    public void run(){

         // creazione dei flussi di ingresso e di uscita
        try{
            in=new BufferedReader(new InputStreamReader(service.getInputStream()));
        } catch (IOException e){
                erreur("Erreur lors de la création du flux déentrée de la socket de service ("+e+")",1);
        }// fine try
        try{
            out=new PrintWriter(service.getOutputStream(),true);
        } catch (IOException e){
                erreur("Erreur lors de la création du flux de sortie de la socket de service ("+e+")",1);
        }// fine try

         // l'identificativo della connessione viene inviato al client
        try{
            out.println("Client ["+identifie(service.getInetAddress())+","+
            service.getPort()+"] connecté au serveur [" + identifie (InetAddress.getLocalHost())
            + "," + service.getLocalPort() + "]");
        } catch (Exception e) {
            erreur("identification liaison",1);
        }

         // ciclo di lettura richiesta/scrittura risposta
        String demande,reponse;
        try{
             // il servizio si interrompe quando il client invia un indicatore di fine file
            while ((demande=in.readLine())!=null){
                // eco della richiesta
                reponse="["+demande+"]";
                out.println(reponse);
                 // il servizio si interrompe quando il client invia "fine"
                if(demande.trim().toLowerCase().equals("fin")) break;
            }// fine ciclo while
        } catch (IOException e){
                erreur("Erreur lors des échanges client/serveur ("+e+")",3);
        }// fine try

         // si chiude il socket
        try{
            service.close();
        } catch (IOException e){
            erreur("Erreur lors de la fermeture de la socket de service ("+e+")",2);
        }// fine try
    }// fine run

     // visualizzazione degli errori
    public static void erreur(String msg, int exitCode){
        System.err.println(msg);
        System.exit(exitCode);
    }// fine errore

     // identificazione
    private String identifie(InetAddress Host){
         // identificazione host
        String ipHost=Host.getHostAddress();
        String nomHost=Host.getHostName();
        String idHost;
        if (nomHost == null) idHost=ipHost;
            else idHost=ipHost+","+nomHost;
        return idHost;
    }

}// fine classe

Le due classi necessarie al servizio sono state riunite in un unico file sorgente. Solo una di esse, quella che contiene la funzione main, possiede l'attributo public. La struttura del server è conforme all'architettura generale dei server TCP. È stato aggiunto un metodo (identifie) che consente di identificare la connessione tra il server e un client. Ecco alcuni risultati:

Il server viene avviato con il comando

    java serveurEcho 187

Nella finestra di controllo viene quindi visualizzato il seguente messaggio:

Serveur d'écho lancé sur le port 187

Per testare questo server, si utilizza il programma telnet, disponibile sia su Unix che su Windows. Telnet è un client TCP universale adatto a tutti i server che accettano righe di testo terminate da un carattere di fine riga nelle loro comunicazioni. È il caso del nostro server echo. Si avvia un primo client telnet su Windows (2000 in questo esempio) digitando telnet in una finestra DOS:


DOS>telnet
Microsoft (R) Windows 2000 (TM) version 5.00 (numéro 2195)
Client Telnet Microsoft
Client Telnet numéro 5.00.99203.1

Le caractère d'échappement est 'CTRL+$'

Microsoft Telnet> help

Les commandes peuvent être abrégées. Les commandes prises en charge sont :

close           ferme la connexion en cours
display         affiche les paramètres d'opération
open            ouvre une connexion à un site
quit            quitte telnet
set             définit les options (entrez 'set ?' pour afficher la liste)
status          affiche les informations d'état
unset           annule les options (entrez 'unset ?' pour afficher la liste)
? ou help       affiche des informations d'aide

Microsoft Telnet> set ?
NTLM            Active l'authentification NTLM.
LOCAL_ECHO      Active l'écho local.
TERM x          (où x est ANSI, VT100, VT52 ou VTNT))
CRLF            Envoi de CR et de LF

Microsoft Telnet> set local_echo

Microsoft Telnet> open localhost 187

Il programma telnet, per impostazione predefinita, non visualizza l'eco dei comandi digitati sulla tastiera. Per ottenere tale eco, si immette il comando:

Microsoft Telnet> set local_echo

Per aprire una connessione con il server, specificando la porta del servizio di eco (187) e l'indirizzo del computer su cui si trova (localhost), si immette il comando:

Microsoft Telnet> open localhost 187

Nella finestra DOS del client viene quindi visualizzato il messaggio:

Client [127.0.0.1,tahe,1059] connecté au serveur [127.0.0.1,tahe,187]

Nella finestra del server compare il messaggio:

Serveur d'écho lancé sur le port 187
Client [127.0.0.1,tahe,1059] connecté au serveur [127.0.0.1,tahe,187]

In questo caso, tahe e localhost indicano la stessa macchina. Nella finestra del client telnet è possibile digitare righe di testo. Il server le riporta in eco:

Client [127.0.0.1,tahe,1059] connectÚ au serveur [127.0.0.1,tahe,187]
je suis là
[je suis là]
au revoir
[au revoir]

Si noti che la porta del client (1059) viene rilevata correttamente, ma che la porta del servizio (187) è identica alla porta di ascolto (187), il che è inaspettato. Ci si sarebbe infatti potuti aspettare di ottenere la porta del socket di servizio e non quella di ascolto. Bisognerebbe verificare se si ottengono gli stessi risultati su Unix. Ora avviamo un secondo client telnet. La finestra del server diventa:

Serveur d'écho lancé sur le port 187
Client [127.0.0.1,tahe,1059] connecté au serveur [127.0.0.1,tahe,187]
Client [127.0.0.1,tahe,1060] connecté au serveur [127.0.0.1,tahe,187]

Nella finestra del secondo client è anche possibile digitare delle righe di testo:

Client [127.0.0.1,tahe,1060] connecté au serveur [127.0.0.1,tahe,187]
ligne1
[ligne1]
ligne2
[ligne2]

Si può quindi notare che il server echo può servire più client contemporaneamente. I client telnet possono essere terminati chiudendo la finestra DOS in cui sono in esecuzione.

8.4.2. Un client Java per il server echo

Nella sezione precedente abbiamo utilizzato un client telnet per testare il servizio echo. Ora scriviamo il nostro client:

// chiamata: clientEcho porta macchina
// client del server di eco
// invia righe al server che le restituisce tramite eco

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

public class clientEcho{
    public final static String syntaxe="Syntaxe : clientEcho machine port";

    // programma principale    
    public static void main (String arg[]){

     // ci sono due argomenti
     if(arg.length != 2)
        erreur(syntaxe,1);

     // il primo argomento deve essere il nome di una macchina esistente
    String machine=arg[0];
    InetAddress serveurAddress=null;
    try{
        serveurAddress=InetAddress.getByName(machine);
    } catch (Exception e){
        erreur(syntaxe+"\nMachine "+machine+" inaccessible (" + e +")",2);
    }

     // la porta deve essere un numero intero maggiore di 0
     int port=0;
     boolean erreurPort=false;
     Exception E=null;
     try{
        port=Integer.parseInt(arg[1]);
     }catch(Exception e){
            E=e;
            erreurPort=true;
     }
     erreurPort=erreurPort || port <=0;
     if(erreurPort)
        erreur(syntaxe+"\nPort incorrect ("+E+")",3);

     // ci si connette al server
     Socket sClient=null;
     try{
        sClient=new Socket(machine,port);
     } catch (Exception e){
        erreur("Erreur lors de la création de la socket de communication ("+e+")",4);
     }

     // si identifica la connessione
    try{
        System.out.println("Client : Client ["+identifie(InetAddress.getLocalHost())+","+
        sClient.getLocalPort()+"] connecté au serveur [" + identifie (sClient.getInetAddress())
        + "," + sClient.getPort() + "]");
    } catch (Exception e) {
        erreur("identification liaison ("+e+")",5);
    }

     // creazione del flusso di lettura delle righe digitate sulla tastiera
    BufferedReader IN=null;
    try{
        IN=new BufferedReader(new InputStreamReader(System.in));
    } catch (Exception e){
        erreur("Création du flux d'entrée clavier ("+e+")",6);
    }
     // creazione del flusso di ingresso associato al socket client
    BufferedReader in=null;
    try{
        in=new BufferedReader(new InputStreamReader(sClient.getInputStream()));
    } catch (Exception e){
        erreur("Création du flux d'entrée de la socket client("+e+")",7);
    }
     // creazione del flusso di uscita associato al socket client
    PrintWriter out=null;
    try{
        out=new PrintWriter(sClient.getOutputStream(),true);
    } catch (Exception e){
        erreur("Création du flux de sortie de la socket ("+e+")",8);
    }

     // ciclo richieste-risposte
    boolean serviceFini=false;
    String demande=null;
    String reponse=null;

    // si legge il messaggio inviato dal server subito dopo la connessione 
    try{
        reponse=in.readLine();
    } catch (IOException e){
            erreur("Lecture réponse ("+e+")",4);
    }        

     // visualizzazione della risposta
    System.out.println("Serveur : " +reponse);

    while (! serviceFini){
         // lettura di una riga digitata sulla tastiera
        System.out.print("Client : ");
        try{
            demande=IN.readLine();
        } catch (Exception e){
            erreur("Lecture ligne ("+e+")",9);
        }
         // invio di una richiesta sulla rete
        try{
            out.println(demande);
        } catch (Exception e){
            erreur("Envoi demande ("+e+")",10);
        }
         // attesa/lettura della risposta
        try{
            reponse=in.readLine();
        } catch (IOException e){
                erreur("Lecture réponse ("+e+")",4);
        }
         // visualizzazione della risposta
        System.out.println("Serveur : " +reponse);
         // È finito?
        if(demande.trim().toLowerCase().equals("fin")) serviceFini=true;
    }
     // è finito
    try{
        sClient.close();
    } catch(Exception e){
        erreur("Fermeture socket ("+e+")",11);
    }
}// main

// visualizzazione degli errori
    public static void erreur(String msg, int exitCode){
        System.err.println(msg);
        System.exit(exitCode);
    }

    // identifica
    private static String identifie(InetAddress Host){
        // identificazione dell'host
        String ipHost=Host.getHostAddress();
        String nomHost=Host.getHostName();
        String idHost;
        if (nomHost == null) idHost=ipHost;
            else idHost=ipHost+","+nomHost;
        return idHost;
    }

}// fine classe

La struttura di questo client è conforme all'architettura generale dei client tcp. In questo caso, le diverse eccezioni possibili sono state gestite una per una, il che appesantisce il programma. Ecco i risultati ottenuti durante il test di questo client:

Client : Client [127.0.0.1,tahe,1045] connecté au serveur [127.0.0.1,localhost,187]
Serveur : Client [127.0.0.1,localhost,1045] connectÚ au serveur [127.0.0.1,tahe,187]
Client : 123
Serveur : [123]
Client : abcd
Serveur : [abcd]
Client : je suis là
Serveur : [je suis là]
Client : fin
Serveur : [fin]

Le righe che iniziano con Client sono quelle inviate dal client, mentre quelle che iniziano con Serveur sono quelle che il server ha restituito.

8.4.3. Un client generico TCP

Molti servizi creati agli albori di Internet funzionano secondo il modello del server di eco esaminato in precedenza: gli scambi client-server avvengono tramite lo scambio di righe di testo. Scriveremo un client TCP generico che verrà avviato nel modo seguente: java cltTCPgenerique server porta

Questo client TCP si connetterà alla porta port del server serveur. Una volta fatto ciò, creerà due thread:

  1. un thread incaricato di leggere i comandi digitati sulla tastiera e di inviarli al server
  2. un thread incaricato di leggere le risposte del server e di visualizzarle sullo schermo

Perché due thread, mentre nell’applicazione precedente non se ne era sentita la necessità? In quest’ultima, il protocollo di dialogo era noto: il client inviava una sola riga e il server rispondeva con una sola riga. Ogni servizio ha il proprio protocollo specifico e si possono verificare anche le seguenti situazioni:

  • il client deve inviare più righe di testo prima di ricevere una risposta
  • la risposta di un server può comprendere più righe di testo

Pertanto, il ciclo «invio di una singola riga al server - ricezione di una singola riga inviata dal server» non è sempre adeguato. Creeremo quindi due cicli distinti:

  • un ciclo di lettura dei comandi digitati sulla tastiera da inviare al server. L’utente segnalerà la fine dei comandi con la parola chiave fin.
  • un ciclo di ricezione e visualizzazione delle risposte del server. Si tratterà di un ciclo infinito che verrà interrotto solo dalla chiusura del flusso di rete da parte del server o dall’utente tramite la tastiera, digitando il comando fin.

Per avere questi due cicli separati, abbiamo bisogno di due thread indipendenti. Vediamo un esempio di esecuzione in cui il nostro client TCP generico si connette a un servizio SMTP (SendMail Transfer Protocol). Questo servizio è responsabile dell’inoltro della posta elettronica ai destinatari. Funziona sulla porta 25 e utilizza un protocollo di comunicazione basato sullo scambio di righe di testo.


Dos>java clientTCPgenerique istia.univ-angers.fr 25
Commandes :
<-- 220 istia.univ-angers.fr ESMTP Sendmail 8.11.6/8.9.3; Mon, 13 May 2002 08:37:26 +0200
help
<-- 502 5.3.0 Sendmail 8.11.6 -- HELP not implemented
mail from: machin@univ-angers.fr
<-- 250 2.1.0 machin@univ-angers.fr... Sender ok
rcpt to: serge.tahe@istia.univ-angers.fr
<-- 250 2.1.5 serge.tahe@istia.univ-angers.fr... Recipient ok
data
<-- 354 Enter mail, end with "." on a line by itself
Subject: test

ligne1
ligne2
ligne3
.
<-- 250 2.0.0 g4D6bks25951 Message accepted for delivery
quit
<-- 221 2.0.0 istia.univ-angers.fr closing connection
[fin du thread de lecture des réponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]

Commentiamo questi scambi client-server:

  • il servizio SMTP invia un messaggio di benvenuto quando un client si connette ad esso:
<-- 220 istia.univ-angers.fr ESMTP Sendmail 8.11.6/8.9.3; Mon, 13 May 2002 08:37:26 +0200
  • alcuni servizi dispongono di un comando help che fornisce indicazioni sui comandi utilizzabili con il servizio. In questo caso non è così. I comandi SMTP utilizzati nell'esempio sono i seguenti:
    • mail from: expéditeur, per indicare l'indirizzo e-mail del mittente del messaggio
    • rcpt to: destinataire, per indicare l’indirizzo e-mail del destinatario del messaggio. Se ci sono più destinatari, il comando rcpt to: va ripetuto tante volte quante necessarie per ciascuno di essi.
    • data, che segnala al server SMTP che si sta per inviare il messaggio. Come indicato nella risposta del server, il messaggio è costituito da una sequenza di righe terminata da una riga contenente il solo carattere punto. Un messaggio può avere delle intestazioni separate dal corpo del messaggio da una riga vuota. Nel nostro esempio, abbiamo inserito un oggetto con la parola chiave Subject:
  • una volta inviato il messaggio, è possibile indicare al server che si è terminato con il comando quit. Il server chiude quindi la connessione di rete. Il thread di lettura può rilevare questo evento e arrestarsi.
  • L’utente digita quindi «fin» sulla tastiera per arrestare anche il thread di lettura dei comandi digitati sulla tastiera.

Se controlliamo la posta ricevuta, troviamo quanto segue (Outlook):

Image

Si noti che il servizio SMTP non è in grado di rilevare se un mittente sia valido o meno. Pertanto, non ci si può mai fidare del campo from di un messaggio. In questo caso, il mittente machin@univ-angers.fr non esisteva.

Questo client TCP generico ci permette di individuare il protocollo di comunicazione dei servizi Internet e, partendo da lì, di creare classi specializzate per i client di tali servizi. Scopriamo il protocollo di comunicazione del servizio POP (Post Office Protocol), che consente di recuperare le proprie e-mail archiviate su un server. Funziona sulla porta 110.


Dos> java clientTCPgenerique istia.univ-angers.fr 110
Commandes :
<-- +OK Qpopper (version 4.0.3) at istia.univ-angers.fr starting.
help
<-- -ERR Unknown command: "help".
user st
<-- +OK Password required for st.
pass monpassword
<-- +OK st has 157 visible messages (0 hidden) in 11755927 octets.
list
<-- +OK 157 visible messages (11755927 octets)
<-- 1 892847
<-- 2 171661
...
<-- 156 2843
<-- 157 2796
<-- .
retr 157
<-- +OK 2796 octets
<-- Received: from lagaffe.univ-angers.fr (lagaffe.univ-angers.fr [193.49.144.1])
<--     by istia.univ-angers.fr (8.11.6/8.9.3) with ESMTP id g4D6wZs26600;
<--     Mon, 13 May 2002 08:58:35 +0200
<-- Received: from jaume ([193.49.146.242])
<--     by lagaffe.univ-angers.fr (8.11.1/8.11.2/GeO20000215) with SMTP id g4D6wSd37691;
<--     Mon, 13 May 2002 08:58:28 +0200 (CEST)
...
<-- ------------------------------------------------------------------------
<-- NOC-RENATER2                  Tl.  : 0800 77 47 95
<-- Fax : (+33) 01 40 78 64 00 ,  Email : noc-r2@cssi.renater.fr
<-- ------------------------------------------------------------------------
<--
<-- .
quit
<-- +OK Pop server at istia.univ-angers.fr signing off.
[fin du thread de lecture des réponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]

I comandi principali sono i seguenti:

  • user login, dove si inserisce il proprio nome utente sul server che ospita le nostre e-mail
  • password password, dove si inserisce la password associata al login precedente
  • list, per ottenere l'elenco dei messaggi sotto forma di numero e dimensione in byte
  • retr i, per leggere il messaggio n. i
  • quit, per terminare la sessione.

Scopriamo ora il protocollo di dialogo tra un client e un server Web che solitamente opera sulla porta 80:


Dos> java clientTCPgenerique istia.univ-angers.fr 80
Commandes :
GET /index.html HTTP/1.0

<-- HTTP/1.1 200 OK
<-- Date: Mon, 13 May 2002 07:30:58 GMT
<-- Server: Apache/1.3.12 (Unix)  (Red Hat/Linux) PHP/3.0.15 mod_perl/1.21
<-- Last-Modified: Wed, 06 Feb 2002 09:00:58 GMT
<-- ETag: "23432-2bf3-3c60f0ca"
<-- Accept-Ranges: bytes
<-- Content-Length: 11251
<-- Connection: close
<-- Content-Type: text/html
<--
<-- <html>
<--
<-- <head>
<-- <meta http-equiv="Content-Type"
<-- content="text/html; charset=iso-8859-1">
<-- <meta name="GENERATOR" content="Microsoft FrontPage Express 2.0">
<-- <title>Bienvenue a l'ISTIA - Universite d'Angers</title>
<-- </head>
....
<-- face="Verdana"> - Dernire mise  jour le <b>10 janvier 2002</b></font></p>
<-- </body>
<-- </html>
<--
[fin du thread de lecture des réponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]

Un client Web invia i propri comandi al server secondo lo schema seguente:

commande1
commande2
...
commanden
[ligne vide]

Il server Web risponde solo dopo aver ricevuto la riga vuota. Nell'esempio abbiamo utilizzato un solo comando:

GET /index.html HTTP/1.0

che richiede al server il file URL /index.html e indica che sta operando con il protocollo HTTP versione 1.0. La versione più recente di questo protocollo è la 1.1. L'esempio mostra che il server ha risposto inviando il contenuto del file index.html e poi ha chiuso la connessione, poiché si vede che il thread di lettura delle risposte si è concluso. Prima di inviare il contenuto del file index.html, il server web ha inviato una serie di intestazioni terminata da una riga vuota:

<-- HTTP/1.1 200 OK
<-- Date: Mon, 13 May 2002 07:30:58 GMT
<-- Server: Apache/1.3.12 (Unix)  (Red Hat/Linux) PHP/3.0.15 mod_perl/1.21
<-- Last-Modified: Wed, 06 Feb 2002 09:00:58 GMT
<-- ETag: "23432-2bf3-3c60f0ca"
<-- Accept-Ranges: bytes
<-- Content-Length: 11251
<-- Connection: close
<-- Content-Type: text/html
<--
<-- <html>

La riga <html> è la prima riga del file /index.html. Quanto sopra è denominato intestazioni HTTP (HyperText Transfer Protocol). Non entreremo qui nei dettagli di queste intestazioni, ma ricordiamo che il nostro client generico ne consente l’accesso, il che può essere utile per comprenderle. Ad esempio, la prima riga:

<-- HTTP/1.1 200 OK

indica che il server Web contattato supporta il protocollo HTTP/1.1 e che ha effettivamente trovato il file richiesto (200 OK), dove 200 è un codice di risposta HTTP. Le righe

<-- Content-Length: 11251
<-- Connection: close
<-- Content-Type: text/html

indicano al client che riceverà 11251 byte corrispondenti al testo HTML (HyperText Markup Language) e che, al termine dell’invio, la connessione verrà chiusa.

Abbiamo quindi un client TCP molto pratico. Senza dubbio fa meno del programma telnet che abbiamo utilizzato in precedenza, ma è stato interessante scriverlo da soli. Il programma del client TCP generico è il seguente:

// pacchetti importati
import java.io.*;
import java.net.*;

public class clientTCPgenerique{

    // riceve come parametro le caratteristiche di un servizio nella forma
     // server porta
     // si connette al servizio
     // crea un thread per leggere i comandi digitati sulla tastiera
     // questi verranno inviati al server
     // crea un thread per leggere le risposte del server
     // queste verranno visualizzate sullo schermo
     // il tutto termina con il comando «fine» digitato sulla tastiera

   // variabile di istanza
  private static Socket client;

    public static void main(String[] args){

         // sintassi
        final String syntaxe="pg serveur port";

         // numero di argomenti
        if(args.length != 2)
            erreur(syntaxe,1);

         // si annota il nome del server
        String serveur=args[0];

         // la porta deve essere un numero intero >0
        int port=0;
        boolean erreurPort=false;
        Exception E=null;
        try{
            port=Integer.parseInt(args[1]);
        }catch(Exception e){
            E=e;
            erreurPort=true;
        }
        erreurPort=erreurPort || port <=0;
        if(erreurPort)
            erreur(syntaxe+"\n"+"Port incorrect ("+E+")",2);

        client=null;
         // potrebbero verificarsi dei problemi
        try{
             // si effettua la connessione al servizio
            client=new Socket(serveur,port);
        }catch(Exception ex){
             // errore
            erreur("Impossible de se connecter au service ("+ serveur
                +","+port+"), erreur : "+ex.getMessage(),3);
             // fine
            return;
        }//catch

         // si creano i thread di lettura/scrittura
    new ClientSend(client).start();
    new ClientReceive(client).start();

        // fine thread principale
        return;
    }// main

     // visualizzazione degli errori
    public static void erreur(String msg, int exitCode){
         // visualizzazione dell'errore
        System.err.println(msg);
         // arresto con errore
        System.exit(exitCode);
    }//errore
}//classe  

class ClientSend extends Thread {
    // classe incaricata di leggere i comandi digitati sulla tastiera
     // e di inviarli a un server tramite un client TCP passato come parametro

    private Socket client;    // il client TCP

     // costruttore
    public ClientSend(Socket client){
         // si specifica il client TCP
        this.client=client;
    }//costruttore

     // metodo Run del thread
    public void run(){

        // dati locali
        PrintWriter OUT=null;            // flusso di scrittura di rete
    BufferedReader IN=null;        // flusso da tastiera
        String commande=null;            // comando letto dalla tastiera

         // gestione degli errori
        try{
             // creazione del flusso di scrittura di rete
            OUT=new PrintWriter(client.getOutputStream(),true);
      // creazione del flusso di input da tastiera
      IN=new BufferedReader(new InputStreamReader(System.in));
            // ciclo di immissione e invio dei comandi
            System.out.println("Commandes : ");
            while(true){
                 // lettura del comando digitato sulla tastiera
                commande=IN.readLine().trim();
                // finito?
                if (commande.toLowerCase().equals("fin")) break;
                // invio del comando al server
                OUT.println(commande);
                 // comando successivo
            }//while
        }catch(Exception ex){
             // errore
            System.err.println("Envoi : L'erreur suivante s'est produite : " + ex.getMessage());
        }//catch
         // fine - si chiudono i flussi
        try{
            OUT.close();client.close();
        }catch(Exception ex){}
         // si segnala la fine del thread
        System.out.println("[Envoi : fin du thread d'envoi des commandes au serveur]");
    }//run
}//classe

class ClientReceive extends Thread{
    // classe incaricata di leggere le righe di testo destinate a un 
     // client TCP passato come parametro

    private Socket client;    // il client TCP

     // costruttore
    public ClientReceive(Socket client){
         // si registra il client TCP
        this.client=client;
    }//costruttore

     // metodo Run del thread
    public void run(){

        // dati locali
        BufferedReader IN=null;        // flusso di lettura di rete
        String réponse=null;        // risposta del server

         // gestione degli errori
        try{
             // creazione del flusso di lettura di rete
            IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
            // ciclo di lettura delle righe di testo del flusso IN
            while(true){
                 // lettura flusso di rete
                réponse=IN.readLine();
                 // flusso chiuso?
                if(réponse==null) break;
                // visualizzazione
                System.out.println("<-- "+réponse);
            }//while
        }catch(Exception ex){
            // errore
            System.err.println("Réception : L'erreur suivante s'est produite : " + ex.getMessage());
        }//catch
         // fine - si chiudono i flussi
        try{
            IN.close();client.close();
        }catch(Exception ex){}
         // si segnala la fine del thread
        System.out.println("[Réception : fin du thread de lecture des réponses du serveur]");
    }//run
}//classe

8.4.4. Un server TCP generico

Ora ci occupiamo di un server

  • che visualizza sullo schermo i comandi inviati dai propri client
  • e invia loro in risposta le righe di testo digitate sulla tastiera da un utente. È quindi quest’ultimo a fungere da server.

Il programma viene avviato con: java serveurTCPgenerique portEcoute, dove portEcoute è la porta a cui i client devono connettersi. Il servizio al client sarà gestito da due thread:

  • un thread dedicato esclusivamente alla lettura delle righe di testo inviate dal client
  • un thread dedicato esclusivamente alla lettura delle risposte digitate dall’utente sulla tastiera. Quest’ultimo segnalerà, tramite il comando «fin», la chiusura della connessione con il client.

Il server crea due thread per ogni client. Se ci sono n client, ci saranno 2n thread attivi contemporaneamente. Il server, dal canto suo, non si arresta mai, a meno che l’utente non prema Ctrl-C sulla tastiera. Vediamo alcuni esempi.

Il server viene avviato sulla porta 100 e si utilizza il client generico per comunicare con esso. La finestra del client è la seguente:

E:\data\serge\MSNET\c#\rete\client tcp generico> java clientTCPgenerique localhost 100
Commandes :
commande 1 du client 1
<-- réponse 1 au client 1
commande 2 du client 1
<-- réponse 2 au client 1
fin
L'erreur suivante s'est produite : Impossible de lire les données de la connexion de transport.
[fin du thread de lecture des réponses du serveur]
[fin du thread d'envoi des commandes au serveur]

Le righe che iniziano con <-- sono quelle inviate dal server al client, le altre sono quelle inviate dal client al server. La finestra del server è la seguente:


Dos> java serveurTCPgenerique 100
Serveur générique lancé sur le port 100
Thread de lecture des réponses du serveur au client 1 lancé
1 : Thread de lecture des demandes du client 1 lancé
<-- commande 1 du client 1
réponse 1 au client 1
1 : <-- commande 2 du client 1
réponse 2 au client 1
1 : [fin du Thread de lecture des demandes du client 1]
fin
[fin du Thread de lecture des réponses du serveur au client 1]

Le righe che iniziano con <-- sono quelle inviate dal client al server. Le righe N: sono quelle inviate dal server al client n. N. Il server sopra indicato è ancora attivo, mentre il client 1 ha terminato l'operazione. Si avvia un secondo client per lo stesso server:


Dos> java clientTCPgenerique localhost 100
Commandes :
commande 3 du client 2
<-- réponse 3 au client 2
fin
L'erreur suivante s'est produite : Impossible de lire les données de la connexion de transport.
[fin du thread de lecture des réponses du serveur]
[fin du thread d'envoi des commandes au serveur]

La finestra del server appare quindi così:


Dos> java serveurTCPgenerique 100
Serveur générique lancé sur le port 100
Thread de lecture des réponses du serveur au client 1 lancé
1 : Thread de lecture des demandes du client 1 lancé
<-- commande 1 du client 1
réponse 1 au client 1
1 : <-- commande 2 du client 1
réponse 2 au client 1
1 : [fin du Thread de lecture des demandes du client 1]
fin
[fin du Thread de lecture des réponses du serveur au client 1]
Thread de lecture des réponses du serveur au client 2 lancé
2 : Thread de lecture des demandes du client 2 lancé
<-- commande 3 du client 2
réponse 3 au client 2
2 : [fin du Thread de lecture des demandes du client 2]
fin
[fin du Thread de lecture des réponses du serveur au client 2]
^C

Simuliamo ora un server web avviando il nostro server generico sulla porta 88:


Dos> java serveurTCPgenerique 88
Serveur générique lancé sur le port 88

Apriamo ora un browser e richiediamo l'http://localhost:88/exemple.html URL. Il browser si connetterà quindi alla porta 88 del computer localhost e richiederà la pagina /exemple.html:

Image

Diamo ora un’occhiata alla finestra del nostro server:

Dos>java serveurTCPgenerique 88
Serveur générique lancé sur le port 88
Thread de lecture des réponses du serveur au client 2 lancé
2 : Thread de lecture des demandes du client 2 lancé
<-- GET /exemple.html HTTP/1.1
<-- Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/msword, */*
<-- Accept-Language: fr
<-- Accept-Encoding: gzip, deflate
<-- User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.0.3705; .NET CLR 1.0.2
914)
<-- Host: localhost:88
<-- Connection: Keep-Alive
<--

Si scoprono così le intestazioni HTTP inviate dal browser. Questo ci permette di scoprire poco a poco il protocollo HTTP. In un esempio precedente, avevamo creato un client Web che inviava solo il comando GET. Era stato sufficiente. Qui vediamo che il browser invia altre informazioni al server. Il loro scopo è indicare al server quale tipo di client ha di fronte. Notiamo inoltre che le intestazioni HTTP terminano con una riga vuota.

Elaboriamo una risposta per il nostro client. L’utente alla tastiera è in questo caso il vero server e può elaborare una risposta manualmente. Ricordiamo la risposta fornita da un server Web in un esempio precedente:

<-- HTTP/1.1 200 OK
<-- Date: Mon, 13 May 2002 07:30:58 GMT
<-- Server: Apache/1.3.12 (Unix)  (Red Hat/Linux) PHP/3.0.15 mod_perl/1.21
<-- Last-Modified: Wed, 06 Feb 2002 09:00:58 GMT
<-- ETag: "23432-2bf3-3c60f0ca"
<-- Accept-Ranges: bytes
<-- Content-Length: 11251
<-- Connection: close
<-- Content-Type: text/html
<--
<-- <html>

Proviamo a dare una risposta analoga:

...
<-- Host: localhost:88
<-- Connection: Keep-Alive
<--
2 : HTTP/1.1 200 OK
2 : Server: serveur tcp generique
2 : Connection: close
2 : Content-Type: text/html
2 :
2 : <html>
2 :   <head><title>Serveur generique</title></head>
2 :   <body>
2 :     <center>
2 :       <h2>Reponse du serveur generique</h2>
2 :     </center>
2 :    </body>
2 : </html>
2 : fin
L'erreur suivante s'est produite : Impossible de lire les données de la connexion de transport.
[fin du Thread de lecture des demandes du client 2]
[fin du Thread de lecture des réponses du serveur au client 2]

Le righe che iniziano con 2: vengono inviate dal server al client n. 2. Il comando fin chiude la connessione tra il server e il client. Nella nostra risposta ci siamo limitati alle seguenti intestazioni HTTP:

HTTP/1.1 200 OK
2 : Server: serveur tcp generique
2 : Connection: close
2 : Content-Type: text/html
2 :

Non specifichiamo la dimensione del file che stiamo per inviare (Content-Length), ma ci limitiamo a indicare che chiuderemo la connessione (Connection: close) dopo averlo inviato. Questo è sufficiente per il browser. Quando rileva che la connessione è stata chiusa, il browser capirà che la risposta del server è terminata e visualizzerà la pagina HTML che gli è stata inviata. Quest’ultima è la seguente:

2 : <html>
2 :   <head><title>Serveur generique</title></head>
2 :   <body>
2 :     <center>
2 :       <h2>Reponse du serveur generique</h2>
2 :     </center>
2 :    </body>
2 : </html>

L'utente chiude quindi la connessione al client digitando il comando fin. Il browser capisce così che la risposta del server è terminata e può quindi visualizzarla:

Image

Se, come sopra, si esegue il comando View/Source per vedere cosa ha ricevuto il browser, si ottiene:

Image

ovvero esattamente ciò che è stato inviato dal server generico.

Il codice del server TCP generico è il seguente:

// pacchetti
import java.io.*;
import java.net.*;

public class serveurTCPgenerique{

    // programma principale
    public static void main (String[] args){

    // riceve le richieste dei client sulla porta di ascolto
     // crea un thread per leggere le richieste del client
     // queste verranno visualizzate sullo schermo
     // crea un thread per leggere i comandi digitati sulla tastiera
     // questi dati verranno inviati al cliente come risposta
     // il tutto termina con il comando "fin" digitato sulla tastiera

    final String syntaxe="Syntaxe : pg port";
   // variabile di istanza
         // c'è un argomento
     if(args.length != 1)
        erreur(syntaxe,1);

         // la porta deve essere un numero intero >0
        int port=0;
        boolean erreurPort=false;
        Exception E=null;
        try{
            port=Integer.parseInt(args[0]);
        }catch(Exception e){
            E=e;
            erreurPort=true;
        }
        erreurPort=erreurPort || port <=0;
        if(erreurPort)
            erreur(syntaxe+"\n"+"Port incorrect ("+E+")",2);

     // si crea il servizio di ascolto
    ServerSocket ecoute=null;
    int nbClients=0;    // numero di client gestiti
        try{
             // si crea il servizio
            ecoute=new ServerSocket(port);
             // monitoraggio
            System.out.println("Serveur générique lancé sur le port " + port);

             // ciclo di servizio ai clienti
            Socket client=null;
            while (true){ // ciclo infinito - verrà interrotto con Ctrl-C
                 // in attesa di un cliente
                client=ecoute.accept();

                 // il servizio è gestito da thread separati
                nbClients++;

                 // vengono creati i thread di lettura/scrittura
        new ServeurSend(client,nbClients).start();
        new ServeurReceive(client,nbClients).start();

                // si torna ad ascoltare le richieste
            }// fine ciclo while
        }catch(Exception ex){
             // si segnala l'errore
            erreur("L'erreur suivante s'est produite : " + ex.getMessage(),3);
        }//catch
    }// fine main

     // visualizzazione degli errori
    public static void erreur(String msg, int exitCode){
         // visualizzazione dell'errore
        System.err.println(msg);
         // arresto con errore
        System.exit(exitCode);
    }//errore
}//classe

class ServeurSend extends Thread{
    // classe incaricata di leggere le risposte digitate sulla tastiera
     // e di inviarle a un client tramite un client TCP passato al costruttore

    Socket client;    // il client TCP
    int numClient;        // numero del client

     // costruttore
    public ServeurSend(Socket client, int numClient){
         // si annota il client TCP
        this.client=client;
         // e il relativo n.
        this.numClient=numClient;
    }//produttore

     // metodo Run del thread
    public void run(){

        // dati locali
        PrintWriter OUT=null;        // flusso di scrittura di rete
        String réponse=null;        // risposta letta dalla tastiera
    BufferedReader IN=null;    // flusso da tastiera

        // monitoraggio
        System.out.println("Thread de lecture des réponses du serveur au client "+ numClient + " lancé");
         // gestione degli errori
        try{
             // creazione del flusso di scrittura di rete
            OUT=new PrintWriter(client.getOutputStream(),true);
      // creazione del flusso da tastiera
      IN=new BufferedReader(new InputStreamReader(System.in));
            // ciclo di immissione e invio dei comandi
            while(true){
                 // identificazione del cliente
                System.out.print("--> " + numClient + " : ");
                 // Lettura della risposta digitata sulla tastiera
                réponse=IN.readLine().trim();
                // finito?
                if (réponse.toLowerCase().equals("fin")) break;
                // invio della risposta al server
                OUT.println(réponse);
                 // risposta successiva
            }//while
        }catch(Exception ex){
             // errore
            System.err.println("L'erreur suivante s'est produite : " + ex.getMessage());
        }//catch
         // fine - si chiudono i flussi
        try{
            OUT.close();client.close();
        }catch(Exception ex){}
         // si segnala la fine del thread
        System.out.println("[fin du Thread de lecture des réponses du serveur au client "+ numClient+ "]");
    }//run
}//classe

class ServeurReceive extends Thread{
    // classe incaricata di leggere le righe di testo inviate al server 
     // tramite un client TCP passato al costruttore

    Socket client;    // il client TCP
    int numClient;        // numero del client

     // costruttore
    public ServeurReceive(Socket client, int numClient){
         // si registra il client TCP
        this.client=client;
         // e il suo n.
        this.numClient=numClient;
    }//produttore

     // metodo Run del thread
    public void run(){

        // dati locali
        BufferedReader IN=null;        // flusso di lettura di rete
        String réponse=null;        // risposta del server

         // monitoraggio
        System.out.println("Thread de lecture des demandes du client "+ numClient + " lancé");
         // gestione degli errori
        try{
             // creazione del flusso di lettura di rete
            IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
            // ciclo di lettura delle righe di testo del flusso IN
            while(true){
                 // lettura del flusso di rete
                réponse=IN.readLine();
                 // flusso chiuso?
                if(réponse==null) break;
                // visualizzazione
                System.out.println("<-- "+réponse);
            }//while
        }catch(Exception ex){
            // errore
            System.err.println("L'erreur suivante s'est produite : " + ex.getMessage());
        }//catch
         // fine - si chiudono i flussi
        try{
            IN.close();client.close();
        }catch(Exception ex){}
         // si segnala la fine del thread
        System.out.println("[fin du Thread de lecture des demandes du client "+ numClient+"]");
    }//run
}//classe

8.4.5. Un client Web

Nell'esempio precedente abbiamo visto alcune delle intestazioni HTTP inviate da un browser:

<-- GET /exemple.html HTTP/1.1
<-- Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/msword, */*
<-- Accept-Language: fr
<-- Accept-Encoding: gzip, deflate
<-- User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.0.3705; .NET CLR 1.0.2
914)
<-- Host: localhost:88
<-- Connection: Keep-Alive
<--

Scriveremo un client Web a cui verrà passato come parametro un URL e che visualizzerà sullo schermo il contenuto di questo URL. Supporremo che il server Web contattato per l'URL supporti il protocollo HTTP 1.1. Tra le intestazioni precedenti, utilizzeremo solo le seguenti:

<-- GET /exemple.html HTTP/1.1
<-- Host: localhost:88
<-- Connection: close
  • la prima intestazione indica quale pagina desideriamo
  • il secondo, quale server stiamo interrogando
  • il terzo che desideriamo che il server chiuda la connessione dopo averci risposto.

Se, nell’esempio sopra riportato, sostituiamo GET con HEAD, il server ci invierà solo le intestazioni HTTP e non la pagina HTML.

Il nostro client web verrà richiamato nel modo seguente: java clientweb URL cmd, dove URL è l’URL desiderata e cmd una delle due parole chiave GET o HEAD per indicare se si desiderano solo le intestazioni (HEAD) o anche il contenuto della pagina (GET). Vediamo un primo esempio. Avviamo il server IIS e poi il client web sulla stessa macchina:

dos>java clientweb http://localhost HEAD
HTTP/1.1 302 Object moved
Server: Microsoft-IIS/5.0
Date: Mon, 13 May 2002 09:23:37 GMT
Connection: close
Location: /IISSamples/Default/welcome.htm
Content-Length: 189
Content-Type: text/html
Set-Cookie: ASPSESSIONIDGQQQGUUY=HMFNCCMDECBJJBPPBHAOAJNP; path=/
Cache-control: private

La risposta

HTTP/1.1 302 Object moved

significa che la pagina richiesta è stata spostata (quindi da URL). Il nuovo URL è indicato dall'intestazione Location:

Location: /IISSamples/Default/welcome.htm

Se utilizziamo GET al posto di HEAD nella chiamata al client Web:

dos>java clientweb http://localhost GET
HTTP/1.1 302 Object moved
Server: Microsoft-IIS/5.0
Date: Mon, 13 May 2002 09:33:36 GMT
Connection: close
Location: /IISSamples/Default/welcome.htm
Content-Length: 189
Content-Type: text/html
Set-Cookie: ASPSESSIONIDGQQQGUUY=IMFNCCMDAKPNNGMGMFIHENFE; path=/
Cache-control: private

<head><title>L'objet a changé d'emplacement</title></head>
<body><h1>L'objet a changé d'emplacement</h1>Cet objet peut être trouvé <a HREF="/IISSamples/Default/we
lcome.htm">ici</a>.</body>

Otteniamo lo stesso risultato di HEAD, con in più il corpo della pagina HTML. Il programma è il seguente:

// pacchetti importati
import java.io.*;
import java.net.*;

public class clientweb{

    // richiede un URL
     // visualizza il contenuto di quest'ultima sullo schermo

    public static void main(String[] args){
        // sintassi
        final String syntaxe="pg URI GET/HEAD";

        // numero di argomenti
        if(args.length != 2)
            erreur(syntaxe,1);

         // si rileva il codice URI richiesto
        String URLString=args[0];
        String commande=args[1].toUpperCase();

        // verifica della validità di URI
        URL url=null;
        try{
            url=new URL(URLString);
        }catch (Exception ex){
             // URI non corretto
            erreur("L'erreur suivante s'est produite : " + ex.getMessage(),2);
        }//intercettazione
         // verifica dell'ordine
        if(! commande.equals("GET") && ! commande.equals("HEAD")){
            // ordine non corretto
            erreur("Le second paramètre doit être GET ou HEAD",3);
        }

         // si estraggono le informazioni utili da URL
    String path=url.getPath();
    if(path.equals("")) path="/";
    String query=url.getQuery();
    if(query!=null) query="?"+query; else query="";
    String host=url.getHost();
    int port=url.getPort();
    if(port==-1) port=url.getDefaultPort();

         // si può procedere
        Socket  client=null;                        // il cliente
        BufferedReader IN=null;                    // il flusso di lettura del cliente
        PrintWriter OUT=null;                        // il flusso di scrittura del client
        String réponse=null;                        // risposta del server
        try{
             // ci si connette al server
            client=new Socket(host,port);

            // si creano i flussi di ingresso-uscita del client TCP
            IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
            OUT=new PrintWriter(client.getOutputStream(),true);

            // richiesta di URL - invio delle intestazioni HTTP
            OUT.println(commande + " " + path + query + " HTTP/1.1");
            OUT.println("Host: " + host + ":" + port);
            OUT.println("Connection: close");
            OUT.println();
             // si legge la risposta
            while((réponse=IN.readLine())!=null){
                 // si elabora la risposta
                System.out.println(réponse);
            }//while
             // è terminato
            client.close();
        } catch(Exception e){
            // si gestisce l'eccezione
            erreur(e.getMessage(),4);
        }//catch
    }//main

     // visualizzazione degli errori
    public static void erreur(String msg, int exitCode){
         // visualizzazione dell'errore
        System.err.println(msg);
         // interruzione con errore
        System.exit(exitCode);
    }//errore
}//classe

L'unica novità in questo programma è l'utilizzo della classe URL. Il programma riceve un URL (Uniform Resource Locator) o un URI (Uniform Resource Identifier) della forma http://serveur:port/cheminPageHTML?param1=val1;param2=val2;.... La classe URL ci permette di scomporre la stringa URL nei suoi diversi elementi. Un oggetto URL viene costruito a partire dalla stringa URLstring ricevuta come parametro:

         // verifica validità di URL
        URL url=null;
        try{
            url=new URL(URLString);
        }catch (Exception ex){
             // URI non corretto
            erreur("L'erreur suivante s'est produite : " + ex.getMessage(),2);
        }//catch

Se la stringa URL ricevuta come parametro non è un URL valido (mancanza del protocollo, del server, ecc.), viene generata un'eccezione. Ciò ci consente di verificare la validità del parametro ricevuto. Una volta creato l’oggetto URL, è possibile accedere ai suoi vari elementi. Pertanto, se l’oggetto url del codice precedente è stato creato a partire dalla stringa

http://server:porta/cheminPageHTML?param1=val1;param2=val2;... 

si avrà:

url.getHost()=serveur

url.getPort()=port oppure -1 se la porta non è specificata

url.getPath()=cheminPageHTML oppure la stringa vuota se non è presente alcun percorso

url.getQuery()=param1=val1;param2=val2;... oppure null se non c'è una richiesta

uri.getProtocol()=http

8.4.6. Client Web che gestisce i reindirizzamenti

Il client Web precedente non gestisce un eventuale reindirizzamento dell'URL che ha richiesto. Il client successivo lo gestisce.

  1. Legge la prima riga delle intestazioni HTTP inviate dal server per verificare se vi sia la stringa «302 Object moved», che indica un reindirizzamento
  2. legge le intestazioni successive. Se c’è un reindirizzamento, cerca la riga «Location: url» che fornisce il nuovo URL della pagina richiesta e ne prende nota.
  3. visualizza il resto della risposta del server. Se è presente un reindirizzamento, i passaggi da 1 a 3 vengono ripetuti con il nuovo URL. Il programma non accetta più di un reindirizzamento. Questo limite è definito da una costante che può essere modificata.

Ecco un esempio:

Dos>java clientweb2 http://localhost GET
HTTP/1.1 302 Object moved
Server: Microsoft-IIS/5.0
Date: Mon, 13 May 2002 11:38:55 GMT
Connection: close
Location: /IISSamples/Default/welcome.htm
Content-Length: 189
Content-Type: text/html
Set-Cookie: ASPSESSIONIDGQQQGUUY=PDGNCCMDNCAOFDMPHCJNPBAI; path=/
Cache-control: private

<head><title>L'objet a chang d'emplacement</title></head>
<body><h1>L'objet a chang d'emplacement</h1>Cet objet peut tre trouv <a HREF="/IISSamples/Default/we
lcome.htm">ici</a>.</body>

<--Redirection vers l'URL http://localhost:80/IISSamples/Default/welcome.htm-->

HTTP/1.1 200 OK
Server: Microsoft-IIS/5.0
Connection: close
Date: Mon, 13 May 2002 11:38:55 GMT
Content-Type: text/html
Accept-Ranges: bytes
Last-Modified: Mon, 16 Feb 1998 21:16:22 GMT
ETag: "0174e21203bbd1:978"
Content-Length: 4781

<html>

<head>
<title>Bienvenue dans le Serveur Web personnel</title>
</head>
....
</body>
</html>

Il programma è il seguente:

// pacchetti importati
import java.io.*;
import java.net.*;
import java.util.regex.*;

public class clientweb2{

    // richiede un URL
     // visualizza il contenuto di quest'ultimo sullo schermo

    public static void main(String[] args){
        // sintassi
        final String syntaxe="pg URL GET/HEAD";

        // numero di argomenti
        if(args.length != 2)
            erreur(syntaxe,1);

         // si prende nota dell'URI richiesto
        String URLString=args[0];
        String commande=args[1].toUpperCase();

        // verifica della validità di URI
        URL url=null;
        try{
            url=new URL(URLString);
        }catch (Exception ex){
             // URI non corretto
            erreur("L'erreur suivante s'est produite : " + ex.getMessage(),2);
        }//intercettazione
         // verifica dell'ordine
        if(! commande.equals("GET") && ! commande.equals("HEAD")){
            // ordine non corretto
            erreur("Le second paramètre doit être GET ou HEAD",3);
        }

         // si può procedere
        Socket  client=null;                        // il cliente
        BufferedReader IN=null;                    // il flusso di lettura del cliente
        PrintWriter OUT=null;                        // flusso di scrittura del client
        String réponse=null;                        // risposta del server
        final int nbRedirsMax=1;                // non è consentito più di un reindirizzamento
        int nbRedirs=0;                                    // numero di reindirizzamenti in corso
        String premièreLigne;                        // prima riga della risposta
        boolean redir=false;                        // indica se c'è o meno un reindirizzamento
        String locationString="";                // la stringa URL di un eventuale reindirizzamento

         // espressione regolare per individuare una stringa di reindirizzamento URL
        Pattern location=Pattern.compile("^Location: (.+?)$");

         // gestione degli errori
        try{
             // è possibile avere più URL da interrogare in caso di reindirizzamenti
            while(nbRedirs<=nbRedirsMax){

                // si estraggono le informazioni utili dal URL
            String protocol=url.getProtocol();
            String path=url.getPath();
            if(path.equals("")) path="/";
            String query=url.getQuery();
            if(query!=null) query="?"+query; else query="";
            String host=url.getHost();
            int port=url.getPort();
            if(port==-1) port=url.getDefaultPort();

                 // ci si connette al server
                client=new Socket(host,port);

                // si creano i flussi di ingresso-uscita del client TCP
                IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
                OUT=new PrintWriter(client.getOutputStream(),true);

                // si richiede l'URL - invio delle intestazioni HTTP
                OUT.println(commande + " " + path + query + " HTTP/1.1");    
                OUT.println("Host: " + host + ":" + port);
                OUT.println("Connection: close");
                OUT.println();

                 // si legge la prima riga della risposta
                premièreLigne=IN.readLine();
                 // echo sullo schermo
                System.out.println(premièreLigne);

                // reindirizzamento?
        if(premièreLigne.endsWith("302 Object moved")){     
                    // c'è un reindirizzamento
                    redir=true;
                    nbRedirs++;
                }//if

                // le intestazioni HTTP successive fino a trovare la riga vuota che segnala la fine delle intestazioni
                boolean locationFound=false;
                while(!(réponse=IN.readLine()).equals("")){
                    // si visualizza la risposta
                    System.out.println(réponse);
                     // se c'è un reindirizzamento, si cerca l'intestazione Location
                    if(redir && ! locationFound){
                        // si confronta la riga con l'espressione relazionale location
                        Matcher résultat=location.matcher(réponse);
                        if(résultat.find()){
                            // se la ricerca ha esito positivo, si annota il codice di reindirizzamento URL
                            locationString=résultat.group(1);             
                             // si registra che è stato trovato
                            locationFound=true;
                        }//if
                    }//se
                    // intestazione seguente
                }//mentre

                 // righe successive della risposta
                System.out.println(réponse);
                while((réponse=IN.readLine())!=null){
                     // viene visualizzata la risposta
                    System.out.println(réponse);
                }//while
                 // si chiude la connessione
                client.close();
                 // si è terminato?
                if ( ! locationFound || nbRedirs>nbRedirsMax)
                    break;
                 // è necessario effettuare un reindirizzamento - si crea la nuova URL
                URLString=protocol +"://"+host+":"+port+locationString;
                url=new URL(URLString);
                // tracciamento
                System.out.println("\n<--Redirection vers l'URL "+URLString+"-->\n");
            }//mentre
        } catch(Exception e){
            // si gestisce l'eccezione
            erreur(e.getMessage(),4);
        }//catch
    }//main

     // visualizzazione degli errori
    public static void erreur(String msg, int exitCode){
         // visualizzazione dell'errore
        System.err.println(msg);
         // interruzione con errore
        System.exit(exitCode);
    }//errore
}//classe

8.4.7. Server per il calcolo delle imposte

Riprendiamo l'esercizio IMPOTS già trattato in varie forme. Ricordiamo l'ultima versione:

È stata creata una classe base per le imposte. I suoi attributi sono tre tabelle di numeri:

public class impots{

  // i dati necessari per il calcolo dell'imposta
   // provengono da una fonte esterna

  protected double[] limites=null;
  protected double[] coeffR=null;
  protected double[] coeffN=null;

   // campo «costruttore» vuoto
  protected impots(){}

   // costruttore
  public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{

La classe impots ha due costruttori:

  • un costruttore a cui vengono passati i tre array di dati necessari per il calcolo dell'imposta
  public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
  • un costruttore senza parametri utilizzabile solo dalle classi figlie
  protected impots(){}

Da questa classe è stata derivata la classe impotsJDBC, che consente di compilare i tre array limites, coeffR e coeffN a partire dal contenuto di un database:

public class impotsJDBC extends impots{
  // aggiunta di un costruttore che consente di creare
   // le tabelle «limites», «coeffr» e «coeffn» a partire dalla tabella
   // imposte di un database
  public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
      throws SQLException,ClassNotFoundException{

    // dsnIMPOTS: nome DSN del database
     // userIMPOTS, mdpIMPOTS: login/password di accesso al database

Era stata sviluppata un'applicazione grafica. L'applicazione utilizzava un oggetto della classe impotsJDBC. L'applicazione e tale oggetto si trovavano sulla stessa macchina. Intendiamo collocare il programma di test e l'oggetto impotsJDBC su macchine diverse. Si otterrà un'applicazione client-server in cui l'oggetto remoto impotsJDBC fungerà da server. La nuova classe si chiama ServeurImpots ed è derivata dalla classe impotsJDBC:

// pacchetti importati
import java.net.*;
import java.io.*;
import java.sql.*;

public class ServeurImpots extends impotsJDBC {

    // attributi
    int portEcoute;                // porta di ascolto delle richieste dei clienti
    boolean actif;                // stato del server

     // costruttore
    public ServeurImpots(int portEcoute,String DSNimpots, String USERimpots, String MDPimpots)
      throws IOException, SQLException, ClassNotFoundException {
       // costruzione padre
        super(DSNimpots, USERimpots, MDPimpots);
         // si nota la porta di ascolto
        this.portEcoute=portEcoute;
         // al momento inattivo
        actif=false;
         // crea e avvia un thread per la lettura dei comandi digitati sulla tastiera
         // il server verrà gestito tramite questi comandi
        Thread admin=new Thread(){
        public void run(){
          try{
            admin();
        }catch (Exception ignored){}
      }
    };
    admin.start();
    }//ServeurImpots

L'unico nuovo parametro nel costruttore è la porta di ascolto delle richieste dei client. Gli altri parametri vengono passati direttamente alla classe base impotsJDBC. Il server delle imposte è controllato da comandi digitati sulla tastiera. Si crea quindi un thread per leggere tali comandi. Ce ne saranno due possibili: start per avviare il servizio, stop per arrestarlo definitivamente. Il metodo admin che gestisce questi comandi è il seguente:

    public void admin() throws IOException{
        // legge i comandi di amministrazione del server digitati dalla tastiera
         // in un ciclo infinito
        String commande=null;
    BufferedReader IN=new BufferedReader(new InputStreamReader(System.in));
        while(true){
             // richiede
            System.out.print("Serveur d'impôts>");
             // lettura comando
            commande=IN.readLine().trim().toLowerCase();
             // esecuzione comando
            if(commande.equals("start")){
                // attivo?
                if(actif){
                     //errore
                    System.out.println("Le serveur est déjà actif");
                     // si prosegue
                    continue;
                }//if
                 // si crea e si avvia il servizio di ascolto
                Thread ecoute=new Thread(){
                public void run(){
                  ecoute();
              }
            };
            ecoute.start();
            }//if
            else if(commande.equals("stop")){
                // fine di tutti i thread di esecuzione
                System.exit(0);
            }//se
            else {
                // errore
                System.out.println("Commande incorrecte. Utilisez (start,stop)");
            }//if
        }//while
    }//admin

Se il comando digitato sulla tastiera è start, viene avviato un thread di ascolto delle richieste dei clienti. Se il comando digitato è stop, tutti i thread vengono arrestati. Il thread di ascolto esegue il metodo ecoute:

    public void ecoute(){
         // thread di ascolto delle richieste dei clienti
         // si crea il servizio di ascolto
        ServerSocket ecoute=null;
        try{
            // si crea il servizio
            ecoute=new ServerSocket(portEcoute);
             // monitoraggio
            System.out.println("Serveur d'impôts lancé sur le port " + portEcoute);

             // ciclo di servizio
            Socket liaisonClient=null;
            while (true){ // ciclo infinito
                 // attesa di un cliente
                liaisonClient=ecoute.accept();

                 // il servizio è gestito da un'altra attività
                new traiteClientImpots(liaisonClient,this).start();

                 // si torna ad ascoltare le richieste
            }// fine while
        }catch(Exception ex){
             // si segnala l'errore
            erreur("L'erreur suivante s'est produite : " + ex.getMessage(),3);
        }//catch
    }//thread di ascolto

È presente un server TCP classico in ascolto sulla porta portEcoute. Le richieste dei client vengono gestite dal metodo run del thread traiteCientImpots, al cui costruttore vengono passati due parametri:

  1. l'oggetto Socket liaisonClient che consentirà di raggiungere il client
  2. l'oggetto impotsJDBC this che consentirà di accedere al metodo this.calculer per il calcolo dell'imposta.
// -------------------------------------------------------
// fornisce il servizio a un cliente del server fiscale

class traiteClientImpots extends Thread{

    private Socket liaisonClient;            // collegamento con il cliente
    private BufferedReader IN;                // flusso in entrata
    private PrintWriter OUT;                    // flusso in uscita
    private impotsJDBC objImpots;            // oggetto Imposta

         // costruttore
    public traiteClientImpots(Socket liaisonClient,impotsJDBC objImpots){
        this.liaisonClient=liaisonClient;
        this.objImpots=objImpots;
    }//costruttore

Il metodo run gestisce le richieste dei clienti. Si tratta di righe di testo che possono assumere due forme:

  1. calcolo coniugato (sì/no) nbEnfants salaireAnnuel
  2. fincalculs

La forma 1 consente il calcolo di un'imposta, mentre la forma 2 chiude la connessione cliente-server.

     // metodo di esecuzione
    public void run(){
         // fornisce il servizio al cliente
        try{
             // flusso in entrata
            IN=new BufferedReader(new InputStreamReader(liaisonClient.getInputStream()));
             // flusso di uscita
            OUT=new PrintWriter(liaisonClient.getOutputStream(),true);
             // invio di un messaggio di benvenuto al cliente
            OUT.println("Bienvenue sur le serveur d'impôts");

             // ciclo di lettura della richiesta/scrittura della risposta
            String demande=null;
            String[] champs=null;    // gli elementi della richiesta
            String commande=null;    // il comando del cliente: calcolo o fine calcolo
            while ((demande=IN.readLine())!=null){
                 // si scompone la richiesta in campi
                champs=demande.trim().toLowerCase().split("\\s+");
                 // due richieste accettate: calcolo e fine calcolo
                commande=champs[0];
                if(! commande.equals("calcul") && ! commande.equals("fincalculs")){
                     // errore del cliente
                    OUT.println("Commande incorrecte. Utilisez (calcul,fincalculs).");
                     // ordine successivo
                    continue;
                }//if
                if(commande.equals("calcul")) calculerImpôt(champs);
                if(commande.equals("fincalculs")){
                     // messaggio di saluto al cliente
                    OUT.println("Au revoir...");
                     // liberazione delle risorse
                    try{ OUT.close();IN.close();liaisonClient.close();}
                    catch(Exception ex){}
                     // fine
                    return;
                }//if
                 //richiesta successiva
            }//while
        }catch (Exception e){
            erreur("L'erreur suivante s'est produite ("+e+")",2);
        }// fine try
    }// fine Run

Il calcolo dell'imposta viene effettuato tramite il metodo calculerImpôt, che riceve come parametro la tabella dei campi della richiesta effettuata dal cliente. Viene verificata la validità della richiesta e, se del caso, l'imposta viene calcolata e restituita al cliente.

     // calcolo delle imposte
    public void calculerImpôt(String[] champs){
         // elabora la richiesta: calcolo per coniugi nbEnfants salaireAnnuel
         // scomposto in campi nella tabella dei campi

        String marié=null;
        int nbEnfants=0;
        int salaireAnnuel=0;

         // validità degli argomenti
        try{
             // sono necessari almeno 4 campi
            if(champs.length!=4) throw new Exception();
            // sposato
            marié=champs[1];
            if (! marié.equals("o") && ! marié.equals("n")) throw new Exception();
            // figli
            nbEnfants=Integer.parseInt(champs[2]);
             // stipendio
            salaireAnnuel=Integer.parseInt(champs[3]);
        }catch (Exception ignored){
            // errore di formato
            OUT.println(" syntaxe : calcul marié(O/N) nbEnfants salaireAnnuel");
             // fine
            return;
        }//if
         // è possibile calcolare l'imposta
        long impot=objImpots.calculer(marié.equals("o"),nbEnfants,salaireAnnuel);
         // si invia la risposta al cliente
        OUT.println(""+impot);
    }//calcolare

Un programma di test potrebbe essere il seguente:

// chiamata: serveurImpots porta dsnImpots userImpots mdpImpots

import java.io.*;

public class testServeurImpots{
    public static final String syntaxe="Syntaxe : pg port dsnImpots userImpots mdpImpots";

    // programma principale
    public static void main (String[] args){

        // sono necessari 4 argomenti
        if(args.length != 4)
            erreur(syntaxe,1);

         // la porta deve essere un numero intero >0
        int port=0;
        boolean erreurPort=false;
        Exception E=null;
        try{
            port=Integer.parseInt(args[0]);
        }catch(Exception e){
            E=e;
            erreurPort=true;
        }
        erreurPort=erreurPort || port <=0;
        if(erreurPort)
            erreur(syntaxe+"\n"+"Port incorrect ("+E+")",2);

         // si crea il server delle imposte
        try{
            new ServeurImpots(port,args[1],args[2],args[3]);
        }catch(Exception ex){
             //errore
            System.out.println("L'erreur suivante s'est produite : "+ex.getMessage());
        }//catch
    }//Main

     // visualizzazione degli errori
    public static void erreur(String msg, int exitCode){
         // visualizzazione dell'errore
        System.err.println(msg);
         // arresto con errore
        System.exit(exitCode);
    }//errore
}// fine classe

Si inseriscono nel programma di test i dati necessari alla creazione di un oggetto ServeurImpots e, a partire da lì, il programma crea tale oggetto.

Proviamo a eseguire il codice per la prima volta:

dos>java testServeurImpots 124 mysql-dbimpots admimpots mdpimpots
Serveur d'impôts>start
Serveur d'impôts>Serveur d'écho lancé sur le port 124
stop

Il comando

dos>java testServeurImpots 124 mysql-dbimpots admimpots mdpimpots

crea un oggetto ServeurImpots che non è ancora in ascolto delle richieste dei client. È il comando start digitato dalla tastiera che avvia l'ascolto. Il comando stop arresta il server. Utilizziamo ora un client. Useremo il client generico creato in precedenza. Il server è avviato:

dos>java testServeurImpots 124 mysql-dbimpots admimpots mdpimpots
Serveur d'impôts>start
Serveur d'impôts>Serveur d'écho lancé sur le port 124

Il client generico viene avviato in un’altra finestra DOS:

Dos>java clientTCPgenerique localhost 124
Commandes :
<-- Bienvenue sur le serveur d'impôts

Si nota che il client ha correttamente ricevuto il messaggio di benvenuto dal server. Si inviano altri comandi:

x
<-- Commande incorrecte. Utilisez (calcul,fincalculs).
calcul
<--  syntaxe : calcul marié(O/N) nbEnfants salaireAnnuel
calcul o 2 200000
<-- 22506
calcul n 2 200000
<-- 33388
fincalculs
<-- Au revoir...
[fin du thread de lecture des réponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]

Torniamo alla finestra del server per arrestarlo:

dos>java testServeurImpots 124 mysql-dbimpots admimpots mdpimpots
Serveur d'impôts>start
Serveur d'impôts>Serveur d'écho lancé sur le port 124
stop

8.5. Esercizi

8.5.1. Esercizio 1 - Client TCP generico grafico

8.5.1.1. Presentazione dell’applicazione

Ci proponiamo di creare un programma in grado di interagire su Internet con i principali servizi TCP. Lo chiameremo client TCP generico. Una volta compresa questa applicazione, ci si rende conto che tutti i client TCP sono simili. La finestra del programma è la seguente:

Image

Il significato dei diversi controlli è il seguente:

n.
nome
tipo
ruolo
1
TxtRemoteHost
JTextField
nome del computer che offre il servizio desiderato
2
TxtPort
JTextField
porta del servizio richiesto
3
TxtSend
JTextField
testo del messaggio che verrà inviato al server dal client
4
OptRCLF
OptLF
JCheckBox
pulsanti che consentono di specificare come terminano le righe nella finestra di dialogo client/server
RCLF: ritorno a capo (#13) + nuova riga (#10)
LF: nuova riga (#10)
5
LstSuivi
JList
visualizza i messaggi relativi allo stato della comunicazione tra client e server
6
LstDialogue
JList
visualizza i messaggi scambiati dal client (->) e dal server (<-)
7
CmdAnnuler
JButton
nascosto - situato sotto l'elenco delle finestre di dialogo - Appare quando la connessione è in corso e consente di interromperla se il server non risponde

Le opzioni di menu disponibili sono le seguenti:

opzione
sotto-opzioni
ruolo
Connessione
Connetti
connette il client al server
 
Disconnetti
chiude la connessione
 
Esci
Chiude il programma
Messaggi
Invia
Invia il messaggio del controllo TxtSend al server
 
RazSuivi
Cancella l'elenco LstSuivi
 
RazDialogue
Cancella l'elenco LstDialogue
Autore
 
Visualizza una finestra di copyright

8.5.1.2. FONCTIONNEMENT DE L’APPLICATION

Inizializzazione dell'applicazione

Quando viene caricato il foglio principale dell’applicazione, avvengono le seguenti operazioni:

  • la scheda viene centrata sullo schermo
  • sono attive solo le opzioni di menu Connexion/Quitter e Auteur
  • il pulsante Annuler è nascosto
  • gli elenchi LstSuivi e LstDialogue sono vuoti
Menu Accesso/Accedi

Questa opzione è disponibile solo quando i campi "Host remoto" e "Numero di porta" non sono vuoti e non è attualmente attiva alcuna connessione. Cliccando su questa opzione si eseguono le seguenti operazioni:

  • viene verificata la validità della porta: deve essere un numero intero maggiore di 0
  • viene avviato un thread per garantire la connessione al server
  • viene visualizzato il pulsante Annuler per consentire all’utente di interrompere la connessione in corso
  • tutte le opzioni di menu vengono disattivate tranne Quitter e Auteur

La connessione può terminare in diversi modi:

  1. L'utente ha premuto il pulsante Annuler: si interrompe il thread di connessione e si riporta il menu allo stato iniziale. Nel log viene indicato che la connessione è stata chiusa dall'utente.
  2. La connessione termina con un errore: si procede come descritto in precedenza e, inoltre, nel log si indica la causa dell’errore.
  3. La connessione si conclude correttamente: si rimuove il pulsante Annuler, si indica nel log che la connessione è stata stabilita, si abilita il menu RazSuivi, si disabilita il menu Connecter, si abilita il menu Déconnecter
Menu Connessione/Disconnessione

Questa opzione è disponibile solo quando è presente una connessione con il server. Quando è attivata, chiude la connessione con il server e riporta il menu allo stato iniziale. Nel log viene indicato che la connessione è stata chiusa dal client.

Menu Connessione/Esci

Questa opzione chiude un’eventuale connessione attiva con il server e termina l’applicazione.

Menu Messaggi/Invia

Questa opzione è accessibile solo se sono soddisfatte le seguenti condizioni:

  • è stata stabilita la connessione con il server

  • c'è un messaggio da inviare

Se queste condizioni sono soddisfatte, al server viene inviato il testo presente nel campo TxtSend (3), terminato dalla sequenza RCLF se è stata selezionata l'opzione RCLF, altrimenti dalla sequenza LF. Un eventuale errore durante l’invio viene segnalato nell’elenco di tracciamento.

Menu RazSuivi e RazDialogue

Svuotano rispettivamente gli elenchi LstSuivi e LstDialogue. Queste opzioni sono disabilitate quando gli elenchi corrispondenti sono vuoti.

Il pulsante Annulla

Questo pulsante, situato nella parte inferiore del modulo, appare solo quando il cliente sta effettuando la connessione al server. La connessione potrebbe non andare a buon fine perché il server non risponde o risponde in modo errato. Il pulsante Annuler offre quindi all’utente la possibilità di interrompere la richiesta di connessione.

Gli elenchi di monitoraggio

L’elenco LstSuivi (5) tiene traccia della connessione. Indica i momenti chiave della connessione:

  • la sua apertura da parte del cliente

  • la sua chiusura da parte del server o del cliente

  • tutti gli errori che possono verificarsi mentre la connessione è attiva

La lista LstDialogue (6) tiene traccia del dialogo che si instaura tra il client e il server. Un thread monitora in background ciò che accade sul socket di comunicazione del client e lo visualizza nella lista 6.

L’opzione Autore

Questo menu apre una finestra denominata «Copyright»:

Image

Gestione degli errori

Gli errori di connessione vengono segnalati nell’elenco di monitoraggio 6, mentre quelli relativi al dialogo client/server nell’elenco di dialogo 7. In caso di errore di connessione, il dialogo client/server viene chiuso e il modulo riportato al suo stato iniziale, pronto per una nuova connessione.

8.5.1.3. Da TRAVAIL a FAIRE

Eseguire l’operazione descritta in precedenza in due modalità:

  • applicazione autonoma
  • applet

8.5.2. Esercizio 2 - Un server di risorse

8.5.2.1. INTRODUCTION

Un'istituzione dispone di diversi potenti server di calcolo accessibili su Internet. Ogni macchina che desideri utilizzare questi servizi di calcolo invia un file di dati alla porta 756 di uno dei server. Questo file contiene diverse informazioni: login, password, comandi che indicano il tipo di calcolo desiderato e i dati su cui eseguire il calcolo. Se il file di dati è corretto, il server di calcolo selezionato lo utilizza e restituisce i risultati al cliente sotto forma di file di testo.

I vantaggi di una tale organizzazione sono molteplici:

  • Qualsiasi tipo di client (PC, Mac, Unix,...) può utilizzare questo servizio
  • il cliente può trovarsi ovunque su Internet
  • le risorse di calcolo sono ottimizzate: sono necessarie solo poche macchine potenti. In questo modo, anche una piccola organizzazione priva di risorse di calcolo può utilizzare questo servizio versando un contributo finanziario calcolato in base al tempo di calcolo utilizzato.

Nonostante la potenza delle macchine, un calcolo può talvolta durare diverse ore: in tal caso il server non è disponibile per altri clienti. Si pone quindi, per un cliente, il problema di trovare un server di calcolo disponibile. A tal fine si ricorre a un «gestore delle risorse di calcolo», denominato di seguito server GRC. Questo servizio è ospitato su un’unica macchina e opera sulla porta 864 in modalità TCP. È a lui che si rivolge un cliente che desidera accedere a un server di calcolo. Il server GRC, che detiene l’elenco completo dei server di calcolo, risponde inviandogli il nome di un server attualmente inattivo. A questo punto, il client non deve fare altro che inviare i propri dati al server che gli è stato indicato.

Ci proponiamo di scrivere il server GRC.

8.5.2.2. L’INTERFACE VISUELLE

L’interfaccia visiva sarà la seguente:

Image

L’interfaccia presenta due elenchi di server:

  • a sinistra, l’elenco dei server inattivi e quindi disponibili per i calcoli
  • a destra, l’elenco dei server occupati dai calcoli di un cliente.

La struttura del menu è la seguente:

Menu principale
Menu secondario
Ruolo
Servizio
Avvia
Avvia il servizio TCP sulla porta 864
 
Arresta
Arresta il servizio
 
Esci
Chiude l'applicazione
Autore
 
Informazioni sul copyright

La struttura dei controlli presenti nel modulo è la seguente:

Nome
Tipo
Ruolo
listLibres
JList
Elenco dei server disponibili
listOccupés
JList
Elenco dei server occupati

8.5.2.3. FONCTIONNEMENT DE APPLICATION

Caricamento dell’applicazione

All’avvio dell’applicazione, l’elenco listLibres viene popolato con l’elenco dei nomi dei server di calcolo gestiti dal GRC. Questi sono definiti in un file «Server» passato come parametro. Questo file contiene un elenco di nomi di server, uno per riga, e viene quindi utilizzato per compilare l’elenco listLibres. Il menu Avvia è abilitato, mentre il menu Arresta è disabilitato.

Opzione Servizio/Avvia

Questa opzione

  • avvia il servizio di ascolto sulla porta 864 del computer
  • disabilita il menu Avvia
  • abilita il menu «Arresta»
Opzione Servizio/Arresta

Questa opzione interrompe il servizio:

  • l'elenco dei server occupati viene svuotato
  • l'elenco dei server liberi viene popolato con il contenuto del file Server
  • il menu Avvia viene abilitato
  • il menu «Arresta» viene disabilitato
Opzione Servizio/Esci

L'applicazione viene chiusa.

Dialogo client/server

Il dialogo client/server avviene tramite lo scambio di righe di testo terminate dalla sequenza RCLF. Il server GRC riconosce due comandi: getserveur e finservice. Di seguito viene descritto in dettaglio il ruolo di questi due comandi:

  • 1-getserveur

Il client chiede se c’è un server di calcolo disponibile per lui.

Il server GRC seleziona quindi il primo server trovato nel proprio elenco di server liberi e ne restituisce il nome al client nel formato:

        100-nom du serveur

Inoltre, inserisce il server assegnato al cliente nell’elenco dei server occupati nel formato:

        serveur (IP du client)

come mostra il seguente esempio, in cui il server calcul1.istia.univ-angers.fr è impegnato a servire il client con indirizzo IP 193.52.43.5:

Image

Un client non può inviare un comando getserveur se gli è già stato assegnato un server di calcolo. Pertanto, prima di rispondere al client, il server GRC verifica che l’indirizzo IP del client non sia già presente tra quelli registrati nell’elenco dei server occupati. In tal caso, il server GRC risponde:

        501-Vous avez actuellement une demande en cours

Infine, c’è il caso in cui non sia disponibile alcun server di calcolo: l’elenco dei server liberi è vuoto. In questo caso, il server GRC risponde:

        502- Il n’y a aucun serveur de calcul disponible

In ogni caso, dopo aver risposto al client, il server GRC chiude la connessione con quest’ultimo per poter servire altri client.

  • 2-finservice

Il client comunica di non aver più bisogno del server di calcolo che stava utilizzando.

Il server GRC verifica innanzitutto che il cliente sia effettivamente uno di quelli che stava servendo. A tal fine, verifica che l’indirizzo IP del client sia presente tra quelli registrati nell’elenco dei server occupati. In caso contrario, il server GRC risponde:

        503-Aucun serveur ne vous a été attribué

Se il client viene riconosciuto, il server GRC risponde:

        101-Fin de service acceptée

e sposta il server di calcolo assegnato a quel cliente nell’elenco dei server liberi. Per riprendere l’esempio precedente, se il cliente invia il comando finservice, la visualizzazione del server GRC diventa:

Image

Dopo l’invio della risposta, qualunque essa sia, il server GRC chiude la connessione.

8.5.2.4. TRAVAIL A FAIRE

Scrivere l'applicazione come programma autonomo che possa essere testato, ad esempio, con un client telnet o con il client TCP generico dell'esercizio precedente.

8.5.3. Esercizio 3 - un client SMTP

8.5.3.1. INTRODUCTION

In questo esercizio desideriamo realizzare un client per il servizio SMTP (SendMail Transfer Protocol) che consenta di inviare la posta. Su Unix o Windows, il programma telnet è un client che opera con il protocollo tcp. Può «comunicare» con qualsiasi servizio tcp che accetti comandi in formato testo terminanti con la sequenza RCLF, ovvero i caratteri con codice ASCII 13 e 10. Ecco un esempio di interazione con il servizio smtp per l’invio di posta:

$ telnet istia.univ-angers.fr 25        // richiamo del servizio SMTP

// risposta del server SMTP


Trying 193.52.43.2...
Connected to istia.univ-angers.fr.
Escape character is '^]'.
220-Istia.Istia.Univ-Angers.fr Sendmail 8.6.10/8.6.9 ready at Tue, 16 Jan 1996 07:53:12 +0100
220 ESMTP spoken here

// commenti --------------

Il programma telnet può richiamare qualsiasi servizio utilizzando la sintassi

telnet machine_service port_service

Gli scambi client/server avvengono tramite righe di testo che terminano con la sequenza RCLF.

Le risposte del servizio smtp hanno il seguente formato:

numero-Messaggio oppure

numero-Messaggio

Il server SMTP può inviare più righe di risposta. L’ultima riga della risposta è contrassegnata da un numero seguito da uno spazio, mentre nelle righe precedenti della risposta il numero è seguito da un trattino -.

Un numero maggiore o uguale a 500 indica un messaggio di errore.

// fine dei commenti

help                        // comando immesso dalla tastiera

// risposta del server SMTP

214-Commands:
214-    HELO    EHLO    MAIL    RCPT    DATA
214-    RSET    NOOP    QUIT    HELP    VRFY
214-    EXPN    VERB
214-For more info use "HELP <topic>".
214-To report bugs in the implementation send email to
214-    sendmail@CS.Berkeley.EDU.
214-For local information send email to Postmaster at your site.
214 End of HELP info
mail from: serge.tahe@istia.univ-angers.fr    // nuovo comando immesso dalla tastiera

// commenti ---------

Il comando mail ha la seguente sintassi:

mail from: indirizzo e-mail del mittente del messaggio

// fine commenti

// risposta del server SMTP

250 serge.tahe@istia.univ-angers.fr... Sender ok

// commenti

Il server smtp non verifica la validità dell'indirizzo del mittente: lo accetta così come gli è stato fornito

// fine dei commenti

rcpt to: user1@istia.univ-angers.fr        // nuovo comando immesso dalla tastiera

// commenti ---------

Il comando rcpt ha la seguente sintassi:

rcpt to: indirizzo e-mail del destinatario del messaggio

Se l'indirizzo e-mail è un indirizzo del computer su cui opera il server smtp, il server verifica che esista; in caso contrario, non effettua alcuna verifica. Se è stata effettuata una verifica e è stato rilevato un errore, questo verrà segnalato con un numero >= 500.

È possibile inviare tutti i comandi rcpt to che si desidera: ciò consente di inviare un messaggio a più destinatari.

// fine dei commenti

// risposta del server SMTP

250 user1@istia.univ-angers.fr... Recipient ok
data                        // nuovo comando immesso dalla tastiera

// commenti ---------

Il comando data ha la seguente sintassi:

data

riga1

riga2

...

.

È seguita dalle righe di testo che compongono il messaggio, il quale deve terminare con una riga contenente il solo carattere «punto».

Il messaggio viene quindi inviato al destinatario specificato dal comando rcpt.

// fine dei commenti

// risposta del server SMTP

354 Enter mail, end with "." on a line by itself

// testo del messaggio digitato dalla tastiera


subject: essai smtp

essai smtp a partir de telnet
.

// commenti

Nelle righe di testo del comando data è possibile inserire una riga subject: per specificare l'oggetto dell'e-mail. Questa riga deve essere seguita da una riga vuota.

// risposta del server SMTP

250 HAA11627 Message accepted for delivery
quit                            // nuovo comando immesso dalla tastiera

// commenti

Il comando quit chiude la connessione al servizio smtp

// fine dei commenti

// risposta del server SMTP

221 Istia.Istia.Univ-Angers.fr closing connection

8.5.3.2. L’INTERFACE VISUELLE

Si propone di realizzare un programma con la seguente interfaccia grafica:

Image

I controlli hanno le seguenti funzioni:

Numero
Tipo
Funzione
1
JTextField
Sequenza di indirizzi e-mail separati da una virgola
2
JTextField
Testo dell’oggetto del messaggio
3
JTextField
Sequenza di indirizzi e-mail separati da una virgola
4
JTextField
Sequenza di indirizzi e-mail separati da una virgola
5
JTextArea
Testo del messaggio
6
JList
lista di monitoraggio
7
JList
elenco delle conversazioni
8
JButton
Pulsante «Annulla» non visualizzato, che appare quando il client richiede la connessione al server SMTP. Consente all’utente di interrompere questa richiesta se il server non risponde.

8.5.3.3. LES MENUS

La struttura dei menu dell’applicazione è la seguente:

Menu principale
Menu secondario
Ruolo
Posta
  
 
Invia
Invia il messaggio del controllo 5
 
Esci
Esci dall'applicazione
Opzioni
  
 
Nascondi tracciamento
Rende invisibile il controllo 6
 
Reset monitoraggio
Svuota l'elenco di monitoraggio 6
 
Nascondi finestra di dialogo
Nascondi elenco dialoghi 7
 
Reset dialogo
Svuota l'elenco di dialogo 7
 
Configura
Consente all’utente di specificare
- l'indirizzo del server SMTP utilizzato dal programma
- il proprio indirizzo e-mail
 
Salva...
Salva la configurazione precedente in un file .ini
Autore
 
Informazioni sul copyright

8.5.3.4. FONCTIONNEMENT DE APPLICATION

Menu Opzioni/Configura

Questo menu fa apparire la seguente finestra:

Image

Entrambi i campi devono essere compilati affinché il pulsante OK sia attivo. Entrambe le informazioni devono essere memorizzate in variabili globali per essere disponibili ad altri moduli.

Menu Posta/Invia

Questa opzione è accessibile solo se sono soddisfatte le seguenti condizioni:

  • la configurazione è stata completata
  • c'è un messaggio da inviare
  • è presente un oggetto
  • c'è almeno un destinatario nei campi 1, 3 e 4

Se queste condizioni sono soddisfatte, la sequenza degli eventi è la seguente:

  • il modulo viene messo in uno stato in cui tutte le azioni che potrebbero interferire con il dialogo client/server vengono disabilitate
  • viene stabilita una connessione sulla porta 25 del server specificato nella configurazione
  • il client comunica quindi con il server SMTP secondo il protocollo descritto sopra
  • il campo «Mail From:» utilizza l’indirizzo e-mail del mittente specificato nella configurazione
  • il campo «rcpt to:» viene utilizzato per ciascuno degli indirizzi e-mail presenti nei campi 1, 3 e 4
  • nelle righe inviate dopo il comando data, si troveranno i seguenti testi:
    • una riga Subject:: testo dell’oggetto del controllo 2
    • una riga Cc: indirizzi del controllo 3
    • una riga Bcc: indirizzi del controllo 4
    • il testo del messaggio del controllo 5
    • il punto terminale
Il pulsante Annulla

Questo pulsante, situato nella parte inferiore del modulo, appare solo quando il cliente sta effettuando la connessione al server smtp. La connessione potrebbe non andare a buon fine perché il server smtp non risponde o risponde in modo errato. Il pulsante Annuler offre quindi all’utente la possibilità di interrompere la richiesta di connessione.

Gli elenchi di monitoraggio

L'elenco (6) tiene traccia della connessione. Indica i momenti chiave della connessione:

  • la sua apertura da parte del client
  • la sua chiusura da parte del server o del cliente
  • tutti gli errori di connessione

La lista (7) tiene traccia del dialogo smtp che si instaura tra il client e il server.

Questi due elenchi sono associati alle opzioni del menu:

Nascondi elenco di monitoraggio
Rende invisibile l'elenco di monitoraggio 6 e la didascalia che si trova sopra di esso. Se l'altezza occupata da questi due controlli è H, tutti i controlli situati sotto vengono spostati verso l'alto di un'altezza pari a H e la dimensione totale del modulo viene ridotta di H. Inoltre, l’opzione “Nascondi tracciamento” rende invisibile l’opzione RazSuivi sottostante.
Azzeramento tracciamento
Svuota l’elenco di monitoraggio 6
Nascondi finestra di dialogo
Rende invisibile l’elenco di dialogo 7, l’etichetta che si trova sopra di esso e l’opzione di menu RazDialogue sottostante. Come per "Nascondi Monitoraggio", la posizione dei controlli sottostanti (ad esempio il pulsante Annuler) viene ricalcolata e la dimensione della finestra ridotta.
Reset finestra di dialogo
Svuota l’elenco di dialogo 7
L’opzione Autore

Questo menu apre una finestra denominata «Copyright»:

Image

Gestione degli errori

Gli errori di connessione vengono segnalati nell’elenco di monitoraggio 6, mentre quelli relativi alla comunicazione client/server nell’elenco di dialogo 7. In caso di errore, l’utente viene avvisato tramite una finestra di errore e viene visualizzato l’elenco contenente la causa dell’errore, se in precedenza era nascosto. Inoltre, il dialogo client/server viene chiuso e il modulo riportato allo stato iniziale.

8.5.3.5. GESTION da UN FICHIER DE CONFIGURATION

È auspicabile che l’utente non debba riconfigurare il software ogni volta che lo utilizza. A tal fine, se l’opzione «Opzioni/Salva la configurazione all’uscita» è selezionata, alla chiusura del programma vengono salvate sia le informazioni acquisite tramite l’opzione Options/Configurer sia lo stato dei due elenchi di monitoraggio in un file sendmail.ini situato nella stessa directory del file .exe del programma. Questo file ha il seguente formato:

SmtpServer=shiva.istia.univ-angers.fr
ReplyAddress=serge.tahe@istia.univ-angers.fr
Suivi=0
Dialogue=1

Le righe SmtpServer e ReplyAddress riportano le due informazioni acquisite tramite l'opzione Opzioni/Configura. Le righe Suivi e Dialogue indicano lo stato delle liste di Monitoraggio e di Dialogo: 1 (presente), 0 (assente).

All’avvio del programma, il file sendmail.ini viene letto, se presente, e il modulo viene configurato di conseguenza. Se il file sendmail.ini non esiste, si procede come se fosse presente:

SmtpServer=
ReplyAddress=
Suivi=1
Dialogue=1

Se il file sendmail.ini esiste ma è incompleto (righe mancanti), la riga mancante viene sostituita dalla riga corrispondente sopra indicata. Pertanto, poiché manca la riga Suivi=..., si procede come se si avesse Suivi=1.

Tutte le righe che non corrispondono al modello:

    mot clé= valeur

vengono ignorate, così come quelle in cui la parola chiave non è valida. La parola chiave può essere scritta in maiuscolo o in minuscolo: non fa differenza.

Nell’opzione Options/Configurer vengono visualizzati i valori SmtpServer e ReplyAddress attualmente in uso. L’utente può quindi modificarli se lo desidera.

8.5.3.6. Da TRAVAIL a FAIRE

Eseguire l’operazione descritta in precedenza. Si consiglia di occuparsi della gestione del file di configurazione per ultima.

8.5.4. Esercizio 4 - client POPPASS

8.5.4.1. Introduction

Si intende creare un client TCP in grado di comunicare con il server POPPASSD che opera sulla porta 106. Questo servizio consente di modificare la propria password su una macchina UNIX. Il protocollo di comunicazione client/server è il seguente:

1 - Le comunicazioni avvengono tramite lo scambio di messaggi che terminano con la sequenza RCLF

2 - Il client invia comandi al server

  • Il server risponde con messaggi che iniziano con numeri a 3 cifre: XXX. Se XXX=200, il comando è stato eseguito correttamente, altrimenti si è verificato un errore.

3 - La cronologia degli scambi è la seguente:

A    - le client se connecte
  • il server risponde con un messaggio di benvenuto
B    - le client envoie USER login
  • il server risponde richiedendo la password se il login viene accettato, altrimenti restituisce un errore
C    - le client envoie PASS mot_de_passe
  • il server risponde richiedendo la nuova password; se la password viene accettata, in caso contrario restituisce un errore
D    - le client envoie NEWPASS nouveau_mot_de_passe
  • il server risponde confermando che la nuova password è stata accettata; in caso contrario, restituisce un errore
E    - le client envoie la commande QUIT
  • il server invia un messaggio di fine sessione e chiude la connessione

8.5.4.2. Il modulo del client

Image

Il significato dei diversi controlli è il seguente:

n.
nome
tipo
funzione
1
txtRemoteHost
JTextField
nome del server
2
txtLogin
JTextField
nome utente
3
txtMdp
JTextField
Password utente
4
txtNewMdp
JTextField
Nuova password utente
5
txtConfirmation
JTextField
Conferma della nuova password
6
lstSuivi
JList
Messaggi relativi al monitoraggio della connessione
7
lstDialogue
JList
Messaggi della finestra di dialogo Client/Server
10
cmdAnnuler
JButton
non raffigurato - Pulsante che appare quando è in corso la connessione al server. Consente di interromperla.

8.5.4.3. I menu

Titolo
Nome del controllo
Funzione
Connessione
mnuconnexion
 
Connetti
mnuconnetter
avvia la connessione al server
Esci
mnuQuitter
chiude l'applicazione
Messaggi
mnuMessages
 
RazSuivi
mnuRazSuivi
cancella l'elenco lstSuivi
RazDialogue
mnuRazDialogue
cancella l'elenco lstDialogue
Autore
mnuAuteur
visualizza la finestra del copyright

8.5.4.4. Funzionamento dell'applicazione

Inizializzazione dell'applicazione

Quando viene caricata la schermata principale dell'applicazione, avvengono le seguenti operazioni:

  • la schermata viene centrata sullo schermo
  • sono attive solo le opzioni di menu Connexion/Quitter e Auteur
  • il pulsante Annuler è nascosto
  • gli elenchi LstSuivi e LstDialogue sono vuoti
Menu Connessione/Connetti

Questa opzione è disponibile solo se i campi da 1 a 5 sono stati compilati. Cliccando su questa opzione vengono eseguite le seguenti operazioni:

  • viene avviato un thread per garantire la connessione al server
  • viene visualizzato il pulsante Annuler per consentire all’utente di interrompere la connessione in corso
  • tutte le opzioni del menu vengono disattivate tranne Quitter e Auteur

La sequenza degli eventi è quindi la seguente:

  1. L'utente ha premuto il pulsante Annuler: il thread di connessione viene interrotto e il menu viene riportato allo stato iniziale. Nel log viene indicato che la connessione è stata chiusa dall'utente.
  2. La richiesta di connessione viene accettata dal server. Si avvia quindi il dialogo con il server per modificare la password. Gli scambi di questo dialogo vengono registrati nell’elenco LstDialogue. Una volta terminato il dialogo, la connessione con il server viene chiusa e il menu del modulo riportato allo stato iniziale.
  3. Finché il dialogo è attivo, il pulsante Annulla rimane visibile per consentire all’utente di chiudere la connessione se lo desidera.
  4. Se durante la comunicazione si verifica un errore, la connessione viene chiusa e la causa dell’errore viene visualizzata nell’elenco di tracciamento LstSuivi.
Menu Connessione/Esci

Questa opzione chiude un’eventuale connessione attiva con il server e termina l’applicazione.

Menu RazSuivi e RazDialogue

Svuotano rispettivamente gli elenchi LstSuivi e LstDialogue. Queste opzioni sono disabilitate quando gli elenchi corrispondenti sono vuoti.

Il pulsante Annulla

Questo pulsante, situato nella parte inferiore del modulo, appare solo quando il cliente sta effettuando la connessione o è già connesso al server. Il pulsante Annuler offre all’utente la possibilità di interrompere la comunicazione con il server.

Gli elenchi di monitoraggio

L’elenco LstSuivi (5) tiene traccia della connessione. Indica i momenti chiave della connessione:

  • la sua apertura da parte del client

  • la sua chiusura da parte del server o del cliente

  • tutti gli errori che possono verificarsi mentre la connessione è attiva

La lista LstDialogue (6) tiene traccia del dialogo che si instaura tra il client e il server.

L'opzione Autore

Questo menu apre una finestra denominata «Copyright»:

Image

Gestione degli errori

Gli errori di comunicazione vengono segnalati nell’elenco di monitoraggio 6, mentre quelli relativi al dialogo client/server nell’elenco di dialogo 7. In caso di errore di connessione, il dialogo client/server viene chiuso e il modulo riportato allo stato iniziale, pronto per una nuova connessione.

8.5.4.5. Da TRAVAIL a FAIRE

Realizzare il lavoro descritto in precedenza sotto forma di applicazione autonoma e successivamente di applet.