Rappels
Un réseau est un ensemble de machines et périphériques connectés de manière à pouvoir échanger des données.
Chaque noeud du réseau possède une adresse unique. Une adresse est une quantité numérique mais une correspondance symbolique peut lui être associée.
Le transfert d'information est réalisé par commutation de paquets.
Pour échanger des données, 2 ou plusieurs machines du réseau doivent adhérer à un protocole de communication commun.
Un protocole de communication définit un ensemble de règles qui régissent la communication.
De nombreux protocoles existent. Par exemple, le protocole HTTP règle la communication entre un navigateur web et un serveur. Le protocole SMTP définit la manière dont un courrier électronique est transféré. Tous deux sont des protocoles de haut niveau ( niveau application ). Java n'est pas concerné par les protocoles de bas niveau.
Les réseaux sont organisés en couches du niveau application au niveau physique. Le niveau application est concerné par la délivrance des données à l'utilisateur.
Les applications concernées par ce TP sont de type client/serveur. Le client initie une conversation. Le serveur attend et écoute une requête client.
Protocole IP ( Internet protocol ) : situé sous la couche application, il est chargé du transfert des données entre un client et un serveur sous forme de paquets.
Protocole TCP ( Transmission Control Protocol ) : en conjonction à IP pour contrôler que tous les paquets sont bien arrivés et dans l'ordre d'émission. Il procède à une retransmission en cas de perte.
Protocole UDP ( User Datagram Protocol ) : est un protocole non fiable dans la mesure où aucune garantie de temps de délivrance, d'ordre des paquets et de réception des paquets n'est fournie.
Adresse IP : adresse numérique unique codée sur 4 octets et encapsulée dans les paquets de données. Elle est utilisée par IP pour router les paquets d'une source à une destination. La lecture humaine est facilitée par sa correspondance symbolique. Par exemple, la commande nslookup www.fnac.com fournit le résultat suivant : 195.42.251.40.
DNS ( Domain Name System ) a été développé pour traduire un nom de domaine ( www.fnac.com ) en une adresse IP. En Java, la classe InetAddress permet de récupérer un nom de domaine connaissant son adresse IP.
Un Port joue le rôle d'une sous-adresse logique pour accéder à un serveur particulier. Par analogie, l'adresse IP correspond à un n° de téléphone tandis que le port correspond au n° de poste dans l'entreprise. Théoriquement, 65.535 ports sont disponibles. Les ports 1 à 1023 sont réservés à des services prédéfinis. Par exemple, le port 80 correspond à HTTP, 13 à l'heure et 7 à l'echo.
Une URL ( Uniform Resource Locator ) fournit un accès sur une ressource particulière. Un URL spécifie :
protocol://hostname[:port]/path/filename#ref
Sockets et URL : Java fournit 2 manières de faire de la programmation réseau. La première approche est associée aux classes Socket, DatagramSocket et ServerSocket. La seconde est associée aux classes URL, URLEncoder et URLConnection.
Programmation Socket : la
classe Socket
permet une programmation réseau utilisant le protocole
TCP, la
classe DatagramSocket
utilise le mode UDP. Toutes deux utilisent la classe ServerSocket.
La programmation socket fournit une approche bas-niveau pour connecter
2 machines. Elle permet l'écoulement des données en mode
full-duplex entre client et serveur. Cela ne signifie pas qu'elles sont
en mesure de communiquer. Encore faut-il disposer d'un protocole
commun. C'est au programmeur d'assigner un protocole acceptable par les
2 parties au niveau application.
Un premier exercice
Il s'agit d'écrire un programme de type client/serveur. Le serveur rend un écho de la chaîne de caractères envoyée par un client précédée de la chaîne : "Voici l'écho : ".
Le serveur d'echo est implémenté par un
thread
qui gèrer le port 7 ( service standard echo).
Nous nous proposons de créer un service d'écho sur un
nouveau port ( n° 7701, 7702, etc ... selon votre poste de travail )
Schéma de la communication client/serveur
Le serveur se met en attente d'une requête client
( méthode accept() ) sur le port 7.
Lorsqu'un appel lui parvient, la méthode accept() retourne
une socket qui est passée à un thread (EchoConnection)
crée par le serveur pour gérer la communication entre le
client et le serveur. Si un autre client émet une requête
vers le même serveur, un nouveau thread EchoConnection
est crée pour initier une nouvelle connection. Une telle socket
est donc associée au port 7. L'identification du client est
mémorisée au niveau du thread EchoConnection.
Coté client, une socket est crée vers le serveur avec le n° de port approprié. Cette socket est mise en correspondance à celle du serveur par le biais de l'identifiant du client.
Marche à suivre
On procédera progressivement .
Gestion de la sécurité
Par défaut, une application Java ne gère pas la
sécurité. Aucune restriction n'est prévue sur ce
que peut faire le programme.
Pour établir une gestion de la sécurité, on envoie
le message setSecurityManager au gestionnaire de
sécurité, ce qui a pour effet d'interdire tout
privilège au code.
Pour personnaliser le gestionnaire de sécurité, il faut
étendre la classe SecurityManager.
Les méthodes commençant par check
contrôlent l'accès aux différents privilèges.
La version par défaut de chaque méthode rend le
privilège associé indisponible.
Pour rendre le privilège disponible, il faut redéfinir la
méthode avec une méthode vide
Pour personnaliser un privilège, il faut la redéfinir
suivant le besoin.
Pour l'exercice, nous étendons la classe SecurityManager de manière à donner tous les privilèges. Pour cela chaque méthode commencant par check est redéfinie avec un corps vide.
code de la classe ServeurSecurityManager.
Le serveur d'écho : EchoServeur
Son rôle est d'instancier un serveur de socket (classe java ServerSocket)
sur le port 77xx capable de générer une socket dès
qu'un appel client survient.
L'EchoServeur
est un thread qui boucle indéfiniment en attente de
requête client.
Le premier appel en provenance du client déclenche un accept()
et par suite la création d'un thread EchoConnection
qui gère la connection avec ce client via une socket coté
serveur. Cette socket est représentée sur le
schéma ci-dessus par 2 flots, l'un en entrée et l'autre
en sortie. L'échange d'information consiste donc à lire
ou écrire sur ces flots.
code de la classe EchoServeur
Le gestionnaire de connection : EchoConnection
L'objet qui gère de bout en bout la connection
est un thread attaché à une socket liée au port
77xx.
Rendu actif (méthode run()), ce thread
crée un flot en lecture (inputStream) et un flot en
écriture (outputStream), tous deux attachés
à la socket.
Le gestionnaire de connection se contente de lire une
chaîne
input de caractères
sur le flot inputStream et d'écrire la chaîne "Voici
l'écho : "+input
sur outputStream.
code de la classe EchoConnection
Un Client : EchoClient
Connaissant l'adresse IP du serveur et le port sur lequel le service convoité est installé, le client crée une socket qu'il associe à un flot en lecture (inputStream) et un flot en écriture (outputStream).
Le client écrit la chaîne "Ceci est un test" sur outputStream puis lit l'écho sur inputStream.
Lorsque la communication est terminée chaque socket doit être fermée.
code de la classe EchoClient
Production du programme serveur sur la machine joule.cnam.fr
public
class AppliEcho
{
public static void main(String[] args)
{
// Création d'un gestionnaire de
sécurité personnalisé
System.setSecurityManager(new
ServeurSecurityManager());
// Instantiation d'un serveur d'objets sur le
port 77xx
// Renvoie en echo les chaînes reçues de clients TCP/IP
EchoServeur echoServeurThread = new
EchoServeur();
}
}
Production du programme client sur la machine newton.cnam.fr
Un second exercice
On souhaite écrire un serveur qui prenne en
charge des requêtes client selon le protocole HTTP.
Pour simplifier, ce serveur ne se préoccupera que de la commande
GET
du protocole. Les autres commandes seront ignorées. Un message
d'erreur sera envoyé au client si elles sont employées.
Implantation du serveur HTTP
Le code du serveur HTTP est identique à celui du serveur d'écho. La seule différence est qu'il est attaché au port 80xx. Ce serveur a pour tâche de créer une connection HTTP dès qu'une requête client survient en lui passant une socket qui est connectée à la socket client.
Le code de la classe HTTPConnection est semblable à celui de la classe EchoConnection pour ce qui concerne son constructeur. La différence essentielle porte sur la méthode run()où le principal travail est réalisé.
Implantation de la
méthode run()
Complétez le code suivant :
public void run()
{
System.out.println("thread actif sur
port 80");
try
{
// Création d'un flot en entrée
à partir de la socket
............................................
// Création d'un flot en sortie vers la
socket
// pour envoyer des chaînes de caractères à un
client.
............................................
// Création d'un flot en sortie vers la
socket
// pour envoyer des tableaux de bytes à un client.
byteOutput = new
DataOutputStream(socket.getOutputStream());
// Lecture d'une requête à partir du
navigateur
............................................
System.out.println("Requête
lue : " + requete);
// Analyse et tentative de réponse
à la requête
// requête uniquement de type GET
// les autres types de requêtes sont ignorées
StringTokenizer stringTokenizer = new
StringTokenizer(requete);
if((stringTokenizer.countTokens() >=
2) && stringTokenizer.nextToken().equals("GET"))
{
System.out.println("Le premier token est : GET");
requete = stringTokenizer.nextToken());
if(requete.endsWith("/") ||
requete.equals(""))
{
System.out.println("Requete se terminant par / ou espace ");
System.out.println(" concaténée avec index.html");
............................................
System.out.println("Requête modifiée : " + requete);
}
System.out.println("Lecture du fichier : " + requete);
FileInputStream fileInputStream = new
FileInputStream(requete);
// Déclaration d'une variable capable de
recevoir les octets
// du fichier. Sa taille est égale au nombre d'octets pouvant
// être lus sans blocage.
byte[] data = new
byte[fileInputStream.available()];
// Lecture du fichier
............................................
// Envoi du tableau de bytes au client.
............................................
byteOutput.flush();
}//end if
stringTokenizer.countTokens...
else
// Lorsque la requête n'est pas GET
outputStream.println("<HTML><BODY><P>
400 Requête non interprétée
<P></BODY></HTML>");
System.out.println("Socket
fermée");
............................................
}
catch(FileNotFoundException e){
outputStream.println("<HTML><BODY><P>404
Fichier non trouvé<P></BODY></HTML>");
try{
socket.close();
System.out.println("Socket fermée");
}catch(IOException
evt){System.out.println(evt);}
}//fin catch FileNotFoundException
catch(SecurityException e){
System.out.println(e);
e.printStackTrace();
outputStream.println("<HTML><BODY><P>403
Opération interdite<P></BODY></HTML>");
outputStream.println(e);
try{
socket.close();
System.out.println("Socket fermée");
}catch(IOException
evt){System.out.println(evt);}
}//fin catch SecurityException
catch( IOException e){
System.out.println( "Erreur
d'E/S " + e );
try{
socket.close();
System.out.println("Socket fermée");
}catch(IOException
evt){System.out.println(evt);}
}//fin catch IOException
}//fin méthode run
A ce stade, le serveur HTTP peut être
lancé sur la machine joule.cnam.fr.
Il faut, pour la poursuite du programme céer 2 fichiers l'un
nommé
index.html dans le
répertoire du serveur, l'autre dans le répertoire
père.
Implantation du client
Syntaxe simplifiée de la commande GET :
GET nom_du_fichier
import
java.net.*;
import java.io.*;
class
HTTPClient
{
public static void main(String[] args)
{
String serveur = "joule.cnam.fr";
int port = 8080;
try{
// Création d'une socket, connectée
au serveur HTTP
// sur le port spécifié.
.......................................
// Création d'un flot d'entrée
à partir de la socket
.......................................
// Création d'un flot de
sortie sur la socket
.......................................
//
Envoi d'une commande GET au serveur
// le fichier requis appartient au répertoire père
.......................................
// le fichier requis est le fichier index.html du
répertoire courant
.......................................
String ligne = null;
// Boucle de lecture et d'affichage à
l'écran tant que null n'est pas reçu
.......................................
.......................................
// Fermer la socket
.......................................
}//fin try
catch(UnknownHostException e){
System.out.println(e);
System.out.println("l'hôte n'est pas activé");
}//fin catch
UnknownHostException
catch(IOException e){System.out.println(e);}
}
}
Lancer ce programme sur la machine newton.cnam.fr
Un troisième exercice
Il s'agit de reprendre une partie du projet gestion des
comptes bancaires. Un compte est une instance de la classe Compte :
public class Compte{
private String
numero;
private float
solde;
public Compte(
String numero, float solde ){
this.numero
= numero;
this.solde
= solde;
}
public void
crediter( float montant ){
solde = solde + montant;
}
public void
debiter( float montant ){
solde = solde - montant;
}
public float quelSolde(){ return solde; }
// on se limite à ces méthodes
}
On considère un serveur capable de gérer un compte courant. Son titulaire peut l'interroger et/ou le débiter à distance.
On demande d'écrire le programme en posant que le compte est crée avec un numéro fixé initialement ainsi qu'un solde quelconque. Le client se contentera d'interroger le compte puis de le débiter d'une somme quelconque.