École Nationale Supérieure des Sciences Appliquées et de Technologie Algo. Distribuée Thread, Socket et RMI 1 / 47 Avant-propos Découpage du module : Une partie théorique Une partie techno. : Thread, socket + RMI Organisation 6h = CM/TP présentation des notions 10h = projet 2 / 47 Première partie Threads 3 / 47 Généralités Utilité des Threads : voir cours de prog. syst. en IMR1 Threads en Java Les méthodes/variables de classes sont connues de chacun des Threads (partage d’information) L’implémentation d’un Thread doit : hériter de la classe Thread ou implémenter l’interface Runnable définir le contenu de la méthode public void run() 4 / 47 Lancement d’un Thread ATTENTION ! Un Thread ne sera PAS exécuté lors de l’appel de la méthode run() Un Thread sera exécuté uniquement après l’appel de la méthode start() (implémentée dans la classe Thread) 5 / 47 Exemple de Threads class ThreadDormeur extends Thread { public ThreadDormeur() { super(); } public void run(){ System.out.println("Dans ThreadDormeur"); } } public class HelloThread { public static void main(String arg[]) { ThreadDormeur dormeur = new ThreadDormeur(); dormeur. start() ; System.out.println("Dans HelloThread"); } } 6 / 47 Exécution $ javac HelloThread.java ThreadDormeur.java $ java -cp . HelloThread Dans HelloThread Dans ThreadDormeur $ 7 / 47 Implémentation de Runnable Une classe de Thread peut également se contenter d’implémenter l’interface Runnable Il faut alors utiliser le constructeur de la classe Thread class RunnableThread implements Runnable { RunnableThread() { ... } public void run() { ... } } RunnableThread p = new RunnableThread(); Thread t = new Thread(p) ; t.start() ; 8 / 47 Contrôle de l’exécution Les outils de contrôle de l’exécution des Threads sont sleep(int n_ms) : le Thread est bloqué pendant n_ms millisecondes isAlive() : indique par un booléen si le Thread est vivant ou non (démarré par start et run non fini) getPriority() et setPriority(int prio) : indication sur la priorité du Thread join() : permet d’attendre la fin de l’exécution du Thread wait(), notify() et notifyAll() : permet de mettre en attente un Thread ou de réveiller un Thread en attente 9 / 47 Exemple : un métronome Les méthodes permettant les rendez-vous doivent se faire sur un objet partagé par les Threads class Alarme { public void reveille() { notify(); } public void dors(){ try{ wait(); } catch(Exception e){} } } 10 / 47 class ThreadDormeur extends Thread { private Alarme alarme; private int sec; public ThreadDormeur(Alarme alarme, int N) { this.alarme = alarme; this.sec = N; } public void run(){ System.out.println("Debut du compte"); while(sec!=0) { try { sleep(1000); } catch(Exception e){} alarme.reveille(); sec = sec-1; } System.out.println("Compte termine"); } } 11 / 47 class ThreadAfficheur extends Thread { private Alarme alarme; private Thread threadAAttendre; public ThreadAfficheur(Alarme alarme, Thread threadAAttendre) { this.alarme = alarme; this.threadAAttendre = threadAAttendre; } public void run(){ while(threadAAttendre.isAlive()) { alarme.dors(); System.out.println("1 seconde ecoulee..."); } } } 12 / 47 public class Metronome { public static void main(String arg[]) { Alarme alarme=new Alarme(); ThreadDormeur dormeur = new ThreadDormeur(alarme, 5); ThreadAfficheur afficheur=new ThreadAfficheur(alarme, dormeur) dormeur.start(); afficheur.start(); try { dormeur.join(); afficheur.join(); } catch (InterruptedException e) { System.out.println("Probleme d’attente : "); e.printStackTrace(); } System.out.println("Execution terminee..."); } } 13 / 47 Exécution de l’exemple : problème ! $ javac Metronome.java Alarme.java \ ThreadDormeur.java ThreadAfficheur.java $ java -cp . Metronome ..... que ce passe-t-il ? 14 / 47 Raison et correction : prise en compte de l’exécution concurrente class Alarme { public synchronized void reveille() { notify(); } public synchronized void dors(){ try{ wait(); } catch(Exception e){} } } 15 / 47 Accès concurrents Pour limiter l’accès à une section critique à un seul thread : utilisation du mot clé synchronized valable pour un bloc d’instruction valable pour une méthode 16 / 47 Deuxième partie Socket en JAVA 17 / 47 Généralités La communication réseau avec des socket passe par : Un serveur qui ouvre un port et se met en attente de message (flux d’octets) Un client qui démarre une connexion sur le port ouvert par le serveur Quand la communication est terminée, le flux doit être fermé 18 / 47 Un exemple de serveur import java.io.*; import java.net.*; public class Serveur { static final int port = 1095; public static void main(String[] args) throws IOException, ClassNotFoundException { ServerSocket s = new ServerSocket(port); Socket socket = s.accept(); ObjectInputStream in = new ObjectInputStream( socket .getInputStream()); MyObject oserver = (MyObject) in.readObject(); System.out.println("recu = " + oserver.toString()); socket.close(); } } 19 / 47 Un exemple de client import java.io.*; import java.net.*; public class Client { static final int port = 1095; static final String host = "127.0.0.1"; public static void main(String[] args) throws UnknownHostException, IOException { Socket socket = new Socket(host, port); ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream()); MyObject o = new MyObject(); out.writeObject(o); socket.close(); } } 20 / 47 Petite remarque.... public class MyObject implements Serializable { public String toString() { return "yahooo"; } } 21 / 47 Lecture et écriture Dans le cadre de l’utilisation de chaı̂nes de caractères utilisation de InputStreamReader/OutputStreamWriter Utilisation de BufferedReader/Writer c http://recursor.blogspot.com/ Lecture/écriture utilisation de la méthode readline/write NE PAS OUBLIER LE flush APRES LE write 22 / 47 Application Transformer l’exemple présenté dans la partie “Thread” afin que le processus dormeur et le processus afficheur puissent petre sur des machines distinctes 23 / 47 Troisième partie RMI 24 / 47 Utilité des objets distants Défauts des sockets : Conception du protocole lourde (exemple ”RFC sur les Ftp” : http://www.faqs.org/rfcs/rfc959.html) Source de confusion sur le nombre et le type des paramètres à envoyer Faibles performances dues aux codages/décodages nécessaire Comparaison socket/rmi : http://java.sun.com/developer/ technicalArticles/ALT/sockets/ ⇒ RMI : permet d’utiliser directement des objets 25 / 47 Fonctionnement de RMI c http://www.sce.carleton.ca/courses/94580/ Java-RMI.html 26 / 47 Conception d’un projet RMI c http://www.sce.carleton.ca/courses/94580/ Java-RMI.html 27 / 47 Exemple d’interface import java.rmi.Remote; import java.rmi.RemoteException; public interface HelloInterface extends Remote { public String say() throws RemoteException ; } 28 / 47 Implémentation de l’interface import java.rmi.RemoteException; public class HelloObject implements HelloInterface { private String message; public HelloObject(String msg) throws RemoteException { message = msg; } public String say() throws RemoteException { return message; } } 29 / 47 Écriture du serveur Le code du serveur doit : 1 2 définir une politique de sécurité enregistrer l’objet distant récupération dynamique du stub (type de l’interface) enregistrement du stub sur le rmiregistry 30 / 47 Déclaration d’une politique de sécurité if (System.getSecurityManager() == null) { System.setSecurityManager(new SecurityManager()); } 31 / 47 Récupération du stub HelloObject helloObject = new HelloObject("Action executee par le serveur"); HelloInterface stub = (HelloInterface) UnicastRemoteObject.exportObject(helloObject, 0 ); 32 / 47 Enregistrement de l’objet créé String name = " objet distant "; Registry registry = LocateRegistry.getRegistry() ; registry.rebind(name, stub); 33 / 47 Écriture de la classe serveur import ... public class HelloServer { public static void main(String[] args) { // definition de la politique de securite try { // recuperation du stub de l’objet HelloObject // enregistrement de l’objet sur le rmiregistry System.out.println("Serveur OK"); } catch (Exception e) { System.out.println("Serveur KO : "); e.printStackTrace(); } } } 34 / 47 Classe Naming L’objet peut être obtenu de 2 façons : en recherchant le rmiregistry (si le client et le serveur s’exécutent sur la même machine) en utilisant la classe Naming et une uri du type : rmi://host:port/object_name Dans les 2 cas, la méthode de recherche est lookup Attention ! Pour exporter un objet en utilisant la classe Naming , il est obligatoire que l’objet distant hérite de la classe UnicastRemoteObject 35 / 47 Retour sur l’implémentation import java.rmi.RemoteException; public class HelloObject extends UnicastRemoteObject implements HelloInterface { private String message; public HelloServer(String msg) throws RemoteException { message = msg; } public String say() throws RemoteException { return message; } } 36 / 47 Différents appels à lookup String name = "objet_distant"; Registry registry = LocateRegistry.getRegistry(args[0]); HelloInterface reference = (HelloInterface) registry.lookup(name); String name = "objet_distant"; String host = "127.0.0.1"; String port = args[0]; HelloInterface reference = (HelloInterface) Naming.lookup("rmi://"+host+":"+port+"/"+name); 37 / 47 Différents appels à rebind String name = "objet_distant"; Registry registry = LocateRegistry.getRegistry(args[0]); registry.rebind(name, stub); String name = "objet_distant"; String host = "127.0.0.1"; String port = args[0]; Naming.rebind("rmi://"+host+":"+port+"/"+name, helloObject ); 38 / 47 Écriture de la classe client import ... public class HelloClient { public static void main(String[] args) { // definition de la politique de securite try { // recuperation de l’objet distant String msg = helloObject.say(); System.out.println(msg); } catch (Exception e) { System.out.println("Erreur Client : "); e.printStackTrace(); } } } 39 / 47 Valeurs de sécurités Le code du serveur, comme du client, met en place une politique de sécurité : définie par un fichier : security.policy cas le plus simple et le plus permissif : grant { permission java.security.AllPermission; }; Utilisation de l’option -Djava.security.policy=<path>/security.policy 40 / 47 Compilation et exécution $ javac HelloInterface.java HelloObject.java \ HelloServer.java $ java -cp .\ -Djava.security.policy=./security.policy \ -Djava.rmi.server.codebase=file:./ \ HelloServer 1099 Serveur KO java.rmi.ConnectException: Connection refused to ho java.net.ConnectException: Connection refused .... 41 / 47 Exécution du registre et du serveur LES INTERFACES DOIVENT ÊTRES DANS LE CLASSPATHAVANT L EXECUTION DU REGISTRE ! $ export CLASSPATH=$CLASSPATH:. $ rmiregistry & $ nmap 127.0.0.1 -p 1099 .... PORT STATE SERVICE 1099/tcp open unknown .... $ java -cp . \ -Djava.security.policy=./security.policy \ -Djava.rmi.server.codebase=file:./ \ HelloServer 1099 Serveur OK 42 / 47 Compilation et exécution du client $ javac HelloInterface.java HelloClient.java $ java -cp . \ -Djava.security.policy=./security.policy \ -Djava.rmi.server.codebase=file:./ \ HelloClient Action exécutée par le serveur .... 43 / 47 Génération des noms d’objets En règle générale, les objets distants ne sont pas directement connus du client : le serveur est connu du client, le client envoie une première requête au serveur le serveur répond au client en désignant l’objet distant à utiliser 44 / 47 Passage et type de paramètres Type de base = passage par valeur Dérivé de Remote = passage par référence autre cas : IMPLÉMENTATION DE Serializable passage sous forme standard création (implicite...) d’un nouvel objet chez le client 45 / 47 Une exception Pour les types complexes et spécifiques à une machine/un état mémoire : Descripteur de fichier Thread ... Il n’est pas possible d’utiliser l’interface Serializable 46 / 47 En complément Il est possible de ne pas supposer l’exécution préalable de rmiregistry : la classe LocateRegistry fourni la méthode createRegistry(int port) Pour être compatible avec du code antérieur à java 1.5, il est nécessaire de générer explicitement les stub : utilisation de rmic il prend en paramètre la classe compilée de l’objet distant 47 / 47