Vous êtes sur la page 1sur 40

Concepts du langage Java

Les concepts expliqués sont : 1. Introduction 2. Introduction aux classes et aux o


bjets 3. Héritage 4. Visibilité des variables 5. Surcharge de méthodes 6. Interfaces 7
. Les Applications Java 8. Les Applets Java 9. Evénements 10. Le fenêtrage avec AWT
11. Processus légers (threads) 12. Exclusion mutuelle 13. Synchronisation : les méth
odes wait() et notify() 14. Les Exceptions 15. Type Wrappers 16. Incrémentation et
décrémentation 17. Les fichiers 18. Les connexions réseau TCP/IP - la classe Socket 1
9. Les serveurs TCP/IP - SocketServer 20. Une Application client-serveur
1. Introduction
Les pages qui constituent cette documentation hypertextuelle sur le langage Java
ont été créées au cours du travail de diplôme d'Alexandre Maret, au printemps 1996. Sur l
a page d'entrée, vous avez la possibilité d'accéder à deux index. Le premier est l'index
des concepts, qui vous offre une liste de pages d'explications sur des points p
récis de Java, ainsi que sur les classes standard qu'il intègre. Vous avez également a
ccès à l'index des règles BNF définissant Java. Il peut être utile de garder une fenêtre ou
erte sur la documentation de Sun sur les API Java. pendant la lecture des exempl
es. Pour plus de clarté, les exemples présents dans ces pages ont été simplifiés. Ainsi, b
ien
qu'il soit conseillé de protéger au mieux les variables à l'aide de modificateurs, cel
a n'est pas visible dans les exemples, afin d'éviter toute lourdeur. De même pour le
s problèmes de concurrence et d'exclusion mutuelle. Les méthodes données en exemple da
ns ces pages ne sont pas prévues pour être utilisées par plusieurs processus, car cert
ains problèmes pourraient en découler. Les conventions typographiques qui sont utili
sées dans cette documentation sont : Ecriture normale pour le texte
Ecriture code pour les mots clés et les codes sources
Italique pour les termes anglais Nous vous souhaitons une bonne lecture...
2. Introduction aux classes et aux objets
Une classe est un type d'objet. On peut comparer la description d'une classe à cel
le d'un article (record), contenant à la fois des données et des méthodes nécessaires au
traitement de celles-ci. On peut par exemple imaginer une classe Voiture qui co
ntiendrait des données - immatriculation, type - ainsi que des procédures localiser(
) . Ces procédures sont appelées méthodes en programmation orientée-objets, et elles opère
nt sur des variables contenues dans l'objet. Nous l'avons dit précédemment, une clas
se est en quelque sorte un type complexe. Lorsque l'on crée un objet d'une certain
e classe, on parle d'une instance de cette classe. Pour créer un objet, on fait ap
pel à un de ses constructeurs. Voyons comment créer une instance voiturePatron de la
classe Voiture, et comment la "localiser".
Voiture voiturePatron = new Voiture("patron","GE999",0,0); position = voiturePat
ron.localiser();
La première ligne crée une nouvelle instance de la classe Voiture, en fournissant 4
paramètres au constructeur. En deuxième ligne, nous faisons un appel à la méthode locali
ser() de l'instance voiturePatron. Le point marque la séparation entre le nom de l
'objet et le nom de la méthode de cet objet. Si l'on veut faire référence à une variable
contenue dans cet objet, on utilisera la même notation. Dans cet exemple, localis
er() ne prend pas de paramètre, mais il est tout de même requis de postfixer le nom
de la méthode avec des parenthèses vides. Des exemples plus précis sont donnés à la page dé
rivant la déclaration d'une classe. On peut aussi se reporter à la page héritage.
3. Héritage
L'avantage d'un langage orienté-objets est que le code est réutilisable. Grâce à l'héritag
e, on
peut modifier une classe sans avoir à la réécrire complètement. On dit que la classe nou
velle hérite de la classe ancienne . On dit aussi que nouvelle étend ancienne ou que
nouvelle est dérivée de ancienne. nouvelle est alors une sous-classe de ancienne, e
t ancienne une super-classe (surclasse) de nouvelle .
Dans la pratique, cela signifie qu'une instance de nouvelle aura la possibilité d'
utiliser des méthodes définies dans ancienne. Lorsque l'on code la classe nouvelle,
on a la possibilité de remplacer des méthodes de la super-classe (classe parente), s
implement en définissant une méthode avec le même nom et les mêmes paramètres. On remarque
ra qu'il n'est pas possible de remplacer un constructeur, puisque celui-ci porte
le nom de la classe dans laquelle il est déclaré, et que le nom des deux classes di
ffère obligatoirement. Nous disions précédemment qu'il est possible de modifier une cl
asse sans avoir à la réécrire complètement. Pratiquement, on déclare des méthodes supplémen
res ou des méthodes portant le même nom que les méthodes de la super-classe. Dans ce d
ernier cas, on masque l'ancienne méthode. Le mot clé super désigne la classe parente e
t on peut alors faire appel à ses méthodes ou variables si celles-ci ont été masquées par
des variables locales de même nom (voir visibilité des variables). Lorsque l'on écrit
super.M(), on fait appel à la méthode M() de la super-classe. En Java, toutes les cl
asses sont dérivées de la classe spéciale Object. C'est la racine de la hiérarchie des c
lasses. Cela veut dire que toutes les classes Java possèdent un certain nombre de
méthodes. La documentation des API disponible chez Sun les explique. Ci-dessous, v
ous trouverez la classe Crayon, et la classe CrayonCouleur qui l'étend.
Crayon
class Crayon { protected int longueur = 100; protected int diametre = 5; // cree
un crayon standard public Crayon() { } // cree un crayon de longueur l et de di
ametre d public Crayon(int l,int d) { longueur = l; diametre = d; } public int q
uelleLongueur() { return longueur; } public int quelDiametre() { return diametre
; } public void changeLongueur(int nouvelleLongueur) { longueur = nouvelleLongue
ur; } } // en millimetres // en millimetres
CrayonCouleur
class CrayonCouleur extends Crayon { protected String couleur = "gris"; public C
rayonCouleur() { super(); } public CrayonCouleur(int l,int d,String c) { super(l
,d); // initialise les variables longueur // et diametre en utilisant le // cons
tructeur de la "superclass" couleur = c; } public CrayonCouleur(String c) { supe
r(); couleur = c; } public String quelleCouleur() { return couleur; } public voi
d changeCouleur(String nouvelleCouleur) { couleur = nouvelleCouleur; }
}
4. Visibilité des variables
En Java, une variable est visible dans le bloc où elle a été déclarée, ainsi que dans tous
les sous-blocs de celui-ci.
Exemples
class scope { float nombre = 5f; int valeur = 12; int methode1() { boolean local
e; // locale visible seulement ici // nombre et valeur sont visibles } int metho
de2() { for (int i;i < n;i++) { // i visible seulement ici // nombre et valeur s
ont visibles } { // debut d'un nouveau bloc int tresLocale; // tresLocale visibl
e ici
}
// nombre et valeur sont visibles } // ni i ni tresLocale ne sont visible // nom
bre et valeur sont visibles
int methode3() { float nombre = 3f; /* la variable nombre declaree au debut de l
a methode n'est plus visible, car elle est masquee par nombre de methode3 */ } i
nt methode4(int valeur) { /* nombre est de nouveau visible ici. Par contre "vale
ur" de la methode est cachee par le parametre valeur */ } }
Prenons methode4 de l'exemple ci-dessus. Il peut tout de même être utile de pouvoir
accéder à la variable globale valeur . Ceci peut être fait en préfixant le nom de la var
iable par this., qui désigne l'objet courant. On pourra ainsi réaliser des opérations
telles que this.valeur = valeur; pour copier le paramètre de méthode4 dans la variab
le d'instance valeur.
5. Surcharge de méthodes
La surcharge (overloading) permet au programmeur de déclarer plusieurs méthodes ou c
onstructeurs ayant le même nom, mais ayant des paramètres différents. Au moment de la
génération de code, le compilateur identifie tous les appels à ces méthodes. Il les comp
are avec les déclarations des méthodes, et choisi la méthode à appeler en fonction de se
s paramètres.
... int somme(int a,int b) { return (a+b); } int somme(int a,int b,int c) { retu
rn (a+b+c); } float somme(float a,float b) { return (a+b); } ... ... int entA =
1,entB = 2,entC = 9; float fA = 1.3,fB = 2.6; int entSomme = somme(entA,entB,ent
C); float fSomme = somme(fA,fB); ...
println est une méthode de la classe PrintStream qui accepte plusieurs types de
paramètres différents, grâce à la surcharge. Le paramètre de println est calculé avant l'ap
el à la méthode. Ainsi, println(1+1) affichera 2 alors que println(1+" "+1) afficher
a 1 1.
Attention !
Il n'est pas possible d'avoir deux méthodes (de même nom) dont tous les paramètres son
t identiques, et dont seul le type de méthode diffère. Le compilateur ne saurait pas
quelle méthode employer. L'exemple ci-dessous n'est pas valide :
int somme(float a,float b) { return ((int)(a+b)); } float somme(float a,float b)
{ return (a+b); }
6. Interfaces
Une interface est un modèle d'implémentation. Tous les objets qui implémentent une int
erface possèdent les méthodes déclarées dans celle-ci. On utilise souvent les interfaces
dans les types de paramètres. En effet, dans certains cas, peu importe la classe
d'un objet donné, car cela ne nous intéresse pas. Nous avons seulement besoin de sav
oir que telle ou telle classe possède ces méthodes et se comporte de telle manière.
On peut imaginer une interface appareilElectrique qui définit les méthodes estEnclen
che() et alimente(boolean). Si une classe implémente l'interface appareilElectriqu
e , on sait donc qu'elle possède au moins ces deux méthodes, qu'il s'agisse d'une ra
dio, d'une lampe ou d'un autre appareil. Lorsque l'on désire indiquer à un appareil
qu'il est alimenté, on exécute app.alimente(true), et pour lui indiquer qu'il a été débran
ché, on exécute app.alimente(false). Nous allons donc créer deux types d'appareils élect
riques, les radios et les lampes. Ces deux types implémentent l'interface appareil
Electrique. Nous allons également créer une classe rallongeElectrique, qui est aussi
un appareil électrique. La méthode la plus intéressante de cette classe est la méthode
branche(appareilElectrique). Celle-ci enregistre l'objet fourni en paramètre comme
étant l'objet alimenté à travers la rallonge. Cette rallonge possède un interrupteur, q
ue l'on peut activer ou désactiver en appelant ses méthodes enclenche(), respectivem
ent declenche(). Lorsque l'on change l'état de l'interrupteur, on indiquera à l'obje
t qui en dépend qu'il est ou qu'il n'est plus alimenté. La page "déclaration d'interfa
ce" peut également intéresser le lecteur.
Interface - appareilElectrique
public interface appareilElectrique { /** teste si l'appareil est enclenche */
public boolean estEnclenche(); /** on appelle cette methode lorsque l'on * branc
he l'appareil dans une source de * courant active, avec true, ou false * si la s
ource est inactive */ public void alimente(boolean alim); }
Implémentation - radio
// c'est une radio rudimentaire, qui se deregle quand on l'eteint ! class radio
implements appareilElectrique { final static int freqMiseEnRoute = 1007; int fre
q; boolean allumee = false; public boolean estEnclenche() { return allumee; } pu
blic void alimente(boolean a) { allumee = a; if (allumee) freq = freqMiseEnRoute
; } /** on ne peut changer de frequence que si la * radio est en marche * retour
ne true si le changement a ete effectue */ public boolean changeFreq(int freq) {
if (!allumee) this.freq = freq; return allumee; } } // 100.7 MHz
Implémentation - lampe
class lampe implements appareilElectrique { boolean allumee = false; public bool
ean estEnclenche() { return allumee; } public void alimente(boolean alim) { allu
mee = alim; } }
Implémentation - rallongeElectrique
class rallongeElectrique implements appareilElectrique { appareilElectrique appa
reilBranche = null; boolean conduit = false; // indique si l'interrupteur est fe
rme (conduit=true) boolean estAlimente = false; public boolean estEnclenche() {
return conduit; } public void alimente(boolean alim) { if (alim) { // on branche
la rallonge dans une prise if (!estAlimente && conduit) on(); } else // on debr
anche if (estAlimente && conduit) off(); estAlimente = alim; } /* gestion de l'i
nterrupteur */ public void enclenche() { if (!conduit && estAlimente) on(); cond
uit = true; } public void declenche() { if (conduit && estAlimente) off(); condu
it = false; } /* gestion de l'appareil branche */ private void on() { if (appare
ilBranche != null) appareilBranche.alimente(true); } private void off() { if (ap
pareilBranche != null) appareilBranche.alimente(false); } // branche un nouvel a
ppareil a la rallonge public boolean branche(appareilElectrique app) { if (appar
eilBranche != null) // il y a deja un appareil branche ! return false; appareilB
ranche = app; return true; }
}
Exemple d'utilisation - testElectrique
class testElectrique { public static void main(String args[]) { rallongeElectriq
ue rallonge1, rallonge2; rallonge1 = new rallongeElectrique(); rallonge2 = new r
allongeElectrique(); radio radioSalon = new radio(); lampe lampeCuisine = new la
mpe(); // on imagine que l'on branche les rallonges dans 2 prises rallonge1.alim
ente(true); rallonge2.alimente(true); // on branche des appareils dans ces 2 ral
longes rallonge1.branche(radioSalon); rallonge2.branche(lampeCuisine); System.ou
t.println("la radio du salon est "+ (radioSalon.estEnclenche() ? "allumee" : "et
einte")); System.out.println("on appuie sur l'interrupteur de la rallonge 1"); r
allonge1.enclenche(); System.out.println("la radio du salon est maintenant "+ (r
adioSalon.estEnclenche() ? "allumee" : "eteinte")); } }
Exécution
la radio du salon est eteinte on appuie sur l'interrupteur de la rallonge 1 la r
adio du salon est maintenant allumee
7. Les Applications Java
Une application Java est un programme qui réside sur la machine qui l'exécute. En Ja
va, tout programme est une classe, et possède donc des méthodes. Lorsque la machine
virtuelle Java veut démarrer le programme, elle appelle la méthode main(String args[
]) de l'application chargée. Il faudra donc placer le code à exécuter dans cette méthode
. Le tableau args contient les éventuels paramètres passés à l'application sur la ligne
de commande. Pour plus d'explication sur la déclaration de la méthode main dans l'ex
emple ci-dessous, veuillez consulter la page Les modificateurs.
Exemple
class exSimple1 { public static void main(String args[]) { int entA = 10; int en
tB = 12; int entX; System.out.println("L'entier A vaut "+entA); System.out.print
ln("L'entier B vaut "+entB); if (entA < entB) System.out.println("A est plus pet
it que B"); else if (entA == entB) System.out.println("A est egal a B"); else Sy
stem.out.println("A est plus grand que B"); System.out.println("comptons de 1 a
"+entA); int somme = 0; int fact = 1; for (int i = 1;i <= entA;i++) { System.out
.print(" "+i); somme += i; fact *= i; } System.out.println(); System.out.println
("la somme de tous les nombres de 1 a "+entA+" vaut "+somme); System.out.println
("la factorielle de "+entA+" vaut "+fact); } }
Exécution
L'entier A vaut 10 L'entier B vaut 12 A est plus petit que B comptons de 1 a 10
1 2 3 4 5 6 7 8 9 10 la somme de tous les nombres de 1 a 10 vaut 55 la factoriel
le de 10 vaut 3628800
8. Les Applets Java
Introduction
Une applet Java est un programme qui est exécuté dans un browser tel que Netscape Na
vigator, ou HotJava de Sun. Une applet est intégrée dans une page au format HTML et
est automatiquement téléchargée sur le poste client. Elle est ensuite exécutée par celui-c
i.
Le téléchargement transparent et l'exécution automatique posent des problèmes de
sécurité, et c'est pour cela que les applets Java sont limitées dans certains domaines
. Typiquement, une applet ne peut pas gérer de fichiers, ni ouvrir des connections
réseau arbitraires.
Intégration de l'applet dans une page Web
Il existe des balises (tags) HTML spécifiques pour indiquer au browser qu'une appl
et est présente dans une page. Il s'agit de <APPLET>. Voici un exemple classique :
<HTML> <HEAD> <TITLE>Exemple de page avec une applet</TITLE> </HEAD> <BODY> <H1>
Exemple d'applet</H1> <applet code="appletExemple.class" width=100 height=50> Vo
tre browser ne supporte pas les applets ! </applet> </BODY> </HTML>
Mise en oeuvre
Tout programme Java est une classe. Une applet n'échappe pas à cette règle. Si l'on ve
ut créer une applet, on doit étendre la classe java.applet.Applet. Cette classe cont
ient les méthodes nécessaires à la gestion de l'applet, et à l'interaction de celle-ci a
vec son environnement (browser).
Voyons les méthodes les plus importantes, que votre applet devra remplacer si nécess
aire :
public void init() Le browser fait appel à cette méthode lorsque l'applet est chargée
(ou rechargée). Cette méthode devra charger les informations telles que images ou so
ns, et récupérer les paramètres présents dans la page HTML. public void start() Après avoi
r été initialisée, l'applet est démarrée, grâce à la méthode start() . L'applet est égaleme
rrée après avoir été stoppée, lorsqu'elle est à nouveau visible. public void stop() Cette m
ode permet à l'applet de s'arrêter lorsqu'elle n'est plus visible, parce que l'utili
sateur a changé de page, par exemple. public void destroy() L'applet est détruite lo
rsque le browser s'arrête, ou avant que l'applet soit rechargée. Cette méthode doit être
remplacée s'il on veut stopper des threads créés par start() de l'applet.
public void paint(Graphics g) Cette méthode est appelée chaque fois que l'on doit re
dessiner l'applet. Le paramètre est de type Graphics et c'est la surface de dessin
sur laquelle on doit travailler.
Exemple
import java.awt.*; import java.applet.*; public class applet1 extends Applet { F
ont font; public void init() { font = new Font("TimesRoman",Font.PLAIN,20); } pu
blic void paint(Graphics g) { g.setFont(font); g.setColor(Color.red); g.drawStri
ng("Je suis une applet !",0,font.getSize()); } }
Cette applet n'implémente pas la méthode start() , car aucune tâche n'est effectuée en p
ermanence. La seule chose que l'applet fait est de se redessiner lorsque le brow
ser le redemande (paint(Graphics g)).
Que se passe-t-il si l'on veut que l'applet ait une activité permanente ? Toutes l
es applets s'exécutant dans browser utilisent la même machine virtuelle Java. On ne
peut donc pas créer une boucle dans la méthode start() pour effectuer une tâche, parce
que l'on occuperait la MV (machine virtuelle). Il faut donc créer un thread afin
d'autoriser l'exécution parallèle des multiples applets.
Exemple
import java.awt.*; public class applet2 extends java.applet.Applet implements Ru
nnable { Thread tache; String lbl; Font font; boolean gris = false;
public void init() { font = new java.awt.Font("TimesRoman", Font.PLAIN, 24); lbl
= getParameter("lbl"); } public void paint(Graphics g) { g.setFont(font); if (g
ris) g.setColor(Color.lightGray); else g.setColor(Color.black); g.drawString(lbl
, 0, font.getSize()); } public void start() { tache = new Thread(this); tache.st
art(); } public void stop() { tache.stop(); } public void run() { while (true) {
try { Thread.sleep(1000); // pause de 1 seconde } catch (InterruptedException e
) {} gris = !gris; repaint(); // on force le réaffichage } } }
La page suivante vous expliquera le fonctionnement des événements.
9. Evénements
Le logiciel de navigation envoie des événements à l'applet par exemple lorsque l'utili
sateur presse une touche du clavier ou utilise sa souris. Il envoie ces événements à l
'applet en appelant une des méthodes prévues à cet effet. On peut détecter ces événements e
remplaçant les méthodes keyDown ou mouseDown.
Cet exemple fait une utilisation très simple des événements qu'il reçoit. S'il s'agit d'
un click de souris, on incrémente le compteur c. Si l'utilisateur a pressé une touch
e flèchée vers le haut ou vers le bas, on augmente ou diminue le compteur. On peut l
e remettre à zéro en pressant 0.
Exemple
import java.awt.*; import java.applet.*; public class applet3 extends Applet { i
nt c = 0; Font font; // valeur du compteur // police de caracteres
public void init() { // on choisit la plus grande police possible : la hauteur d
e l'applet font = new Font("Helvetica",Font.BOLD,size().height); } public void p
aint(Graphics g) { g.setFont(font); g.setColor(Color.blue); g.drawString("c = "+
c,0,font.getSize()); } public boolean mouseDown(Event evt,int x,int y) { c++; re
paint(); return true; } public boolean keyDown(Event evt,int key) { if (key == E
vent.UP) c++; else if (key == Event.DOWN) c--; else if ((char)key == '0') c = 0;
repaint(); return true; } }
Pour plus d'informations sur la structure du système de fenêtrage AWT (Abstract Wind
ow Toolkit) ainsi que sur ses multiples composants, clickez ici (AWT)
10. Le fenêtrage avec AWT
Introduction
La librairie AWT (AWT Toolkit) fournit au programmeur toutes les primitives nécess
aires à la gestion de l'interface graphique utilisateur. Cette librairie utilise l
e système graphique
sous-jacent pour afficher des objets graphiques tels que boutons, fenêtres, etc. M
algré tout, toute applet ou application fonctionnera indépendamment du système d'explo
itation. Pour les premiers exemples graphiques, vous pouvez consulter les pages
sur les applets et sur les événements AWT.
Les différents types d'éléments d'AWT
Il existe principalement quatre types d'objets dans le monde AWT. Les conteneurs
(container) Les conteneurs sont capables de contenir des canevas, des composant
s graphiques, ou d'autres conteneurs. Le panneau, décrit plus loin, est un exemple
typique de conteneur. Les canevas (canvas) Un canevas est un objet graphique très
simple, sur lequel on ne peut que dessiner et afficher des images. On parle aus
si de toile de fond, ou tout simplement de fond. Les composants graphiques (grap
hic components) Les composants graphiques regroupent les objets traditionnels prés
ents dans une interface graphique. On retrouve par exemple les boutons, les list
es, les champs de saisie de texte, et bien d'autres objets encore. Les composant
s de fenêtres (windowing components) Les composants de fenêtres permettent de créer de
s fenêtres ou des boîtes de dialogue. La gestion des menus et de la barre de titre f
ont également partie de ce type d'objets. Généralement, une applet ne fait pas usage d
e ces objets, bien que cela soit possible.
Les panneaux et les gestionnaires de mise en page
Commençons par remarquer qu'une applet est un panneau, et que c'est grâce à cette prop
riété que l'on peut insérer des boutons dans une applet. Cette insertion d'objets se f
ait de manière dynamique, lors de l'exécution. Pour définir l'apparence de l'interface
graphique, on utilise un gestionnaire de mise en page (layout manager). Celui-c
i définit la taille et la position de chaque composant graphique lors de son inser
tion. Il existe plusieurs gestionnaires de mise en page, chacun positionnant dif
féremment les éléments graphiques, selon un algorithme qui leur est propre.
L'exemple ci-dessous montre comment faire bon usage des gestionnaires de mise en
page. Le code source de cette applet étant relativement long, vous pouvez y accéder
sur cette page.
S'il on regarde le code source, on voit que la première opération consiste à diviser l
a surface de l'applet en une grille de 1 ligne de 3 colonnes. On utilise le gest
ionnaire de mise en page par défaut pour la première case, à gauche. La case du milieu
utilise BorderLayout, qui nous permet de placer des objets dans un bord de la p
age, ou au milieu. Pour la troisième case, à droite, nous construisons une grille de
4 lignes de 3 colonnes, que l'on remplit de boutons. Nous avons créé une structure
hiérarchique de panneaux, en les imbriquant.
La gestion de événements et des actions
Lorsque l'utilisateur agit sur un objet graphique tel que bouton ou champ de tex
te, le logiciel de navigation envoie des actions et des événements au panneau conten
ant l'objet. Les actions sont similaires aux événements, sauf qu'ils ne sont pas tra
ités par la méthode handleEvent , mais action. Chaque panneau possède sa propre méthode
action. Si l'on ne remplace pas cette méthode par une méthode spécifique, l'événement (act
ion) se propagera automatiquement au panneau parent, et ainsi de suite.
Dans l'applet ci-dessus, nous traitons tous les événements au niveau de l'applet. Ai
nsi, lorsque l'on presse le bouton "3", par exemple, le browser fait appel à la méth
ode panNumer.action. Comme nous n'avons pas remplacé cette méthode, l'action est pro
pagée au panneau parent, c'est-à-dire l'applet layoutDemo. Lorsque la méthode action e
st appelée, deux paramètres sont fournis. Il s'agit de l'événement, et d'un argument. Le
s événements ont déja été décrits dans la page événements. L'élément evt.target est l'objet
usé l'événement, et l'argument arg est un objet dont le type varie en fonction du type
de l'événement. Si l'événement est produit pas une pression sur un bouton, l'argument s
era une chaîne de caractères contenant le nom du bouton (label). Si l'action est due
à un champ de texte, on sait que l'utilisateur a validé avec la touche entrée, car au
cune action n'est générée lorsque l'on modifie ou que l'on sélectionne le champ. Les val
eurs retournées par les méthodes d'événements (keyDown, mouseUp, etc) et la méthode action
sont de type booléen. Si l'on souhaite propager l'événement au panneau parent, il fau
dra retourner false, autrement on retournera true pour indiquer que l'événement a été tr
aité. Il existe une grande quantité d'objets que nous n'avons pas décrit ici, pour plu
s d'informations sur ce sujet, vous pouvez vous reporter à la spécification des API
sur le serveur Web de Sun, à la page concernant AWT.
11. Processus légers (threads)
Java permet l'exécution concurrente de plusieurs processus légers threads, c'est-à-dir
e plusieurs tâches. On différencie généralement les processus légers des processus lourds.
Les premiers sont internes à une application alors que les processus lourds sont
vus par le système d'exploitation comme plusieurs processus. Les processus légers pa
rtagent un espace d'adressage commun. Comme on le sait, programmation concurrent
e sous-entend
mécanismes de synchronisation et d'exclusion mutuelle.
Le mécanisme d'exclusion mutuelle présent dans l'environnement Java est le moniteur,
que l'on implémente grâce au modificateur synchronized. Le principe de base est qu'
un seul processus ne peut entrer dans un moniteur si celui-ci est occupé. Mais voy
ons tout d'abord comment créer plusieurs processus. Nous pouvons tout simplement éte
ndre la classe java.lang.Thread et remplacer sa méthode run() par notre propre alg
orithme. Lorsque l'on appelle la méthode start(), la méthode run() est déclenchée, et el
le s'arrête lors d'un appel à stop().
Exemple
class afficheur extends Thread { /** constructeur permettant de nommer le proces
sus */ public afficheur(String s) { super(s); } /** affiche son nom puis passe l
e controle au suivant */ public void run() { while (true) { System.out.println("
je suis le processus "+getName()); Thread.yield(); // passe le controle } } } cl
ass thread12 { public static void main(String args[]) { afficheur thread1 = new
afficheur("1"); afficheur thread2 = new afficheur("2"); thread1.start(); thread2
.start(); while (true) { System.out.println("je suis la tache principale !"); Th
read.yield(); }
} }
Exécution
je je je je je je je je suis suis suis suis suis suis suis suis la le le la le l
e la le tache principale ! processus 1 processus 2 tache principale ! processus
1 processus 2 tache principale ! processus 1
je je je je je je je je je je je je je je je je
processus 2 tache principale ! processus 1 processus 2 tache principale ! proces
sus 1 processus 2 tache principale ! processus 1 processus 2 tache principale !
processus 1 processus 2 tache principale ! processus 1 processus 2 Le problème est
que la méthode Thread.stop() est qualifiée de final. On ne peut donc
suis suis suis suis suis suis suis suis suis suis suis suis suis suis suis suis
le la le le la le le la le le la le le la le le
pas la remplacer.
Une autre solution est possible. L'interface Runnable permet de créer un objet, qu
e l'on utilisera ensuite comme constructeur d'un Thread. C'est la méthode start()
de l'objet qui sera responsable de la création et de l'activation du thread.
Exemple
class afficheurRunnable implements Runnable { boolean cont = true; String nomPro
cessus; Thread th; /** constructeur permettant de nommer le processus */ public
afficheurRunnable(String s) { nomProcessus = s; } public void start() { if (th =
= null) { th = new Thread(this,nomProcessus); th.start(); } } public void stop()
{ if (th != null) { th.stop(); th = null; } } /** affiche son nom puis passe le
controle au suivant */ public void run() { while (cont) { System.out.println("j
e suis le processus "+th.getName()); Thread.yield(); // passe le controle } }
} class runnable12 { public static void main(String args[]) { afficheurRunnable
run1 = new afficheurRunnable("1"); afficheurRunnable run2 = new afficheurRunnabl
e("2"); run1.start(); run2.start(); while (true) { System.out.println("je suis l
a tache principale !"); try { Thread.sleep(20); } catch (InterruptedException e)
{} }
} }
Exécution
je je je je je je je je je je je je je je je suis suis suis suis suis suis suis
suis suis suis suis suis suis suis suis la le le le le le le le le la le le le l
e le tache principale ! processus 1 processus 2 processus 1 processus 2 processu
s 1 processus 2 processus 1 processus 2 tache principale ! processus 1 processus
2 processus 1 processus 2 processus 1
Attention !
L'implémentation et le comportement de l'ordonnanceur de processus (scheduler) n'e
st pas spécifié par Sun. Cela veut dire que les différentes machines virtuelles (MV) J
ava n'auront pas forcément le même comportement. Une MV peut se comporter comme un n
oyau temps réel (pas de timeslicing) ou comme un noyau préemptif.
Comme nous l'avons vu dans les exemples précédents, on utilise Thread.yield() afin d
e redonner le contrôle à l'ordonnanceur. De cette manière, quel que soit le comporteme
nt de la machine virtuelle Java, les différents processus s'entrelaceront. Il est ég
alement important de s'assurer que les accès concurrents aux variables et méthodes n
e mettent pas en péril la validité des données. Pour ce faire, veuillez vous reporter à
la page décrivant les mécanismes d'exclusion mutuelle de Java. Pour les mécanismes de
synchronisation inter-processus, veuillez vous référer à la page décrivant les signaux.
12. Exclusion mutuelle
Introduction
Le mécanisme d'exclusion mutuelle présent dans Java est le moniteur. Si l'on désire défi
nir une section critique, afin de s'assurer de la cohérence des données, nous devons
utiliser le mot-clé synchronized . C'est de cette manière que l'on crée un moniteur.
Sécurisation d'une méthode
Lorsque l'on crée une instance d'une certaine classe, on crée également un moniteur qu
i lui est associé. Lorsque l'on applique le modificateur synchronized, on place la
méthode (le bloc de code) dans ce moniteur, ce qui assure l'exclusion mutuelle.
Exemple
class mutexAcc { int accumulateur = 0; public synchronized void stocke(int val)
{ accumulateur += val; } public int lit() { return accumulateur; } }
L'opération d'incrémentation += prend plusieurs instructions, et un changement de co
ntexte entre elles pourrait créer une incohérence des résultats. Le modificateur synch
ronized indique que la méthode stocke fait partie du moniteur de l'instance, empêcha
nt son exécution simultanée par plusieurs processus. Si le moniteur est déja occupé, les
processus suivants seront mis en attente. L'ordre de réveil des processus n'est p
as déterministe.
Sécurisation de blocs de code
L'utilisation de méthodes synchronisées trop longues peut créer une baisse d'efficacité.
Avec Java, il est possible de placer n'importe quel bloc dans un moniteur, ce q
ui permet ainsi de réduire la longueur des sections critiques. Dans l'exemple suiv
ant, le code de methode1 et de methode2 est équivalent.
synchronized void methode1() { // section critique... } void methode2() { synchr
onized(this) { // section critique... } }
Exemple
public class Tortue { private Point pos; private int angle; public Tortue(int x,
int y,int angle) { // ... } public synchronized void tourne(int degres) { angle
+= degres; } public synchronized void avance(int distance) { pos.x += (int) ((do
uble)distance*Math.cos((double)angle)); pos.y += (int) ((double)distance*Math.si
n((double)angle)); } public int angle() { return angle; } public synchronized Po
int pos() { return new Point(pos.x,pos.y); } } public class mutexLogo { public /
/ // // // } int lectureEntier() { ... lecture d'un nombre entier pouvant prendr
e du temps ...
public static void carre(Tortue tortue) { int largeur = lectureEntier(); int lon
gueur = lectureEntier(); synchronized (tortue) { tortue.tourne(90);tortue.avance
(largeur); tortue.tourne(90);tortue.avance(longueur); tortue.tourne(90);tortue.a
vance(largeur); tortue.tourne(90);tortue.avance(longueur); } // autres taches...
} }
Comme nous l'avons vu, quand le modificateur synchronized qualifie une méthode, on
place cette méthode dans le moniteur de l'instance. Cela permet de sécuriser l'accès à
une instance particulière d'un objet. En créant un bloc synchronized (tortue), on en
tre dans le moniteur associé à l'instance tortue. Dans l'exemple ci-dessus, si un pr
ocessus est en train d'afficher un carré en faisant appel à mutexLogo.carre(tortue)
, un autre processus ne pourra pas déplacer la tortue. Malgré tout, pendant que l'on
se trouve dans le
méthode carre et que l'on est en train de lire les dimensions du carré, un autre pro
cessus pourra utiliser la tortue. En effet, le moniteur n'est pas occupé, parce qu
e la méthode n'est pas synchronized, seul un bloc l'est.
Sécurisation des variable de classes
Considérons maintenant le cas où il faut sécuriser l'accès à une variable de classe. La so
lution est simple, il faut créer un moniteur qui est commun à toutes les instances d
e la classe. La méthode getClass() retourne la classe de l'instance dans laquelle
on l'appelle. Nous pouvons maintenant créer un moniteur qui utilise le résultat de g
etClass() comme "verrou".
class mutexStatic { private int accumulateur = 0; private static int acces = 0;
public synchronized void stocke(int val) { accumulateur += val; synchronized (ge
tClass()) { acces += 1; } } public int lit() { synchronized (getClass()) { acces
+= 1; } return accumulateur; } public int acces() { return acces; } }
Cet exemple définit une classe d'accumulateur qui incrémente acces chaque fois que l
'on accède à stocke ou à lit . La variable acces est une variable de classe (déclarée publ
ic), elle est donc partagée par les différentes instances de cette classe. La méthode
getClass() retourne un objet de type Class avec lequel on crée un nouveau moniteur
.
13. Synchronisation : les méthodes wait() et notify()
Il peut être nécessaire de synchroniser des processus qui accèdent aux mêmes ressources.
L'utilisation des moniteurs permet de garantir l'exclusion mutuelle, et pour la
synchronisation, on utilisera des signaux, qui sont modélisés par les méthodes wait()
et notify().
Un des exemples classiques de l'utilisation des signaux est celui des producteur
s-consommateurs. Prenons un tampon borné de n objets, un processus
producteur et un processus consommateur.
Exemple - tamponCirc.java
class tamponCirc { private Object tampon[]; private int taille; private int en,
hors, nMess; /** constructeur. crée un tampon de taille éléments */ public tamponCirc
(int taille) { tampon = new Object[taille]; this.taille = taille; en = 0; hors =
0; nMess = 0; } public synchronized void depose(Object obj) { while (nMess == t
aille) { // si plein try { wait(); // attends non-plein } catch (InterruptedExce
ption e) {} } tampon[en] = obj; nMess++; en = (en + 1) % taille; notify(); // en
voie un signal non-vide } public synchronized Object preleve() { while (nMess ==
0) { // si vide try { wait(); // attends non-vide } catch (InterruptedException
e) {} } Object obj = tampon[hors]; tampon[hors] = null; // supprime la ref a l'
objet nMess--; hors = (hors + 1) % taille; notify(); // envoie un signal non-ple
in return obj; } }
Exemple - utiliseTampon.java
class producteur extends Thread { private tamponCirc tampon; private int val = 0
; public producteur(tamponCirc tampon) { this.tampon = tampon; } public void run
() { while (true) {
System.out.println("je depose "+val); tampon.depose(new Integer(val++)); try { T
hread.sleep((int)(Math.random()*100)); attend jusqu'a 100 ms } catch (Interrupte
dException e) {} } } } class consommateur extends Thread { private tamponCirc ta
mpon; public consommateur(tamponCirc tampon) { this.tampon = tampon; } public vo
id run() { while (true) { System.out.println("je preleve "+((Integer)tampon.prel
eve()).toString()); try { Thread.sleep((int)(Math.random()*200)); attends jusqu'
a 200 ms } catch (InterruptedException e) {} } } } class utiliseTampon { public
static void main(String args[]) { tamponCirc tampon = new tamponCirc(5); product
eur prod = new producteur(tampon); consommateur cons = new consommateur(tampon);
prod.start(); cons.start(); try { Thread.sleep(30000);
//
//
secondes } }
// s'execute pendant 30
} catch (InterruptedException e) {}
Exécution
... je depose 165 je depose 166 je preleve 161 je depose 167 je preleve 162 je d
epose 168 je preleve 163 je depose 169 je preleve 164 je depose 170
je preleve 165 je depose 171 je preleve 166 je preleve 167 ...
Nous déclarons 2 processus, un producteur et un consommateur. Les données sont produ
ites plus vite que le consommateur ne peux les prélever, à cause de la différence de d
urée des délais (aléatoires) introduits dans les 2 processus.
Il existe deux variantes de la méthode wait() qui permettent de spécifier un temps l
imite après lequel le processus sera réveillé, sans avoir à être notifié par un processus c
ncurrent. Il s'agit de wait(long milli), qui attend milli millisecondes, et de w
ait(long milli,int nano) qui attend nano nanosecondes en plus du temps milli. Il
n'est pas possible de savoir si wait() s'est terminé à cause d'un appel à notify() pa
r un autre processus, ou de l'épuisement du temps. La méthode notifyAll() réveille tou
s les processus, et dès que le moniteur sera libre, ils se réveilleront tour à tour.
Attention !
Le noyau Java ne fait aucune garantie concernant l'élection des processus lors d'u
n appel à notify(). En particulier, il ne garantit pas que les processus seront débl
oqués dans l'ordre ou ils ont été bloqués. C'est à cause de cela que l'on a placé wait() da
s une boucle dans l'exemple précédent, car un consommateur pourrait réveiller un autre
consommateur alors que le tampon est vide.
Remarquons aussi que les méthodes wait, notify() et notifyAll() ne peuvent être appe
lées que dans des méthodes synchronisées (synchronized).
14. Les Exceptions
Une exception survient lors d'un cas de figure anormal. Elle interrompt le flot
normal d'exécution et exécute les éventuelles routines de traitement. Par exemple, s'i
l on essaye d'ouvrir en lecture un fichier qui n'existe pas, une exception IOExc
eption sera générée.
En Java, il existe une classe Exception. Si l'on désire créer un nouveau "type" d'ex
ception, il suffit d'étendre la classe, comme présenté ci-dessous :
class divisionParZeroException extends Exception { } Remarquons que Exception et
Error sont des sous-classes de Throwable . Les objets de
la classe Error sont des erreurs internes à la machine virtuelle Java, c'est pourq
uoi il faut être très vigilant si l'on désire les récupérer. En effet, il ne serait pas trè
judicieux d'ignorer une exception de type "Plus de mémoire".
Si dans un bloc (une méthode), on fait appel à une méthode qui peut potentiellement génére
r une exception, on doit soit essayer de la récupérer avec try, soit ajouter le mot
clé throws dans la déclaration du bloc. Si on ne le fait pas, il en résultera une erre
ur à la
compilation. Il n'est toutefois pas requis de traiter les exceptions et erreurs
déclarées dans le paquetage java.lang, car celles-ci peuvent se déclencher partout. Po
ur générer une exception, il suffit d'utiliser throw, suivi d'un objet dont la class
e est dérivée de Throwable. Si l'on veut générer une exception dans une méthode, avec thro
w, il faudra l'indiquer dans la déclaration de méthode, en utilisant le mot clé throws
.
throw new Exception(); throw new IOException("fichier ABSENT.TXT non trouve"); t
hrow e; // si on ne fait que propager l'exception e
Un exemple complet de l'utilisation des exceptions est donné à la page décrivant la cl
ause try.
Attention !
Attention à l'orthographe, lorsque l'on indique qu'une méthode peut générer une exceptio
n, dans la déclaration de la méthode, on utilise le mot clé throws , qui prend un s. L
orsque l'on génére l'exception, on utilise throw, qui ne prend pas de s.
15. Type Wrappers
A chaque type standard est associée une classe que l'on appelle type wrapper. Il e
xiste les classes Boolean, Integer, Long, Float, Double, Character. Ces classes
peuvent stocker une valeur du type correspondant, mais surtout, elles offrent de
s méthodes facilitant les tâches de conversion. Grâce à ces classes, on peut par exemple
effectuer la conversion d'une chaîne de caractères en nombre entier. Ces méthodes de
conversion opèrent sur des instances, mais il est également possible d'utiliser des
méthodes statiques.
Exemples
Float objetPi = new Float("3.1415"); float r = 3.5f; float perimetre = 2 * objet
Pi.floatValue() * r; int valeur = Integer.valueOf("555").intValue(); String peri
metreStr = Float.toString(perimetre);
Attention !
Ces classes ne sont pas interchangeables avec les types d'origine, car il s'agit
d'objets. S'il on désire obtenir la valeur contenue dans un objet de ce type, on
utilisera la méthode typeValue() où type est le nom du type standard.
16. Incrémentation et
décrémentation
Il existe deux opérateurs facilitant l'incrémentation et la décrémentation. Il s'agit re
spectivement de ++ et --. Ils peuvent tous deux être utilisés en configuration postf
ixe, ou préfixe. C'est-à-dire avant l'identificateur, ou après celui-ci. Si l'on place
l'opérateur avant l' identificateur, on effectuera l'incrémentation avant l'exécution
de la ligne en cours. La ligne System.out.println(x++); est équivalente à System.ou
t.println(x); x = x+1; . De même, System.out.println(++x); équivaut à x = x+1;System.o
ut.println(x);.
Exemple
class incrementation { public static void main(String args[]) { int x = 0; int y
= 0; x++; ++y; // postfixe // prefixe
System.out.println("x == "+x); System.out.println("y == "+y); System.out.println
("x++ == "+ x++); System.out.println("++y == "+ ++y); System.out.println("x == "
+x); System.out.println("y == "+y); x = x++; y = --y; // inutile car equivalent
a "x = x; x = x+1;" // inutile car equivalent a "y = y-1; y = y;"
System.out.println("x == "+x); System.out.println("y == "+y); x = y++; // equiva
lent a "x = y; y = y+1;"
System.out.println("x == "+x); System.out.println("y == "+y); } }
Exécution
x == 1 y == 1 x++ == 1 ++y == 2 x == 2 y == 2 x == 2 y == 1
x == 1 y == 2
Notons que l'incrémentation et la décrémentation sont également possibles sur des variab
le à virgule flottante. Cela a pour effet de leur ajouter, respectivement soustrai
re, la quantité 1.0.
17. Les fichiers
En Java, on peut facilement accéder à des fichiers à partir d'une application. On ne p
eut pas le faire à partir d'une applet pour des questions de sécurité. On remarquera éga
lement qu'à tout accès au système de fichiers, il peut se produire une exception java.
io.IOException.
L'exemple ci-dessous montre comment obtenir des informations sur un fichier. Not
ons qu'il n'existe pas de classe spéciale pour traiter des répertoires, ceux-ci sont
considérés comme des fichiers.
Informations sur un fichier
import java.io.*; class fileInfo { public static void main (String args[]) throw
s java.io.IOException { System.out.println("Entrez le repertoire ou se trouve le
fichier : "); char ch; // directory StringBuffer dirBuf = new StringBuffer(); w
hile ((ch = (char)System.in.read()) != '\n') dirBuf.append(ch); File dir = new F
ile(dirBuf.toString()); // file System.out.println("Entrez le nom du fichier : "
); StringBuffer fileBuf = new StringBuffer(); while ((ch = (char)System.in.read(
)) != '\n') fileBuf.append(ch); File fil = new File(dir,fileBuf.toString()); if
(fil.exists()) { System.out.println("Fichier trouve"); System.out.println("Nom d
e fichier : "+fil.getName()); System.out.println("Chemin du fichier : "+fil.getP
ath()); System.out.println("Chemin absolu : "+fil.getAbsolutePath()); System.out
.println("Droit de lecture : "+fil.canRead()); System.out.println("Droit d'ecrit
ure : "+fil.canWrite()); System.out.println("\nContenu du repertoire :"); String
listing[] = dir.list(); for (int i = 0;i < listing.length;i++) System.out.print
ln(listing[i]);
}
}
} else { System.out.println("Fichier absent"); }
La lecture et l'écriture se fait en utilisant les différentes classes de traitement
des flux (streams). L'exemple ci-dessous copie un fichier source dans un fichier
destination, après avoir effectué un certain nombre de tests pour vérifier que l'opérat
ion de copie est possible. Les classes utilisées sont FileInputStream et FileOutpu
tStream du paquetage java.io. Ces deux classes étendent java.io.InputStream.
Copieur de fichiers
import java.io.*; class fileCopy { public static void main (String args[]) throw
s java.io.IOException { char ch; // repertoire source System.out.println("repert
oire source : "); StringBuffer dirBuf = new StringBuffer(); while ((ch = (char)S
ystem.in.read()) != '\n') dirBuf.append(ch); File srcDir = new File(dirBuf.toStr
ing()); if (!srcDir.exists()) { System.out.println("repertoire absent"); System.
exit(1); } else if (!srcDir.canRead()) { System.out.println("repertoire illisibl
e"); System.exit(1); } // fichier source System.out.println("fichier source : ")
; StringBuffer fileBuf = new StringBuffer(); while ((ch = (char)System.in.read()
) != '\n') fileBuf.append(ch); File srcFile = new File(srcDir,fileBuf.toString()
); if (!srcFile.exists()) { System.out.println("fichier absent"); System.exit(1)
; } else if (!srcFile.canRead()) { System.out.println("fichier illisible"); Syst
em.exit(1); } // repertoire destination System.out.println("repertoire destinati
on : "); dirBuf = new StringBuffer(); while ((ch = (char)System.in.read()) != '\
n') dirBuf.append(ch); File dstDir = new File(dirBuf.toString());
if (!dstDir.exists()) { System.out.println("repertoire absent"); System.exit(1);
} else if (!dstDir.canWrite()) { System.out.println("repertoire protege en ecri
ture"); System.exit(1); } // fichier destination System.out.println("fichier des
tination : "); fileBuf = new StringBuffer(); while ((ch = (char)System.in.read()
) != '\n') fileBuf.append(ch); File dstFile = new File(dstDir,fileBuf.toString()
); if (dstFile.exists()) { System.out.println("fichier existant"); System.exit(1
); } // copie du fichier FileInputStream inStream = new FileInputStream(srcFile)
; FileOutputStream outStream = new FileOutputStream(dstFile); // tant que ce n'e
st pas la fin du fichier while (inStream.available() > 0) outStream.write(inStre
am.read()); // fermeture des fichiers inStream.close(); outStream.close();
}
}
18. Les connexions réseau TCP/IP la classe Socket
Avec Java, il est très facile d'ouvrir une connexion TCP/IP depuis une applet ou u
ne application. La classe Socket fournit tous les outils nécessaires à cela. Une foi
s qu'une connexion est réalisée avec une autre machine, on a accès à un flux d'entrée et u
n flux de sortie, comme si l'on travaillait avec un fichier.
Exemple
import java.io.*; import java.net.*; public class jnetcat { public static void m
ain(String args[]) { if (args.length != 2) System.out.println("usage : jnetcat h
ote port"); else {
Socket sk = null; try { sk = new Socket(args[0],Integer.valueOf(args[1]).intValu
e()); DataInputStream is = new DataInputStream(sk.getInputStream()); String lign
e; while ((ligne = is.readLine()) != null) System.out.println(ligne); } catch (U
nknownHostException e) { System.out.println("hote inconnu : "+e.getMessage()); }
catch (IOException e) { System.out.println("erreur entree/sortie : "+e.getMessa
ge()); } } } }
Cette application se connecte sur la machine et le port spécifiés sur la ligne de co
mmande. Elle lit ensuite toutes les données, et les affiche à l'écran. On peut par exe
mple exécuter :
java jnetcat eig6.unige.ch 13
Exécution - port 13
Thu May 30 11:23:19 1996
Le port 13 donne l'heure, on parle du généralement du port daytime.
Exécution - port 19
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abc defg !"#$%&'
()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcd efgh "#$%&'()*+,-.
/0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcde fghi #$%&'()*+,-./012345
6789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdef ghij $%&'()*+,-./0123456789:;<
=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefg hijk %&'()*+,-./0123456789:;<=>?@ABC
DEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefgh ijkl &'()*+,-./0123456789:;<=>?@ABCDEFGHIJ
KLMNOPQRSTUVWXYZ[\]^_`abcdefghi jklm '()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQ
RSTUVWXYZ[\]^_`abcdefghij klmn
Le port 19 est le port générateur de caractères, chargen, il envoie des lignes de cara
ctères tant que le client est connecté. Pour arrêter le client, on doit presser contrôle
-C .
19. Les serveurs TCP/IP -
SocketServer
Nous allons voir comment réaliser une application serveur TCP/IP. La classe Socket
Server permet de se mettre en attente d'un client sur un port. La méthode SocketSe
rver.accept() retourne un objet de la classe Socket lorsqu'un client veut se con
necter. C'est grâce à cet objet que l'on pourra établir une connexion avec le client.
Serveur d'heure
import java.net.*; import java.io.*; import java.util.Date; class serveurHeure {
public static void main(String args[]) { try { ServerSocket sks = new ServerSoc
ket(11111); Socket sk = sks.accept(); System.out.println(sk.toString()); DataOut
putStream out = new DataOutputStream(sk.getOutputStream()); out.writeBytes("Bien
venue "+sk.getInetAddress().getHostName()+ ". Il est actuellement "+new Date()+"
\n"); sk.close(); sks.close(); } catch (IOException e) { System.out.println(e.ge
tMessage()); } } }
Le résultat que l'on peut voir apparaitre sur la console où l'on a lancé le serveur es
t :
Socket[addr=129.194.184.2,port=2569,localport=11111] Pour se connecter à ce serveu
r, on peut utiliser telnet ou l'utilitaire jnetcat qui a été
décrit dans les pages décrivant la classe Socket. Le client qui se connecte sur le p
ort 11111 du serveur peut voir apparaitre un message du type :
Bienvenue eig. Il est actuellement Thu May 30 14:12:35 MET DST 1996
Généralement, on désire pouvoir servir plusieurs applications clientes à partir du même se
rveur, comme c'est le cas pour FTP et HTTP. Nous allons voir comment implémenter u
n serveur multi-thread, qui crée un processus par client. Nous déclarons une classe
connexion qui est capable de traiter les entrées/sorties d'un client que nous inst
ancions chaque fois qu'un client se connecte sur le port serveur. Le programme c
i-dessous est un exemple de serveur qui peut s'accomoder de plusieurs clients co
ncurrement. Lorsqu'un client s'y connecte, le serveur initialise un compteur à 0,
et chaque nombre que le client lui envoie est additionné à ce compteur.
Serveur multi-clients
/* * Serveur multi-clients * Additionne tous les nombres fournis par les clients
*/ import java.io.*; import java.net.*; public class serveurAdd { static int po
rt; public static void main(String args[]) { if (args.length != 1) System.out.pr
intln("usage : serveurAdd port"); else { ServerSocket serveur; try { port = Inte
ger.parseInt(args[0]); } catch (NumberFormatException e) { System.out.println("l
e port doit etre une valeur entre 1024 et 65535"); System.exit(1); } try { serve
ur = new ServerSocket(port); for (;;) new connexion(serveur.accept()); } catch (
IOException e) { System.out.println("erreur a la creation d'un objet Socket : "+
e.getMessage()); System.exit(1); } } } } class connexion implements Runnable { S
ocket client; DataInputStream depuisClient; PrintStream versClient; int somme =
0; public connexion(Socket client) { this.client = client; try { // creation des
flux de/vers le client depuisClient = new DataInputStream(client.getInputStream
()); versClient = new PrintStream(client.getOutputStream()); // message d'accuei
l versClient.println("Bienvenue sur le serveur additionneur"); versClient.printl
n("Entrez un nombre suivi de entree");
}
} catch (IOException e) { try { client.close(); } catch (IOException ee) {} } ne
w Thread(this).start();
public void run() { try { String lue; while ((lue = depuisClient.readLine()) !=
null) { somme += Integer.parseInt(lue); versClient.println("merci. La somme est
maintenant "+somme); } versClient.println("Au revoir !"); } catch (IOException e
) { System.out.println("Exception entree/sortie : "+e.getMessage()); } catch (Nu
mberFormatException e) { versClient.println("Seul les nombres sont autorises. Au
revoir !"); } stop(); } public void stop() { try { client.close(); } catch (IOE
xception e) { System.out.println("exception a la fermeture d'une connexion : "+e
); } } }
20. Une Application client-serveur
Il est conseillé de lire les pages concernant les classes Socket et SocketServer a
vant de poursuivre la lecture de cette page.
Nous allons créer un système qui permet à des applets d'afficher des messages sur un s
ystème serveur. Lorsque le logiciel de navigation lance l'exécution de l'applet cli,
celle-ci se connecte sur le serveur pour obtenir une liste de noms. Cette liste
est traitée est insérée dans une objet graphique List (voir awt). Lorsque l'utilisate
ur presse le bouton "envoi", l'applet transmet le message au serveur, dans le fo
rmat :
send:nom:message
Lorsque le serveur recoit un message comme celui-ci, il l'affiche sur la console
. La liste de noms que le serveur envoie à l'applet est contenue dans un fichier q
ui est lu à l'initialisation.
Le client - cli.java
import import import import import java.awt.*; java.applet.*; java.io.*; java.ne
t.*; java.util.StringTokenizer;
public class cli extends Applet implements Runnable { final static int serverPor
t = 11115; Thread kicker; List beepList; TextField messageArea; Button sendButto
n; String serverHost; Socket sk; DataInputStream fromServer; PrintStream toServe
r; public void init() { add(new Label("Client")); beepList = new List(5,false);
add(beepList); add(new Label("Entrez votre message : ")); messageArea = new Text
Field(30); add(messageArea); sendButton = new Button("Envoi"); add(sendButton);
serverHost = getCodeBase().getHost(); } public void start() { if (kicker == null
) { kicker = new Thread(this); kicker.start(); } } public void stop() { if (kick
er != null) { kicker.stop();
}
}
kicker = null;
public void run() { try { connect(); String line; while ((line = fromServer.read
Line()) != null) { StringTokenizer st = new StringTokenizer(line,":",false); Str
ing token = st.nextToken(); if (token.equals("beep")) { beepList.addItem(st.next
Token()); } else if (token.equals("message")) { System.out.println("message du s
erveur : "+st.nextToken()); } else if (token.equals("hello")) { showStatus("conn
ected to server"); toServer.println("list"); } else if (token.equals("end")) { s
howStatus("Entrez votre message et pressez Envoi"); } else { toServer.println("m
essage:Unknown command"); System.out.println("message inconnu du serveur : "+lin
e); } } } catch (IOException e) { showStatus("Ne peut pas se connecter au serveu
r"); } finally { disconnect(); } } public boolean action(Event evt,Object arg) {
if (evt.target instanceof Button) { if (evt.target == sendButton) try { sendPag
e(); } catch (IOException e) { System.out.println("IO : "+e.getMessage()); } } e
lse return false; return true; } public void sendPage() throws IOException { if
(beepList.getSelectedItem() == null) { showStatus("Vous devez selectionner un de
stinataire"); return; } if (messageArea.getText().equals("")) { showStatus("Vous
devez entrer un message"); return; } System.out.println("Envoi d'un message a "
+beepList.getSelectedItem()+" !");
toServer.println("send:"+beepList.getSelectedItem()+":"+messageArea. getText());
} public void connect() throws IOException { sk = new Socket(serverHost,serverP
ort); fromServer = new DataInputStream(sk.getInputStream()); toServer = new Prin
tStream(new DataOutputStream(sk.getOutputStream())); } public void disconnect()
{ try { sk.close(); } catch (IOException e) { } } }
Le serveur - ser.java
/* * Serveur multi-clients */ import java.io.*; import java.net.*; import java.u
til.*; public class ser { static int port; */ /** cree une Hashtable avec le nom
des personnes atteignables
public static Hashtable buildDataBase(String filename) { Hashtable db = new Hash
table(); try { DataInputStream fileInput = new DataInputStream(new FileInputStre
am(filename)); String line; while ((line = fileInput.readLine()) != null) { Stri
ngTokenizer st = new StringTokenizer(line,":",false); db.put(st.nextToken(),st.n
extToken()); } } catch (NoSuchElementException e) { System.out.println("Erreur d
e format dans la base de donnees : "+e.getMessage()); System.exit(1); } catch (I
OException e) { System.out.println("Erreur lors de la lecture de la base de donn
ees : "+e.getMessage()); System.exit(1); } return db; } public static void main(
String args[]) { if (args.length != 2)
System.out.println("usage : ser bdonnee port"); else { ServerSocket server; try
{ port = Integer.parseInt(args[1]); } catch (NumberFormatException e) { System.o
ut.println("le port doit etre une valeur entre 1024 et 65535"); System.exit(1);
} try { server = new ServerSocket(port); Hashtable dataBase = buildDataBase(args
[0]); for (;;) new connection(server.accept(),dataBase); } catch (IOException e)
{ System.out.println("erreur a la creation d'un objet Socket : "+e.getMessage()
); System.exit(1); } } } } class connection implements Runnable { Hashtable db;
Socket client; DataInputStream fromClient; PrintStream toClient; int somme = 0;
public connection(Socket client,Hashtable db) { this.client = client; this.db =
db; try { // creation des flux de/to le client fromClient = new DataInputStream(
client.getInputStream()); toClient = new PrintStream(client.getOutputStream());
System.out.println("Nouvelle connexion au serveur : "+client); } catch (IOExcept
ion e) { try { client.close(); } catch (IOException ee) {} } new Thread(this).st
art(); } public void run() { try { toClient.println("hello"); String line; while
((line = fromClient.readLine()) != null) { StringTokenizer st = new StringToken
izer(line,":",false); String token = st.nextToken(); if (token.equals("list")) s
endList(); else if (token.equals("send")) { sendPage(st.nextToken(),st.nextToken
()); toClient.println("message:Le serveur vous dit
merci !");
} else if (token.equals("message")) System.out.println("message : "+st.nextToken
()); else if (token.equals("exit") || token.equals("quit")) break; else toClient
.println("message:Unknown command"); } toClient.println("message:bye !"); } catc
h (IOException e) { System.out.println("Exception entree/sortie : "+e.getMessage
()); } stop(); } public void stop() { try { client.close(); } catch (IOException
e) { System.out.println("exception a la fermeture d'une connection : "+e); } }
/** envoie la liste de noms au client */ public void sendList() { for (Enumerati
on e = db.keys();e.hasMoreElements();) { toClient.println("beep:"+(String)e.next
Element()); } toClient.println("end"); } /** cette methode est appelee lorsqu'un
client demande l'envoi d'un message */ public void sendPage(String name,String
msg) { System.out.println("Requete d'envoi du message \""+msg+"\" a "+name); } }

Vous aimerez peut-être aussi