Maîtrise Informatique | Programmation Répartie et Architecture 3 Tiers |
TD-TP - no 3 : Chat Server avec RMI |
|
Denis Caromel, Christian Delbé | |
Université Nice-Sophia Antipolis, Département Informatique |
Le but de ce TD est d'apprendre à
écrire des interfaces remote
pour rendre des objets
existants accessibles à distance, d'utiliser les fonctions avancées
de RMI et de comprendre les problèmes de déploiement d'une
application distribuée. Il est vivement conseillé de faire un
schéma indiquant les relations entre les différents morceaux de l'application a l'éxécution.
Nous allons partir d'une application simple de dialogue en direct (chat en Anglais). Cette application est composée de trois packages. Un package serveur qui comprend le serveur de chat et un package client qui comprend un client capable de se connecter au serveur et d'envoyer comme de recevoir des messages. Un troisième package contient les interfaces remote communes. Un quatrième package, qui n'est pas directement lié à l'application sera utilisé pour permettre le téléchargement de code avec RMI.
Récupérer l'archive td03.zip et décompacter la dans un répertoire de
votre choix en utilisant la commande unzip
. Vous obtenez
un répertoire td03
qui contient, dans le répertoire src, les sous-répertoires :
Nom du répertoire | contenu |
---|---|
chat/client/
|
Le package client de l'application de chat |
chat/server/
|
Le package serveur de l'application de chat |
chat/remote/
|
Le package des interfaces remote de l'application de chat |
classServer/
|
Le package de code downloading qui implémente un serveur HTTP capable de répondre à des GET
|
Le principe est le suivant : le serveur de chat est une classe bien définie ServerImpl
qui implémente une interface Server
qui offre un ensemble de services. Cette classe n'est pas accessible à distance. Pour rendre le serveur accessible par des client distribués, on définie un objet Remote (RemoteServerImpl
) qui expose l'interface RemoteServer
. RemoteServerImpl
instancie un ServerImpl
et l'utilise directement. Une fois serveur RemoteServerImpl
lancé sur une machine (host) donnée, il s'enregistre dans le RMIRegistry
de cette machine. Le client est aussi un objet Remote
car il a besoin d'être contacté par le serveur. Le client expose l'interface RemoteClient
. Le client prend en paramètre de lancement l'URL du serveur auquel il doit se connecter. Une fois lancé, il fait un lookup du serveur et obtient une référence distante sur ce serveur (un Stub
) qui implémente l'interface RemoteServer
. Cette interface définie une méthode logon qui permet au client de se faire connaître du serveur.
public RemoteConnection logon(String name, RemoteClient c) throws java.rmi.RemoteException;
En regardant cette méthode de plus près on voit qu'elle prend en paramètre le nom du client et une référence Remote
pour les call-back. Elle retourne une référence Remote
sur une connexion qui permettra au client de parler au serveur. Cette interface Remote
est RemoteConnection
. Elle définie deux méthodes, l'une pour envoyer un message au serveur, l'autre pour permettre au client de se déconnecter.
Si l'explication ci-dessus n'est pas claire n'hésitez pas à poser des questions.
Liste des fichiers et leur utilisation
Nom du fichier | rôle | |
---|---|---|
Interfaces Communes | ||
RemoteClient.java
|
Interface remote du client (pour les call-back du serveur). |
|
RemoteServer.java
|
Interface remote du serveur que les clients récupèrent dans le RMIRegistry et qui permet de faire le logon
|
|
RemoteConnection.java
|
Interface remote que le serveur retourne au client en résultat du logon et qui permet au client de communiquer avec le serveur. L'interface fournie est vide son écriture est l'objet d'une question ci-dessous. | |
Server | ||
RemoteServerImpl.java
|
Implémentation de l'interface RemoteServer basé sur un UnicastRemoteObject
La méthode de logon est vide. Son implémentation est l'objet d'une question ci-dessous.
|
|
RemoteConnectionImpl.java
|
Implémentation de l'interface RemoteConnection basé sur un UnicastRemoteObject .
La classe fournie n'a que le constructeur. L'implémentation de l'interface RemoteConnection est l'objet d'une question ci-dessous.
|
|
Server.java
|
Interface offrant des fonctionnalités d'un serveur de chat. Cette interface n'est pas remote et représente une vue locale du serveur. | |
ServerImpl.java
|
Implémentation de l'interface Server . ServerImpl est le code réel du serveur qui gère les clients. Il est utilise par la class RemoteServerImpl.java
|
|
Client | ||
ClientImpl.java
|
Une implémentation de l'interface RemoteClient qui lit les messages sur System.in et affiche sur System.out
|
Pour voir que tout fonctionne essayez de compiler les 3 packages puis utilisez rmic
pour générer les Stub
des 3 objets remote. Notez qu'il est encore impossible de tester le serveur avant d'écrire le code manquant (question suivante).
Il est important de comprendre que pour compiler, le package chat.server n'a besoin QUE des interfaces communes et des classes serveurs. De la même manière, le package chat.client n'a besoin QUE des interfaces communes et de la classe client. Au fonctionnement, le serveur aura besoin des Stub
sur le client et le client aura besoin des Stub
sur le serveur et la connexion. Ces classes peuvent être téléchargées automatiquement par le runtime RMI comme nous le verrons plus tard.
Chat Client
cd ~/td03 export CLASSPATH=classes javac -d classes src/chat/remote/*.java javac -d classes src/chat/client/*.java rmic -d classes chat.client.ClientImpl
Chat Server
cd ~/td03 export CLASSPATH=classes javac -d classes src/chat/remote/*.java javac -d classes src/chat/server/*.java rmic -d classes chat.server.RemoteServerImpl rmic -d classes chat.server.RemoteConnectionImpl
Class Server (notez que le serveur de classes ne sera utilisé que plus tard)
cd ~/td03 export CLASSPATH=classes javac -d classes src/classserver/*.java
Comme explique ci-dessus, l'interface de connexion RemoteConnection
qui permet au client de communiquer avec le serveur est vide. Le but de cette partie est d'écrire cette interface, d'écrire son implémentation et de modifier le code du client et du serveur pour l'utiliser.
Q |
|
---|
Recompiler le package client et serveur pour prendre en compte les changements effectués. Ne pas oublier d'utiliser rmic
sur RemoteConnectionImpl
car l'interface remote qu'il implémente a changé.
Pour chaque commande décrite ci-dessous il faut que la variable CLASSPATH
pointe sur les classes du client et du serveur. Pour cela on utilisera :
export CLASSPATH=classes
rmiregistryLancement du serveur
cd ~/td03 java -Djava.security.policy=java.policy \ chat.server.RemoteServerImpl MonNomServerLancement d'un client
cd ~/td03 java -Djava.security.policy=java.policy \ chat.client.ClientImpl rmi://machine.unice.fr/MonNomServer Zorglub
Afin de mettre en évidence les problèmes de déploiement
d'une application distribuée, le serveur et le client ne doivent
maintenant pas partager le même classpath
(cas le
plus courant dans la réalité). La conséquence est qu'il faut
permettre a RMI de télécharger les classes dynamiquement. Pour cela
nous devons lancer deux serveurs de classes qui sont de très simple
HTTP serveurs capable de lire des fichiers classes depuis un
répertoire donné. Suivez les étapes ci-dessous pour lancer
l'application.
Q |
Observer ce qui se passe dans chaque fenêtre et expliquez. En particulier observez les deux fenêtres des ClassServer et expliquez qui télécharge quoi.
|
---|
Q |
Passer un peu de temps à regarder le code du serveur. Essayer de bien comprendre la chaîne de responsabilité qui s'établit entre les classes RemoteServerImpl , RemoteConnectionImpl et ServerImpl . Combien d'instances de chaque classe sont présentes à un instant donné dans la JVM du serveur.
|
---|
Créez 2 répertoires (serverSide et clientSide). Déplacez dans le premier les fichiers .class nécessaires au serveur, et dans le second les fichiers .class nécessaires au client.
Dans un premier Shell assurez-vous que le classpath ne contient rien puis lancez le rmiregistry :
export CLASSPATH= rmiregistry
Dans un deuxième Shell lancez le ClassServer
en le pointant vers les classes du serveur. Le numéro de port choisi est arbitraire mais il doit correspondre à celui passé en paramètre au Server
.
cd ~/td03 export CLASSPATH=classes java classserver.ClassFileServer 3000 serverSide
Dans un troisième Shell lancez le ClassServer
en le pointant vers les classes du client. Le numéro de port choisi est arbitraire mais il doit correspondre à celui passé en paramètre au ChatClient
cd ~/td03 export CLASSPATH=classes java classserver.ClassFileServer 4000 clientSide
Dans un quatrième Shell lancez le Server
en lui indiquant quel fichier de police utiliser pour la sécurité et quel est le codebase
, c'est a dire quel est l'URL du serveur HTTP capable de servir les classes nécessaire pour utiliser ce serveur. C'est le serveur que nous avons lance sur le port 3000. En paramètre il faut mettre le nom sous lequel le serveur va s'enregistrer dans le RMIRegistry
.
cd ~/td03/serverSide export CLASSPATH=. java -Djava.security.policy=../java.policy \ -Djava.rmi.server.codebase=http://machine.unice.fr:3000/ \ chat.server.RemoteServerImpl MonNomServer
Dans un cinquième Shell lancez le ChatClient
en lui indiquant quel fichier de police utiliser pour la sécurité et quel est le codebase
, c'est a dire quel est l'URL du serveur HTTP capable de servir les classes nécessaire pour utiliser la partie remote de ce client. C'est le serveur que nous avons lance sur le port 4000. En paramètre il faut mettre l'URL du chatServer
et le nom de logon du client.
cd ~/td03/clientSide export CLASSPATH=. java -Djava.security.policy=../java.policy \ -Djava.rmi.server.codebase=http://machine.unice.fr:4000/ \ chat.client.ClientImpl rmi://machine.unice.fr/MonNomServer Zorglub
Lancez un autre client et essayez de communiquer en tapant des messages sur la console.