Plan du polycopié
Cette deuxième partie du cours POD suppose de connaître le noyau objet
du langage Java comme présenté dans le cours de tronc commun de maîtrise
interfaces et architectures d'applicatons. Les ouvrages indiqués dans la
bibliographie permettent une mise à niveau, en particulier l'ouvrage ``Java : de l'esprit à la méthode'' est une introduction au langage Java.
La première section introduit les processus légers de Java qui implante
les moniteurs et a une gestion des priorités entre threads ainsi que la
notion de groupe de threads.
La deuxième section décrit rapidement la bibliothèque AWT pour la construction d'interface graphique. Elle montre les difficultés de communication entre les objets source d'événements ``utilisateurs'' et les objets traitants ces actions. C'est l'occasion d'introduire les classes locales qui facilitent cette communication.
La troisième section montre qu'une applet n'est en fait qu'un composant graphique de base (Panel) qui implante l'interface des processus légers (Runnable).
La quatrième section revient sur les prises de communication Internet (socket)
en décrivant les principales classes Java pour leur mise en oeuvre.
La cinquième section présente le modèle de programmation parallèle à mémoire
distante (distincte ou partagée) et introduit les objets répartis.
Ceux-ci sont mis en oeuvre via la bibliothèque RMI (Remote Method Invocation)
qui autorise l'envoi d'un message à un objet distant.
La sixième section s'intéresse à la connaissance des objets dans un programme.
Cette introspection va permettre d'inspecter des classes et des objets dont onne connait pas le source.
La septième section montre le mécanisme de chargement dynamique
de classes.
La dernière section effectue une comparaison des langages Objective Caml et Java, tant au niveau du noyau objet et de son typage, qu'au niveau des bibliothèques et des outils et environnement de développement.
Planche : 1
Bibliographie
-
``Java in a Nutshell'', D. Flanagan, O'Reilly, 1997
Très bon pour le programmeur C ou C++, JDK 1.1
- ``Java : de l'esprit à la méthode'',
M. Bonjour, G. Falquet, J. Guyot et A. Le Grand
Vuibert, 2e édition, 1999.
Très clair, bonne pédagogie, adapté au JDK 1.2
- ``Just JAVA and Beyond'', P. van der Linden, SUN Press 1998.
Très bonne présentation avancée, JDK 1.1
- ``Data Structures and Algorithms in Java'', M. T. Goodrich,
R. Tamassia, Wiley 1998.
Algorithmique en Java + un ``Primer'' en annexe, JDK 1.1
- ``Java et Internet'', G. Roussel et E. Duris
Vuibert, 2000
Présentation complète de la partie réseau et distribution du langage.
Planche : 2
Processus légers (Thread)
Modèle de parallélisme à mémoire partagée
-
contexte d'exécution sur une même machine virtuelle
- même zone mémoire (¹ fork)
- ne dépend pas du système (Windows 3.1)
- possède un nom et une priorité
- pas de préemption à priorité égale
Þ le faire à la main
dépend de l'implantation de la JVM
- ne va pas plus vite
- sert à exprimer des algos concurrents
Un programme exécutant plusieurs threads attendra que toutes soient finies
avant de se terminer.
Un thread s'exécute jusqu'au moment où
-
une thread de plus grande priorité devient ``exécutable'';
- une méthode yield ou sleep est lancée;
- ou que son quota de temps a expiré pour les systèmes implantant un temps partagé.
Il y aura une différence de comportement selon l'implantation de la JVM
sur les systèmes gérant le temps partagé et les autres. Dans le premier
cas le multi-threading sera préemptif alors que dans le second cas
il sera coopératif. Pour écrire un code portable sur différents systèmes,
il est préférable de ne pas faire d'hypothèse sur l'implantation des threads.
Les threads JAVA implantent un mécanisme de ``moniteurs'' protégeant
l'accès aux objets ``synchronisés''. L'exclusion mutuelle s'effectue
sur l'appel des méthodes déclarées synchronized
dans la définition de la classe de l'objet receveur.
Une seule exécution d'une méthode synchronized peut être effectuée
en même temps, les autres appels sont alors bloqués. Il existe deux méthodes
primitives principales wait et notify qui permettent une communication entre threads ayant accès au même moniteur.
Planche : 3
Création et Exécution
2 possibilités :
-
sous-classer la classe Thread
et redéfinir la méthode public void run()
MCThread x = new MCThread (); x.start();
- Implanter l'interface Runnable
et implanter public void run
MCTheadI x = new MCThreadI();
MCThreadI y = new Thread(x,"Nom"); y.start()
Quand start() retourne, la tâche termine.
class TestThreads {
public static void main (String []args) {
MyThread mt1 = new MyThread(1);
MyThread mt2 = new MyThread(2);
mt1.setPriority(10);
mt2.setPriority(10);
mt1.start();
mt2.start();
}
}
class MyThread extends Thread {
int num;
int st = 1;
MyThread(int n) { num = n;}
public void run() {
int i=0;
while (st < 5000001) {
if ((st % 1000000) == 0) {System.out.println("MT" + num+ " trace = "+st);}
st++;
yield();
}
}
}
Planche : 4
Méthodes sur les threads
-
start() et stop()
- suspend() et resume()
- wait() et notify (synchronisation)
- sleep(i) en millisecondes
- yield()
- join()
- setPriority(i)
Un thread une fois crée peut être dans les états suivants :
-
exécutable
- en cours d'exécution
- bloqué
- mort
La méthode start() rend un thread ``exécutable'', et lance la méthode run(). Le thread se termine quand cette méthode a fini son exécution.
La méthode stop() arrête un thread (dépréciée).
Les méthodes suspend() (dépréciée), sleep(i) et wait met un thread dans l'état bloqué.
Les méthodes resume() (dépréciée) et notify() fait passer un thread de l'état ``bloqué'' à l'état ``exécutable''.
La méthode yield() fait passer de l'état ``en cours d'exécution'' à l'état exécutable.
La méthode join() attend la fin d'un thread.
La méthode setPriority(i) modifie la priorité d'un thread (valeur entre MIN_PRIORITY et MAX_PRIORITY.
Planche : 5
Relations entre threads
-
sans relation
- avec relation mais sans synchronisation
- relation d'exclusion mutuelle
- relation d'exclusion mutuelle avec communication
Exemple de deux threads sans relation
public class SR {
public static void main ( String []s) {
Thread t1 = new Aff("Bonjour");
t1.start();
new Aff("Au revoir").start();
}
}
class Aff extends Thread {
String txt;
Aff (String s){txt=s;}
public void run () {
while(true) {
ES.writeln(txt);
yield();
}
}
}
Planche : 6
Relation sans synchronisation
sur une même structure de données à des endroits différents :
Exemple : calcul de la nouvelle génération du jeu de la vie
par différentes threads.
ou ouverture d'une connexion sur une socket
Planche : 7
Synchronisation
Verrou (accès exclusif) sur un objet :
synchronized (o) { ... }
}
// ou sur une methode
synchronized type nom_methode( ..) { ...}
Une seule thread peut prendre le verrou (les autres attendent).
Le verrou est rendu à la sortie du bloc de synchronisation.
Planche : 8
Relation d'exclusion mutuelle : moniteurs
Dès que le verrou est pris :
-
appel d'une méthode synchronized
il garantit qu'une seule méthode ``synchronisée'' peut être
appelée sur cet objet
- bloc synchronized sur un objet
il verouille l'objet.
Un moniteur est associé à une donnée et aux fonctions qui verrouillent
l'accès à cette donnée. En Java, cette donnée sera un objet et les fonctions
les méthodes déclarées synchronized dans la définition de la classe
de cet objet.
Il y a création d'un moniteur pour chaque objet qui possède au moins
une méthode synchronized.
Quand un thread a pris le moniteur sur un objet (c'est-à-dire est entré dans
l'exécution d'une méthode ``synchronisée''), les autres threads
voulant exécuter une méthode ``synchronisée'' sur cet objet sont bloqués.
Quand le premier thread a terminé l'exécution du code de la méthode ``synchronisée'' elle libère le moniteur qui peut alors être pris par un autre thread.
Les moniteurs permettent aussi la communication entre threads par le mécanisme
d'attente/notification (wait et notify).
Planche : 9
Communication
à l'intérieur d'un bloc de synchronisation
-
o.wait() : relache le verrou et attend une notification
- o.notify() : relance une tâche en attente (une au hasard). Elle doit
en premier réacquérir le verrou.
- o.notifyAll() : relance toutes les tâches.
Planche : 10
Exclusion mutuelle avec communication
Producteur/Consommateur :
// thread du producteur // thread consommateur
entree code sync entree code sync
while(buffer plein) while (buffer vide)
wait() wait()
produit_1() consomme_1()
notify() notify()
Exemple : un producteur/consommateur
public class Sync {
public static void main (String [] args) {
Producteur p = new Producteur();
p. start();
Consommateur c = new Consommateur(p);
c.start();
}
}
class Consommateur extends Thread {
Producteur mien;
Consommateur(Producteur un){mien=un;}
public void run() {
while(true) {
String r = mien.consomme();
System.out.println("Consommation : "+r);
try {sleep((int)(1000*Math.random()));}
catch (Exception e0) {}
}
}
}
class Producteur extends Thread {
private String [] buffer = new String [8];
private int ip = 0;
private int ic = 0;
public void run() {
while(true) {
produce();
}
}
synchronized void produce() {
while (ip-ic+1 > buffer.length) {try{wait();} catch(Exception e) {} }
buffer[ip % 8] = "Machine "+ip;
System.out.println("produit : ["+(ip % 8)+"] "+buffer[ip % 8]);
ip++;
notify();
}
synchronized String consomme() {
while (ip == ic) {try{wait();} catch(Exception e) {}}
notify();
return buffer[ic++%8];
}
}
Planche : 11
Abstract Windowing Toolkit
Paquetage de classes pour la construction d'interfaces graphiques :
-
système graphique indépendant d'une plateforme
Þ avoir les boutons Mac sur MAc, Motif sous Motif, ...
- avoir une API de porgrammation simple (??)
- qui pourra être intégré dans un navigateur.
Planche : 12
Hiérarchie de classes
Planche : 13
Composants
objet abstrait ayant : une position, une taille, dessinable et pouvant recevoir des évènements
-
composants simples : Button, Label, ...
- conteneurs : pouvant recevoir d'autres composants
possèdent un contexte graphique (Graphics).
Planche : 14
Conteneurs
-
Ceux qui n'ouvrent pas de fenêtres : Panel, Applet
- Ceux qui ouvrent une fenêtre : Dialog, FileDialog et Frame
Pouvant suivre différents algorithmes de placement de composants.
Application graphique : instancie au moins une fois une Frame
Applet : est un composant graphique
Exemple de dessin
import java.awt.*;
import java.awt.event.*;
class TestXframeD {
static public void main(String [] args) {
XframeD d = new XframeD();
}
}
class XframeD extends Frame {
Canvas p;
Color c1 = Color.blue;
Color c2 = Color.red;
Color c=c1;
XframeD() {
super("DESSIN");
p = new Canvas();
p.setSize(400,300);
this.setSize(420,320);
this.add(p);
this.pack();
this.show();
}
public void paint( Graphics gs) {
Graphics g = p.getGraphics();
if (c.equals(c1)){c=c2;}
else {c=c1;}
g.setColor(c);
g.drawString("Mon dessin",160,40);
for (int i = 0; i< 10; i++) {
g.fillRect(40+i*20,100,10,10);
}
}
}
Créer une interface
import java.awt.*;
import java.awt.event.*;
class TestXframe {
static public void main(String [] args) {
Xframe e = new Xframe();
e.init_event();
}
}
class Xframe extends Frame {
String monlogin ="emmanuel";
String monpasswd ="pilpoil";
TextField login;
TextField passwd;
Panel p;
Xframe() {
super("LOGIN");
p = new Panel();
p.setSize(400,500);
login = new TextField(8);
passwd = new TextField(8);
p.add(new Label("Login : "));
p.add(login);
p.add(new Label("Password : "));
passwd.setEchoChar('*');
p.add(passwd);
this.add(p);
this.pack();
this.show();
}
void init_event() {
}
}
Planche : 15
Evénements
En JDK 1.1 : modèle par délégation.
Planche : 16
Sources et délégués
Les composants (Source) peuvent déclencher des évènements
Ceux-ci seront traités par les ``délégués'' enregistrés:
instances de classe implantant des interfaces `Listener
public <TypeEvt>Listener set<TypeEvt>Listener(
<TypeEvt>Listener monDelegue)
public <TypeEvt>Listener add<TypeEvt>Listener(
<TypeEvt>Listener monDelegue)
public <TypeEvt>Listener remove<TypeEvt>Listener(
<TypeEvt>Listener monDelegue)
où <TypeEvt>
est remplacé par un événement :
addMouseListener(MouseListener monDelegue)
Planche : 17
Evénements et Composants
Chaque composant peut déclencher certains événements :
un Panel pourra déclencher un événement souris
(MouseEvent).
Un délégué implante une interface Listener.
Il existe des adaptateur implantant certaines interfaces (méthodes
> 1).
Listener Interface |
Adaptater Class |
Methods |
ActionListener |
NON |
actionPerformed |
MouseListener |
MouseAdapter |
mousePressed ... |
Exemple de délégué
import java.awt.*;
import java.awt.event.*;
class AdaptateurAction implements ActionListener {
XframeE A;
AdaptateurAction(XframeE a) {
A=a;
}
public void actionPerformed(ActionEvent e) {
if ((e.getSource() == A.login) || (e.getSource() == A.passwd))
{ if ((A.login.getText().equals(A.monlogin)) &&
(A.passwd.getText().equals(A.monpasswd)))
{A.OK=true; A.good();}
else {A.nogood();}
}
}
}
Exemple d'association de traitement d'événements
import java.awt.*;
import java.awt.event.*;
class TestXframeE {
static public void main(String [] args) {
XframeE e = new XframeE();
e.init_event();
}
}
class XframeE extends Xframe {
boolean OK = false;
void init_event () {
AdaptateurAction aa;
aa = new AdaptateurAction(this);
login.addActionListener(aa);
passwd.addActionListener(aa);
}
void good() {
ES.writeln("C'est parti");
System.exit(0);
}
void nogood() {
ES.writeln("Essaie encore!!!");
}
}
Cet exemple utilise les classes définies pages ?? et ??.
Planche : 18
Classes locales
Pour simplifier la communication entre le délégué et le
composant :
le JDK 1.1 a introduit les classes locales ou internes (y compris anonymes).
Þ cela permet de ne pas passer le composant comme variable
du délégué.
Les variables dínstance du composant sont directement accessibles.
Planche : 19
Déclaration de classes locales
-
nouveau membre (champs) d'une classe
- à l'intérieur d'un bloc d'instructions
Premier cas : accessible par la notation ``point''
Deuxième cas : non accessible en dehors du bloc
mais la valeur peut sortie du bloc (si implantation d'une interface)
Exemple d'une classe locale
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
class Test3_1 extends Applet {
class AdaptateurSouris extends MouseAdapter
implements MouseListener {
public void mousePressed (MouseEvent e)
{
int x = e.getX();
int y = e.getY();
System.out.println("x = " + x + " y = "+y);
getGraphics().drawString("salut tout le monde",x,y);
}
}
Test3_1() {
MouseListener clic = new AdaptateurSouris();
addMouseListener(clic);
}
public void init () {
this.setBackground(Color.white);
}
}
Le constructeur Test3_1() crée une instance de la classe
locale AdaptateurSouris qui est immédiatement ajoutée comme
traiteur des événements souris à l'applet Test3_1() construite.
Planche : 20
Classe (locale) anonyme
Dans les cas où le nom de la classe n'a pas d'importance :
elle peut être déclarée anonymement!!!
Exemple d'une classe locale anonyme
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
class Test3_2 extends Applet {
Test3_2() {
MouseListener clic = new MouseAdapter () {
public void mousePressed (MouseEvent e)
{
int x = e.getX();
int y = e.getY();
System.out.println("x = " + x + " y = "+y);
getGraphics().drawString("salut tout le monde",x,y);
}
};
addMouseListener(clic);
}
public void init () {
this.setBackground(Color.white);
}
}
Planche : 21
Applets
La classe Applet hérite de Panel et implante Runnable.
Une applet possède une zone graphique (conteneur Panel) qui n'ouvre
pas une nouvelle fenêtre.
Planche : 22
cycle de vie
init()Þstart()Þstop()Þdestroy() où :
-
init() : appelée au démarrage de l'applet(initialisation);
- start() : appelée pour lancer l'applet (après l'initialisation ou
après un stop()),
effectue le travail;
- stop() : appelée pour arrêter l'applet (quand la page HTML disparaît);
- destroy() : appelée pour libérer les ressources
allouées par l'applet (juste avant la disparition de l'applet).
void paint(Graphics g) : sera appelée à chaque réaffichage.
Planche : 23
Exécution
-
Ecrire un fichier ``HTML''avec une balise
<APPLET>... </APPLET>
- Lancer
appletviewer
sur ce fichier
- Télécharger ce fichier dans un navigateur :
HotJava, Communicator et I-Explorer
Planche : 24
Balise
<html>
<head> Exercices en Java
</head>
<body>
<H1> Test </H1>
<P>
<applet code="Test1" height=400 width=400>
<P><EM> Not a java-powered browser! </EM>
</applet>
</body>
</html>
Applet de dessin
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
public class graf extends Applet {
public void paint(Graphics g) {
g.drawRect(25,30,60,40);
g.drawRect(125,30,100,100);
g.setColor(Color.cyan);
g.drawOval(25,30,60,40);
g.drawOval(125,30,100,100);
}
}
Applet et applications
Il peut être utile de créer une application qui lance une applet. Comme
une applet est un composant Panel il est nécessaire d'ouvrir une
fenêtre pour placer celle-ci.
import java.awt.*;
public class grafa {
public static void main(String []args) {
Frame d = new Frame();
d.setSize(400,300);
graf g = new graf();
g.setSize(300,200);
d.add(g);
d.show();
g.init();
g.start();
d.paint(d.getGraphics());
}
}
Applet de login
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
public class passwdTest extends Applet {
String monlogin ="tartempi";
String monpasswd ="itaparit";
TextField login;
TextField passwd;
boolean OK = false;
ActionListener RC = new ActionListener() {
public void actionPerformed(ActionEvent e) {
if ((e.getSource() == login) || (e.getSource() == passwd))
{ if ((login.getText().equals(monlogin)) &&
(passwd.getText().equals(monpasswd)))
{OK=true; good();}
else {nogood();}
}
}
};
public void init() {
login = new TextField(8);
passwd = new TextField(8);
add(new Label("Login : "));
add(login);
add(new Label("Password : "));
passwd.setEchoChar('*');
add(passwd);
login.addActionListener(RC);
passwd.addActionListener(RC);
}
public void good() {
resize(120,180);
this.getGraphics().drawString("c'est parti...",10,150);
}
public void nogood() {
this.getGraphics().drawString("identification incorrecte",10,100);
}
}
Planche : 25
Persistance
conservation d'un objet en dehors de l'exécution courante :
dans un fichier ou un flux
But: récupération ultérieure dans le même programme
ou un autre.
Difficultés: :
-
lecture/écriture de données
- structures circulaires
- coercion de type à la relecture
- sauvegarde de code
En jdk1.1 : mécanisme de sérialisation!!!
Planche : 26
Sérialisation
-
classes de flux
-
ObjectOutputStream : flux de sérialisation
- ObjectInputStream : flux de désérialisation
- interface
-
Serializable : doit être implantée (vide) pour être sérialisé
Planche : 27
Que stocke-t-on?
Le stockage contient :
-
le nom et une clé (checksum) de la classe
- tous les champs (de données) sérialisables
La clé permet de vérifier la version de la classe.
Le modifieur transient permet d'indiquer qu'un champs ne doit pas être sérialisé.
possibilité de changer le mécanisme de sérialisaiton en implantant une classe Externalizable respectant les interfaces writeExternal
et readExternal.
La méthode void writeObject(Object) écrit dans un flux (de type ObjectOutputStream) une instance de classe qui implante l'interface Serializable.
La méthode Object readObject() lit dans un flux (de type ObjectInputStream) un persistant et retourne une valeur de type Object.
Il est alors
utile de faire une coercition de type (vers une sous-classe d'Object)
sur la valeur lue pour pouvoir la manipuler avec son bon type.
Pour que cette contrainte de type puisse avoir lieu, il est nécessaire que la classe soit connue du programme (ou chargeable à ce moment là). Si la classe n'existe pas
une exception est alors déclenchée de type ClassNotFound.
Comme la classe doit être connue par le programme qui récupère le persistant, il n'est pas nécessaire de conserver dans celui-ci le code des méthodes. Seuls
les champs de données ``sérialisés'' seront conservées dans le persistant.
Exemple
import java.io.*;
class Exemple20 implements Serializable {
String nom;
Exemple20 autres;
Exemple20() {nom=null;autres=null;}
Exemple20(String n, Exemple20 e) {nom=n;autres=e;}
boolean estVide() {return (nom == null);}
void detruit() {nom=null;autres=null;}
public String toString() {
if (estVide()) return "[]";
else if (autres.estVide()) return nom;
else return nom+"::"+autres.toString();
}
public void ajoute (String unNom) {
System.out.println("*");
Exemple20 e = new Exemple20(nom,autres);
autres=e;
nom=unNom;
}
}
class Execute {
public static void main (String[] args) {
Exemple20 e = new Exemple20();
ObjectOutputStream out;
ObjectInputStream in;
try {
e.ajoute("machin");
e.ajoute("truc");
System.out.println("1 : "+e);
out = new ObjectOutputStream(new FileOutputStream("Exemple20.dat"));
out.writeObject(e);
out.flush(); out.close();
e.detruit();
System.out.println("2 : "+e);
in = new ObjectInputStream(new FileInputStream("Exemple20.dat"));
e.ajoute("bidule");
e.autres = (Exemple20)in.readObject();
in.close();
System.out.println("3 : "+e);
}
catch (java.lang.ClassNotFoundException exc){System.err.println(exc);}
catch (StreamCorruptedException exc) {System.err.println(exc);}
catch (IOException exc) {System.err.println(exc);}
}
}
Planche : 28
Exécution
java Execute
*
*
1 : truc::machin
2 : []
*
3 : bidule::truc::machin
*
: trace de ajoute(..)
La classe Exemple20 est une classe pour les listes de chaînes de caractères. La représentation de la liste vide est une instance de cette classe dont
les variables d'instance nom et autres valent null.
Cette classe implante l'interface Serializable. Cette interface ne
demande pas l'écriture de méthodes particulières.
Les * indique l'ajout d'un élément à la liste. A la trace 1
il y a deux noms dans la liste.
Celle-ci est sauvée dans le fichier Exemple20.dat puis détruite dans
l'exécution du programme (trace 2).
Une nouvelle tête de liste est créée.
Le persistant stocké dans le fichier Exemple20.dat est ensuite relu
et son type est forcé en Exemple20 et ajouté en queue de liste.
Le résultat est affiché à la trace 3.
Il est à noter que la contrainte de type effectue en fait un test dynamique de type sur la valeur qui vient d'être lue. Si la classe indiquée n'existe pas une exception ClassNotFound est alors déclenchée.
Planche : 29
Sockets
paquetage : java.net : fournit un ensemble de classes pour communiquer sur le réseau Internet.
-
classe de bas niveau : pour les couches basses (DatagramPacket,...)
- et de haut niveau : pour les clients/serveurs
La classe InetAddress représente les adresses Internet. Elle est utilisée dans la classe Socket.
Planche : 30
Classes de haut niveau
-
ServerSocket : méthode accept retourne une socket quand une demande est reçue;
- Socket (classe finale) : pour la communication à travers le réseau. Les méthodes getOutputStream() et getInputStream
permettent de récupérer les canaux de communication.
- URL : permet de dialoguer via une URL.
Planche : 31
Exemple de client/serveur
Conversion de chaînes en MAJUSCULE via un serveur (Java : de l'esprit à
la méthode)
Le serveur est construit à partir de 2 classes :
-
Serveur : classe générique (au numéro du port près)
- Connexion : pour les canaux et le traitement
Lors d'une connexion d'un client au serveur (Serveur.run()) une nouvelle instance de Connexion est créée.
Partie serveur générique
Cet exemple provient de l'ouvrage ``Java : de l'esprit à la méthode''.
import java.io.*;
import java.net.*;
public class Serveur extends Thread {
protected static final int PORT =45678;
protected ServerSocket ecoute;
Serveur ()
{ try
{ ecoute = new ServerSocket(PORT);}
catch (IOException e)
{System.err.println(e.getMessage());
System.exit(1);
}
System.out.println(
"Serveur en ecoute sur le port : "+PORT);
this.start();
}
public void run ()
{ try
{while (true)
{Socket client=ecoute.accept();
Connexion c = new Connexion (client);}}
catch (IOException e)
{System.err.println(e.getMessage());
System.exit(1);}
}
public static void main (String[] args)
{new Serveur();}
}
Partie serveur spécifique
class Connexion extends Thread {
protected Socket client;
protected DataInputStream in;
protected PrintStream out;
public Connexion(Socket client_soc)
{ client=client_soc;
try
{ in=new DataInputStream(client.getInputStream());
out=new PrintStream(client.getOutputStream());}
catch (IOException e)
{try {client.close();}
catch (IOException e1){}
System.err.println(e.getMessage());
return;}
this.start();
}
public void run()
{ try
{ while (true)
{String ligne=in.readLine();
if (ligne.toUpperCase().compareTo("FIN")==0) break;
System.out.println(ligne.toUpperCase());
out.println(ligne.toUpperCase());
out.flush();
}}
catch (IOException e)
{System.out.println("connexion : "+e.toString());}
finally
{try {client.close();} catch (IOException e) {}}
}
}
Partie client
import java.io.*;
import java.net.*;
public class Client {
protected static final int PORT=45678;
public static void main(String[] args)
{
Socket s=null;
if (args.length != 1)
{System.err.println("Usage: java Client <hote>");
System.exit(1);}
try
{ s=new Socket (args[0],PORT);
DataInputStream canalLecture =
new DataInputStream(s.getInputStream());
DataInputStream console =
new DataInputStream (s.getInputStream());
PrintStream canalEcriture =
new PrintStream(s.getOutputStream());
System.out.println("Connexion etablie : "+
s.getInetAddress()+" port : "+s.getPort());
String ligne = new String();
char c;
while (true)
{ System.out.print("?"); System.out.flush();
ligne = "";
while ((c=(char)System.in.read()) != '\n') {ligne=ligne+c;}
canalEcriture.println(ligne);
canalEcriture.flush();
ligne=canalLecture.readLine();
if (ligne == null)
{System.out.println("Connexion terminee"); break;}
System.out.println("!"+ligne);
}
}
catch (IOException e) {System.err.println(e);}
finally
{ try {if (s != null) s.close();}
catch (IOException e2) {}
}
}
}
Planche : 32
Execution
[chaillou@ippc56]$ java Serveur
Serveur en ecoute sur le port : 45678
ADIEU
MONDE CRUEL
coté client
[chaillou@ippc56]$ java Client ippc56
Connexion etablie : ippc56/134.157.116.56 port : 45678
?adieu
!ADIEU
?monde cruel
!MONDE CRUEL
?fin
Connexion terminee
Planche : 33
Communication avancée
Pour établir un nouveau service, il est souvent nécessaire
de définir un ``protocole'' de communication entre le
serveur et les clients.
...
Planche : 34
Objets distribués
Possibilités:
clients/serveurs + persistance Þ transport d'objets
par copie (et création de nouvelles instances)
Références distantes: références de plusieurs endroits du réseau au même objet
pour invoquer ses métodes et/ou modifier ses variables d'instances.
Planche : 35
Difficultés de mise en oeuvre
-
transparence référentielle
- Garbage Collector
- typage des objets copiés
- exceptions distantes
le jdk 1.1 offre un mécanisme simple nommé RMI (Remote Method Invokation) permettant de manipuler des objets distants.
Planche : 36
RMI
-
objet distant : sur une autre machine virtuelle Java
- garde le même modèle objet
- utilisation d'interface étendant Remote
- passage de paramètre et résultat par copie (Serializable)
Planche : 37
Structure générale
client serveur
^ ^
| |
| |
| |
v v
--------------- ---------------
| stub | | skeleton |
-----------------------------------
| couche des references |
-----------------------------------
| couche transport |
-----------------------------------
Planche : 38
Serveur
Le serveur s'en trouve compliqué sur les services suivants :
-
GC des objets distribués
- réplication d'objets distants
- activation d'objets persistants
Planche : 39
Paquetages
Les paquetages utiles sont :
-
java.rmi : pour le coté client
- java.rmi.server : pour le coté serveur
- java.rmi.registry : pour l'utilisation d'un service d'enregistrement
et de référentiel des objets serveurs,
- tt java.rmi.dgc : pour le GC distribué.
L'interface d'objets distants étend l'interface Remote en ajoutant les méthodes désirées qui peuvent déclencher des RemoteException.
Planche : 40
Exemple : Points distants
Interface:
import java.rmi.*;
public interface PointRMI extends Remote {
void moveto (int a, int b) throws RemoteException;
void rmoveto (int dx, int dy) throws RemoteException;
void affiche() throws RemoteException;
double distance() throws RemoteException;
}
Planche : 41
Implantation d'interfaces distantes
Implantation de l'interface:
import java.rmi.*;
import java.rmi.server.UnicastRemoteObject;
public class PointD extends UnicastRemoteObject
implements PointRMI {
int x,y;
PointD(int a, int b) throws RemoteException {x=a;y=b;}
PointD() throws RemoteException {x=0;y=0;}
public void moveto (int a, int b)
throws RemoteException
{ x=a; y=b;}
public void rmoveto (int dx, int dy)
throws RemoteException
{ x = x + dx; y = y + dy;}
public void affiche()
throws RemoteException
{ System.out.println("(" + x + "," + y + ")");}
public double distance()
throws RemoteException
{ return Math.sqrt(x*x+y*y);}
}
Planche : 42
Compilation
-
interface et implantation par : javac
- stubs et skeletons par : rmic de la manière suivante :
rmic PointD
qui créera les fichiers PointD_Stub.class
et
PointD_Skel.class
.
Planche : 43
création et enregistrements
Le serveur de points lui va créer des instances de PointD et les enregistrer (rebind)
auprès du démon (rmiregistery) qui gère le protocole rmi.
import java.rmi.*;
import java.rmi.server.UnicastRemoteObject;
public class Creation {
public static void main (String args[]) {
System.setSecurityManager(new RMISecurityManager());
try {
PointD p0 = new PointD();
PointD p1 = new PointD(3,4);
Naming.rebind("//youpou.lip6.fr/point0",p0);
Naming.rebind("//youpou.lip6.fr/point1",p1);
System.out.println("Objets distribues 'p0' " +
"et 'p1' sont enregistres");
}
catch (Exception e) { e.printStackTrace();
}
}
}
Planche : 44
Commentaires
-
le ``security manager'' garantit (??) qu'il n'y aura pas d'opérations illégales.
- l'enregistrement nécessite de nommer chaque instance
La classe Naming permet de nommer des objets pour leur enregistrement sur le serveur (bind,
rebind, unbind, list, lookup).
Compilation classique avec javac.
Planche : 45
un client
import java.rmi.*;
public class Client {
public static void main( String argv[]) {
String url0="rmi://youpou.lip6.fr/point0";
String url1="rmi://youpou.lip6.fr/point1";
try {
PointRMI p0 = (PointRMI)Naming.lookup(url0);
PointRMI p1 = (PointRMI)Naming.lookup(url1);
p0.affiche(); p1.affiche();
p0.rmoveto(7,12);
p1.rmoveto(5,6);
p0.affiche(); p1.affiche();
if (p0.distance() == p1.distance())
System.out.println("c'est le hasard");
else
System.out.println("on pouvait parier");
}
catch (Exception e) {
System.err.println("exception : " +
e.getMessage());
e.printStackTrace();
} } }
Planche : 46
Exécution
1ère exécution:
(0,0)
(3,4)
(7,12)
(8,10)
on pouvait parier
2ème exécution:
(7,12)
(8,10)
(14,24)
(13,16)
on pouvait parier
Planche : 47
Exceptions
Si le démon ne fonctionne plus au moment de l'utilisation d'un objet distant, on récupère l'exception dans le catch et on affiche les messages suivants :
exception : Connection refused to host;
...
et si l'objet demandé n'est pas enregistré, la suivante :
exception : point0
java.rmi.NotBoundException: point0
Planche : 48
port de communication
Par défaut, le port du service rmi est le 1099. Il est possible de changer de numéro. Pour cela
le serveur d'enregistrement doit être lancé avec le paramètre du port :
rmiregistry 2000&
et il faut indiquer ce nouveau port aux URL employées :
//youpou.lip6.fr:2000
Planche : 49
CORBA
Common Object Request Broker Architecture : norme (OMG - 1990)
permettant :
-
d'être indépendant des langages
- d'avoir des services d'enregistrements plus complets
- de ne pas dépendre d'un constructeur :
de nombreux ORB, y compris gratuit
Pour être indépendant CORBA définit le langage IDL.
Planche : 50
IDL
Langage de définition d'interfaces qui permet de décrire les
méthodes et champs visibles des objets.
Accepte l'héritage simple et multiple d'interfaces.
Point:
interface Point {
void moveto(in int a, in int b);
void rmoveto(in int dx, in int dy);
void affiche();
double distance();
}
A partir de cette description, le compilateur IDL (fourni avec un ORB), engendreles
stubs et les skeletons pour le serveur et cela pour différents langages (Smalltalk, C++, Java ...)
Planche : 51
Introspection
Objectifs: : explorer et de modifier des valeurs (des objets en Java) existant pendant une exécution.
Pour cela une classe elle-même est considérée comme un objet, ce qui permet :
-
de connaître la classe d'un objet et obtenir la description d'une classe;
- créer des objets sans connaître son nom à l'exécution;
- accéder et modifier les champs de données;
- invoquer une méthode
mais ne permet pas de définir une nouvelle classe ni ne modifier la hiérarchie des classes existante,
Il existe un véritable besoin de pouvoir explorer dynamiquement (sans connaître les sources de la compilation) l'intérieur des objets et des classes Java.
Cela permet de construire (facilement) des extensions au langage et de pouvoir construire des environnements visuels (ne faisant qu'explorer de l'extérieur)
comme par exemple pour les Java Beans.
Planche : 52
L'API java.lang.reflect permet, si les conditions de sécurité l'acceptent de :
-
construire de nouvelles classes et des tableaux
- d'accéder et de modifier des champs d'objets existants
- d'invoquer des méthodes de classes ou d'objets
- d'accéder et de modifer des element des tableaux.
les domaines d'applications sont :
-
accès de l'extérieur (JavaBeans);
- outils de développement, browser de classes, débogueurs, interprète, décompilateur.
Planche : 53
classes pour l'introspection
Planche : 54
class Class
méthodes principales:
-
static Class forName(String)
- Object newInstance()
- Field [] Fields()
- Method [] Methods()
La classe Class représente une classe Java. On construit un objet Class soit en envoyant le message getClass() sur une instance, soit en la chargeant dynamiquement avec la méthode Class.forName auquel on passe le nom de la méthode sous forme de chaîne de caractères.
On peut à partir d'une classe créer une nouvelle instance de celle-ci :
la méthode newInstance() crée une nouvelle instance de la classe qui reçoit ce message en utilisant son constructeur sans paramètre.
D'autre part il est possible de récupérer les informations d'une classe (champs, méthodes, constructeurs, ...). On obtient alors des instances des
classes : Field, Method, Constructor sous forme de tableaux. L'exemple suivant montre comment afficher ces informations.
Exemple d'exploration
Ce programme liste tous les attributs et méthodes publics d'une classe passée en paramètre :
import java.lang.reflect.*;
public class Lecture {
public static void main(String args[]) {
Class c = null;
Field[] champs = null;
Method[] methodes = null;
try {
c = Class.forName(args[0]);
champs = c.getDeclaredFields();
methodes = c.getMethods();
}
catch (ClassNotFoundException e) { // ...;
System.exit(0);}
catch (SecurityException e) { // PB d'autorisation
System.exit(0);}
for (int i=0; i< champs.length;i++) {
Field uc = champs[i];
System.out.println("champs "+i+" : "+uc);
}
for (int i = 0; i < methodes.length; i++) {
Method um = methodes[i];
System.out.println("methodes "+i+ " : " + um);
}
}
}
Son exécution donne :
$ java Lecture java.lang.Boolean
champs 0 : public static final java.lang.Boolean java.lang.Boolean.TRUE
champs 1 : public static final java.lang.Boolean java.lang.Boolean.FALSE
champs 2 : public static final java.lang.Class java.lang.Boolean.TYPE
champs 3 : private boolean java.lang.Boolean.value
champs 4 : private static final long java.lang.Boolean.serialVersionUID
methodes 0 : public static java.lang.Boolean java.lang.Boolean.valueOf(java.lang.String)
methodes 1 : public static boolean java.lang.Boolean.getBoolean(java.lang.String)
methodes 2 : public final native java.lang.Class java.lang.Object.getClass()
methodes 3 : public int java.lang.Boolean.hashCode()
methodes 4 : public boolean java.lang.Boolean.equals(java.lang.Object)
methodes 5 : public java.lang.String java.lang.Boolean.toString()
methodes 6 : public final native void java.lang.Object.notify()
methodes 7 : public final native void java.lang.Object.notifyAll()
methodes 8 : public final native void java.lang.Object.wait(long)
throws java.lang.InterruptedException
methodes 9 : public final void java.lang.Object.wait(long,int)
throws java.lang.InterruptedException
methodes 10 : public final void java.lang.Object.wait()
throws java.lang.InterruptedException
methodes 11 : public boolean java.lang.Boolean.booleanValue()
Il est aussi possible de créer des instances de classes à partir de la méthode newInstance envoyée à une objet de type Class. Cette méthode retourne un Object. Une fois un objet crée, on récupère ses champs par geField(name), et l'on peut accéder ou modifier la valeur de celui-ci (get et set).
Planche : 55
Chargeur de classes utilisateur
-
classe abstraite java.lang.ClassLoader :
-
charger le byte-code :
méthode : byte [] loadClassData (String)
- définir un objet Class à partie de cette suite d'octets :
méthode : Class defineClass(byte [], int,int)
- faire l'édition de liens :
méthode : resolveClass(Class)
- la méthode Class loadClass(String,bool) effectue ces tâches.
La machine virtuelle Java charge dynamiquement les classes dont l'exécution du programme en cours a besoin. L'option -verbose de l'interprète de byte-code de la machine
abstraite Java. Habituellement la machine virtuelle Java charge une classe à partir d'un fichier local. Ce chargement peut être dépendant du système (variable CLASSPATH sous Unix, ...). Néanmoins il peut avoir des situations où les classes doivent être chargées de manière différentes : classes distantes (accessibles à partir d'un serveur sur le réseau), format de fichier spécifique, conversion à la volée, modification de la sécurité. Pour ces cas, il est nécessaire de définir une sous-classe de la classe abstraite ClassLoader pour étendre le comportement de chargement.
L'exemple suivant, tiré du tutorial de Java, montre comment créer un chargeur de classes pour le réseau. La classe NetworkClassLoader définit deux méthodes :
loadClassData qui d'une URL retourne un tableau d'octets correspondant au code
transmis et loadClass (seule méthode abstraite de la classe ClassLoader)
pour le chargement effectif. Elle contient d'autre part une table de hachage pour connaître les classes déjà transférées. loadClass vérifie si le nom de la méthode
est déjà dans la table de hachage, si ce n'est pas le cas, elle transfère les données
et construit la classe à partir d'un tableau d'octets, stocke la classe dans la table de hachage puis déclenche resolveClass pour autoriser la création d'instances.
class NetworkClassLoader extend ClassLoader {
String host;
int port;
Hashtable cache = new Hashtable();
private byte loadClassData(String name)[] {
// load the class data from the connection
...
}
public synchronized Class loadClass(String name,
boolean resolve) {
Class c = cache.get(name);
if (c == null) {
byte data[] = loadClassData(name);
c = defineClass(data, 0, data.length);
cache.put(name, c);
}
if (resolve)
resolveClass(c);
return c;
}
}
Le code suivant montre comment créer une instance de la classe Main chargée dynamiquement par le nouveau chargeur.
ClassLoader loader= new NetworkClassLoader(host,port);
Object main= loader.loadClass("Main", true).newInstance();
...
Les navigateurs WWW, intégrant une machine virtuelle Java, implantent une sous-classe
de ClassLoader (abstraite) pour le transfert via le réseau des classes et pour modifier la sécurité (d'où un changement de comportement entre appletviewer et netscape).
Planche : 56
Comparaison O'Caml et Java
-
Noyau
- implantation
- typage
- bibliothèques
- outils de développement
Planche : 57
Noyau et implantation : Java
orienté objet (de classes) |
syntaxe à la C |
types de base, tableaux et objets (pas de pointeurs manipulables) |
seulement des méthodes (pas de fonctions) |
pas de classes paramétrées |
exceptions (vues comme des objets) |
récuperateur automatique de mémoire (GC) |
processus légers |
compilation vers du byte-code (portable) |
Planche : 58
Noyau et implantation : O'Caml
extensions objet du noyau fonctionnel avec traits impératifs |
type de données complexes (records, sommes,références) et classes |
polymorphe paramétrique, classes paramétrées |
exceptions comme valeurs |
récuperateur automatique de mémoire (GC) |
processus légers |
compilation vers du byte-code (portable) et vers du code natif |
Le langage Java est un langage objet à classe. Il possède aussi des types
de base comme les entiers pour des questions d'efficacité. Par contre il n'y
a pas de fonctions comme en C. Néanmoins les méthodes statiques peuvent jouer
ce rôle.
Le langage O'Caml possède une extension objet au dessus de son noyau fonctionnel et impératif. Seule la liaison retardée est implantée dans la mesure où les
fonctions et les modules simples permettent la liaison précoce.
Tous deux permettent la programmation multi-threading et ont un mécanisme
d'exceptions permettant de propager des valeurs structurées.
Leur implantation sont assez proches. Les deux langages peuvent produire
du code-octet pour une machine virtuelle. Les bibliothèques d'exécution
possèdent un mécanisme de récupération de mémoire (GC). Dans les deux cas
il n'y a pas de manipulation explicite des pointeurs bien que toute valeur
structurée (y compris les objets) sont représentées par un pointeur vers
le tas.
Le langage O'Caml possède un compilateur natif produisant un code efficace
pour les principales architectures (Intel, Power-OC, Sparc, HP-PA ...).
Planche : 59
Typage : Java
polymorphisme d'inclusion et ad hoc (de surcharge) |
surcharge résolue à la compilation |
pas complètement typé statiquement |
(vérification de type à l'exécution)) |
système de modules (correspondant à une hiérarchie de fichiers) |
Planche : 60
Typage : O'Caml
typé statiquement |
inférence de types |
polymorphisme paramétrique et polymorphisme d'inclusion |
contraintes de types explicites |
système de modules paramétrés |
Il manque à Java la notion de classes paramétrées (à la C++). Sa
résolution de la surcharge peut échouer à la compilation. Le type de this n'apparaît pas dans le type d'une méthode.
Il manque à O'Caml un mécanisme de surcharge. Il est difficile à implanter. Cela est du principalement à l'inférence de types.
De plus O'Caml possède l'héritage multiple.
bibliothèques et outils de développement
Java possède de très nombreuses bibliothèques fournies sous forme de
classes ou de composants (Java Beans). On peut citer l'interface graphique (AWT),
les classes réseaux (pour les clients/serveurs), les connexions aux bases de données (JDBC) et le mécanisme d'objets distribués (RMI). Les otuils de développement sont eux aussi nombreux et variés. Les principaux outils compatibles avec le jdk 1.1 sont JBuilder de Borland, Visual Age d'IBM. Ils intègrent des
débogueurs, des outils de constructions d'interfaces graphiques et ne compilation rapide.
O'Caml a un environnement moins riche, mais ses bibliothèques sont distribuées avec les sources. On note principalement l'interface graphique CamlTk, les
outils d'analyses syntaxiques (camllex, camlyacc et les streams intégrées au langage),
une artihmétique à précision arbitraite sur les réels. Le débogueur pour les systèmes Unix permet de revenir à des points d'exécution dans le passé. Un environnement de
développement a été développé pour le langage caml-light sous NextStep.
sécurité des applets
Les deux langages s'évertuent de construire des applets sûres. La différence principale ajoute de l'information de type au byte-code. Celle-ci sera vérifiée au chargement.
O'caml ajoute une authentification cryptée pour garantir la provenance.
portabilité, interopérabilité, efficacité et disponibilité
La technique de compilation vers du byte-code rend les deux langages facilement portable
sur différentes architectures de machine (Intel, Sparc, HP-PA, alpha, PowerPC, ...) et
sur différents système (Windows 95/NT, Unix sous X-window, MacOS).
Les deux langages s'interfacent avec des programmes écrits en C.
O'Caml possède un compilateur natif pour les principales architectures de machine.
Java permet de faire une expansion du byte-code Java en instructions de la machine hote
(technique Just In Time). Les premiers compilateurs natifs de Java (j2c, assymetrix) n'étaient pas très convaincant, mais d'autres plus efficaces devraient suivre, en particulier
la technique HotSpot de Sun.
O'caml est distribué gratuitement avec les sources à partir du site ftp de l'Inria
et peut être redistribué en l'état et les extensions soit sous forme de bibliothèque,
soit sous forme de fichiers patch indiquant les lignes modifiées par rapport à la version de distribution. Le navigateur MMM qui permet d'exécuter des applets en O'Caml est aussi distribué avec ses sources.
Le compilateur jdk de Sun est gratuit, mais les sources du système sont accordées sous licence. Les spécifications du langage sont publiques. Le navigateur Hotjava, développé
en Java, est gratuit d'utilisation. Les sources des navigateurs Netscape et Explorer
ne sont pas disponibles.
domaines d'applications
Ces deux langages ne jouent pas dans la même catégorie. Java est un langage qui vise à être utilisé dans les entreprises pour la majorité des développements (gestion, interface graphique, intranet, ...). O'Caml vient du monde académique et est
principalement utilisé dans celui-ci : enseignement, recherche publique et privée. Néanmoins le langage de communication
SCOL
pour les mondes virtuels, de la socièté CRYO, s'inspire fortement du noyau fonctionnel de Caml.
Java et O'Caml peuvent être considérés comme des langages "généralistes" et peuvent répondre à la majorité des besoins
pour le développement d'applications. industrielles, pour l'enseignement supérieur et pour les travaux de la recherche soit sur les langages, soit comme langage d'implantation.
Planche : 61
Liens
Ce document a été traduit de LATEX par
HEVEA.