Programmation Objet en JAVA Cours 10 : Thread (fin) / Réseau (début)
PLAN Petits rappels Réseau (introduction) Applet RMI
Remarques Le partiel : Les notes bientôt. Le cours, le cours ... Le contrôle continu L’examen : Des questions de cours Des questions de TD Un problème. Le projet : Où en êtes vous ? TD sur les interfaces graphiques
Threads, résumé Un thread est un fil d’instruction indépendant au sein de la machine virtuelle. La machine virtuelle est un processus lançant plusieurs threads. un thread est dédié au main de l’application lancée. Une application peut lancer à son tour d’autres threads. Une application « multi-threaded » peut effectuer plusieurs tâches en parallèle : accès réseau/disque, périphériques (imprimante) gestion des événements graphiques effectuer plusieurs traitements en parallèle.
Threads, manipulation Un processus léger ou thread se manipule par un objet de type Thread : démarrage, les choses à faire, ... Un objet Thread possède un attribut de type Runnable qu’on pourrait nommer « todo » : l’interface Thread impose la méthode run(). Lorsqu’un thread est démarré (start), appel de « todo.run() ». Parfois, todo = this; (Thread implémente Runnable).
Threads, partage des données Synchroniser ce que l'on veut, sur ce que l'on veut. synchronized(syncObject){ /* Ce code ne peut être exécuter que par un seul thread à la fois */ ... } Ainsi les 2 morceaux de codes suivants sont équivalents. void myMethod () { synchronized (this) { ... } synchronized void myMethod () { ... }
Verrou (lock) Chaque objet possède un « «monitor » en tant qu'Object. Lors de l'appel d'une méthode synchronized : l'objet est-il accessible (non-vérouillé) ? si oui, la méthode est exécutée et l'objet verrouillé; si non, la méthode doit attendre la levée du verrou (que la méthode qui « a le verrou » ai terminé). Un seul verrou est partagé par toutes les méthodes synchronized.
wait en détail (I. Charon) wait est héritée de la classe Object. Cette instruction s’utilise dans un code synchronisé. Le thread en cours d'exécution attend. La méthode doit être invoquée sur l'instance verrouillée. Typiquement, on pourra écrire : synchronized(UnObjet) { ... while (condition) unObjet.wait(); } Le thread en cours d'exécution est bloqué par unObjet. L'instruction unObjet.wait() signifie que unObjet bloque le thread en cours d'exécution. Ce n'est pas unObjet qui attend. Il est fréquent que la condition condition porte sur les attributs de unObjet mais ce n'est pas obligatoire. Pendant que le thread attend, le verrou sur unObjet est relâché.
notify en détail (I. Charon) Pour pouvoir débloquer un thread qui attend, bloqué par unObjet : synchronized(refereMemeObjetQuUnObjet) { ... refereMemeObjetQuUnObjet.notify(); } Si plusieurs threads sont dans l’attente imposée par unObjet, l'instruction notify() débloque celle qui attend depuis le plus longtemps. Sinon notifyAll(). Signalons qu'il existe aussi wait(long temps) qui termine l'attente après temps millisecondes, si cela n'est pas fait auparavant.
Planification des threads Lorsqu'un thread est lancé, il garde la main jusqu'à ce qu'il atteigne un des états suivants : Sleeps : appel de Thread.sleep() ou wait(). Waits for lock : une méthode synchronized est appellée, mais le thread n'a pas le verrou. Blocks on I/O : lecture de fichier, attente reseau. Explicit yield control : appel de yield(). Terminates : la méthode run() du thread est terminé ou appel de stop(). Qui prend la main ensuite ?
Timer : Simple et Utile Pragmatiquement Un Thread pour repousser une action dans le temps Dans 3 secondes, si l’utilisateur n’a pas bougé sa souris, afficher un popup disans « Ce bouton sert à quitter le document » Un Thread pour répéter une action régulièrement Tous les 0.5 secondes, redessine la barre de progression. Pour ces cas simple, pas besoin de faire des Threads compliqués : Utiliser un Timer !
Exemple Tutorial Java ->Essential ->Threads ->Timer http://java.sun.com/docs/books/tutorial/essential/threads/timer.html Déclencher une action tout en continuant Déclencher une action après n millisecondes Déclencher des actions toutes les p millisecondes
Exemple de Timer import java.util.Timer; import java.util.TimerTask public class Reminder { private Timer timer; // l'attribut timer est initialisé dans le constructeur public Reminder(int seconds) { timer = new Timer(); // Définition du planning timer.schedule(new RemindTask(), seconds*1000); } // Un timer exécute une tâche de type TimerTask (comparable au // thread!). Définit ici en classe interne : accès aux attributs. class RemindTask extends TimerTask { public void run() { System.out.println("Time's up!"); timer.cancel(); //Terminate the timer thread public static void main(String args[]) { new Reminder(5); System.out.println("Task scheduled.");
plus propre - 1 import java.util.Timer; import java.util.TimerTask; public class Reminder2 { private Timer timer; // Pour le timer est la tâche puisse communiquer : reférences croisées public Reminder2(int seconds) { timer = new Timer(); timer.schedule(new ReminderTask2(this), seconds*1000); } // méthode de contrôle du timer : pour l'extérieur public void stopAll(){ timer.cancel(); public static void main(String args[]) { new Reminder2(5); System.out.println("Task scheduled.");
plus propre - 2 import java.util.TimerTask; public class ReminderTask2 extends TimerTask { // reférences croisées : suite et fin private Reminder2 rem; ReminderTask2(Reminder2 r){ rem=r; } public void run() { System.out.println("Time's up!"); rem.stopAll();
Un timer pour répéter public class AnnoyingBeep { Timer timer; public AnnoyingBeep() { timer = new Timer(); timer.schedule(new RemindTask(),0,1*1000); } class RemindTask extends TimerTask { int numWarningBeeps = 3; public void run() { if (numWarningBeeps > 0) { System.out.println("Beep!"); numWarningBeeps--; } else { System.out.println("Time's up!"); //timer.cancel() is ot necessary because we call : System.exit(0); //stops the AWT thread and everything else. ...
timer.schedule(...) Avec cette méthode (ou ses variantes), il est possible de faire beaucoup de choses. schedule(TimerTask task, long delay, long period) schedule(TimerTask task, Date time, long period) scheduleAtFixedRate(TimerTask task, long delay, long period) scheduleAtFixedRate(TimerTask task, Date firstTime, long period)
PLAN Petits rappels Réseau (introduction) RMI Applet
Rappel sur les E/S Entrées-sorties, le package : java.io.* Basées sur la notion de stream (flux) : un canal de communication avec un écrivain à une extrémité et un lecteur à l'autre. Les classes de base : InputStream/OutputStream classes abstraites définissant les opérations de base pour la lecture/écriture de séquence d'octets Reader/Writer classe abstraites définissant les opérations de base pour la lecture/écriture de séquence de données de type cacactère et supportant l'Unicode. Les méthodes principales : read(...) et write(...), close() java.io.IOException
Flux d’entrée / Flux de sortie Ex : lire un fichier Nous sommes la destination Sortie Ex : écrire un fichier Nous sommes la source
Schéma de programmation Choisir son gestionnaire de flux : Basé sur des caractères : XxxReader/Writer Basé sur des octets : XxxInputStream/OutputStream Ex : lire un fichier c’est du texte : FileReader Des octets : FileInputStream Ouvrir un flux F (connexion) Passe par la construction d’un ou plusieurs objets, afin de s’adapter aux besoins. Lire ou écrire sur F appel de read ou write Fermer F A ne pas oublier. close
E/S : combinaisons de flux Combinaison de streams Systèmes d'enveloppes successives (Wrapper) que l'on met en cascade : « Bufferisation » envelopper le flux à bufferiser dans un Buffered???Stream pour améliorer les performances : InputStream bufferedIn = new BufferedInputStream(unInputStream); Utiliser le flux spécialisé de données pour accéder facilement aux données de types primitifs : DataInputStream dis = new DataInputStream( System.in ); doudle d = dis.readDouble(); Transformations en chaîne de caractères PrintWriter méthodes print(), println() Il est facile (très facile) de se perdre. Garder sous la main suffisement d'exemples, pour trouver son bonheur : Thinking in Java, Bruce Eckel (Chap 11).
Exemple d'entrées adapté du livre de Bruce Eckel. public static void main(String[] args) throws IOException { //Reading input by lines: BufferedReader in = new BufferedReader(new FileReader("/home/allauzen/foo")); String s; while((s = in.readLine())!= null){...} in.close(); //Reading standard input: BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in)); System.out.print("Enter a line (ended with empty line):"); while((s = stdin.readLine()).length() != 0){...} stdin.close(); // Input from memory StringReader in2 = new StringReader(s2); int c; while((c = in2.read()) != -1) System.out.print((char)c); // Formatted memory input try { DataInputStream in3 = new DataInputStream(new ByteArrayInputStream(ba)); while(true) System.out.print((char)in3.readByte()); } catch(EOFException e) { System.err.println("End of stream"); } .... adapté du livre de Bruce Eckel.
Exemple de sorties adapté du livre de Bruce Eckel. public static void main(String[] args) throws IOException { // Print a BufferedReader in file BufferedReader in; // ... try { PrintWriter out1 = new PrintWriter(new BufferedWriter(new FileWriter("foo"))); int lineCount = 1; while((s = in.readLine()) != null ) out1.println(lineCount++ + ": " + s); out1.close(); } catch(EOFException e) { System.err.println("End of stream"); } DataOutputStream out2 = new DataOutputStream(new BufferedOutputStream( new FileOutputStream("/home/allauzen/foo"))); out2.writeDouble(3.14159); out2.writeChars("That was pi\n"); out2.writeBytes("That was pi\n"); out2.close(); } catch(EOFException e) { adapté du livre de Bruce Eckel.
Introduction au réseau Un réseau c'est l'interconnexion de machines (ordinateurs, périphériques). Un noeud est une machine : un hôte (host). Pour échanger des données, un protocole est indispensable : TCP : Transport Control Protocol. Le protocole TCP est « sécurisé » : il est fiable et ordonné. accusé de réception pour les données, gestion des erreurs de transmissions, ... Il existe un autre protocole : UDP User Datagram Protocol. Plus rapide, moins fiable. Dédié aux échanges nécessitant de la rapidité au détriment de la fiabilité (un peu de perte, ce n'est pas toujours grave).
Introduction au réseau (suite) Pour repérer un noeud du réseau, on utilise un adresse : adresse internet, utilisant l'internet protocol : IP. Entre l'adresse et le nom il y a le server DNS (Domain Name System). Le protocole de communication (ce n'est pas le protocole de transport). http, ftp, telnet, ... . Architecture de la communication : client/serveur. Chaque hôte peut fournir plusieurs services : ftp, mail, http. Un hôte possède alors plusieurs port. Pour repérer une ressource sur internet : URL (Uniform Resource Location).
Java et le réseau Faire le point sur ce dont on a besoin : Faire communiquer des applications sur des machines distinctes et distantes. Echanger des ressources à travers le réseau. Travailler à plusieurs et à distance. Centraliser les infos sur un serveur. Java est un langage qui est par essence lié au réseau. La base : le package java.net. Avec Java, travailler sur le réseau est proche du travail sur les fichiers.
Identification d'une machine Internet Protocole : identification unique d'une machine, IP address. Existe sous deux formes : La forme lisible DNS (Domain Name System) : ftp.limsi.fr. La forme illisible « Dotted Quad » : 192.44.78.17 Le lien entre les deux formes se fait via un server DNS. Une IP address est représentée dans les 2 cas par un nombre sur 32 bits. Chaque « quad » nombre est compris entre 0 et 255. En Java pas de souci, il existe une classe qui gère l'IP address : InetAddress. Pour les protocoles IPv4 et IPv6, il existe les classes Inet4Address et Inet6Address qui hérite de InetAddress.
La classe InetAddress classe représentant une adresse IP. Peut être utilisé en local, sur réseau local, ou en global. « The InetAddress class has a cache to store successful as well as unsuccessful host name resolutions. » static InetAddress getByAddress(byte[] addr) Returns an InetAddress object given the raw IP address . static InetAddress getByAddress(String host, byte[] addr) Create an InetAddress based on the provided host name and IP address No name service is checked for the validity of the address. static InetAddress getByName(String host) Determines the IP address of a host, given the host's name.
Exemple d'utilisation de la classe InetAddress import java.net.*; public class WhoAmI { public static void main(String[] args) throws Exception { if(args.length != 1) { System.err.println( "Usage: WhoAmI MachineName"); System.exit(1); } InetAddress a = InetAddress.getByName(args[0]); System.out.println(a); du livre de Bruce Eckel, utile si vous n'avez pas d'adresse IP fixe. > java WhoAmI ftp.limsi.fr ftp.limsi.fr/192.44.78.17 > java WhoAmI popeye popeye/199.190.87.75
Tester ses programmes sans réseau Si vous n'avez pas de réseau sous la main (pas de connexion) et encore moins plusieurs machines ? L'Internet Protocol a prévu l'adresse localhost : « the local loopback » En Java : InetAddress addr = InetAddress.getByName(null); InetAddress.getByName("localhost"); InetAddress.getByName("127.0.0.1");
le port L'adresse IP n'est pas suffisante : il peut y avoir plusieurs « server » sur une même machine. L'idée : en s'adressant à un port, on s'adresse à un service particulier, celui qui est associé à ce port. chaque service (server) est associé à un numéro de port. Un port ne correspond à aucune réalité physique, c'est une abstraction logicielle. Pour se connecter à une machine, il faut son adresse et choisir un port. Le système se réserve les ports de 1 à 1024.
Serveurs et clients L'objectif premier est de connecter ensemble 2 machines et de les faire communiquer. La méthode pour se retrouver, comme dans une gare : l'une ne bouge pas et observe si on la cherche : le server l'autre cherche : le client La notion de client/server est fondamentale pour la connexion de 2 machines, mais ne présume en rien des rôles futurs des machines, cependant ... . Après la connexion, les 2 machines peuvent communiquer via les mécanismes d'entrées/sorties usuels. Un client peut initier (demander) une connexion, alors que le serveur peut accepter une connexion, il doit donc être attentif.
La communication Via TCP : chaque extrémité d'une connexion = « terminal » : socket une socket par client A chaque socket est associé un flux d'E/S. Une socket représente la connexion à une machine (adresse IP)+ un service particulier (un numéro de port) Via UDP : une seule socket qui reçoit tous les paquets des clients demultiplexage : via le port et l'adresse.
socket « The socket is the software abstraction used to represent the terminals of a connection between two machines. » Pour une connexion entre 2 machines, il y a un hypothétique câble qui relie les 2, et à chaque extrémité, il y a 2 sockets : java.net.Socket. En Java, on crée une socket pour se connecter à une autre machine. A partir ce cette socket, sont accessibles un InputStream et un OutputStream (ou avec les convertisseurs adaptés un Reader et un Writer) La connexion est alors considérée comme un flux d'E/S.
Socket : une prise réseau
ServerSocket En plus le server a besoin d'un ServerSocket : un objet qui attend une connexion, bloqué sur l'appel de sa méthode accept() qui renvoie une socket. Un server nécessite 1 ServerSocket et autant de Socket que de client. Un ServerSocket produit des sockets, ce n’est pas une socket.
ServerSocket - 2
Un client Une socket donne accès à un flux d'E/S générique // Attention aux imports try { Socket server = new Socket(toto.free.fr",8080); InputStream in = server.getInputStream(); OutputStream out = server.getOutputStream(); // Write a byte out.write(42); // Say "Hello" (send newline delimited string) PrintStream pout = new PrintStream( out ); pout.println("Hello!"); // Read a byte Byte back = in.read(); // Read a newline delimited string DataInputStream din = new DataInputStream( in ); String response = din.readLine(); server.close(); } catch ( UnknownHostException e ) { System.out.println("Can't find host."); catch ( IOException e ) { System.out.println("Error connecting to host."); Une socket donne accès à un flux d'E/S générique Qu'il est possible de spécialiser Attention à la fermeture de la socket. Gestion efficace des Exceptions. Pour les E/S on peut bufferiser (utilisation de BufferReader et Writer).
Un server // pendant ce temps sur monserver.free.fr try { ServerSocket listener = new ServerSocket(8080); Socket aClient = listener.accept(); // la connexion est établie, ... InputStream in = aClient.getInputStream(); OutputStream out = aClient.getOutputStream(); // Read a byte Byte importantByte = in.read(); // Read a string DataInputStream din = new DataInputStream( in ); String request = din.readLine(); // Write a byte out.write(43); // Say "Goodbye" PrintStream pout = new PrintStream( out ); pout.println("Goodbye!"); aClient.close(); listener.close(); } catch (IOException e ) { } Du coté du server, d'abord on « écoute », attentif aux requètes extérieures. Même communication via un flux d'E/S. Attention à tout fermer. Voici pour le principe, mais : single-threaded ! un seul client ! une seule réponse !
Un server http – 1 import java.net.*; import java.io.*; import java.util.*; /* Un navigateur lance une requète à un server http de la forme : GET /path/filename [optional stuff] */ public class TinyHttpd { public static void main( String arg[] ) throws IOException { ServerSocket ss=new ServerSocket(Integer.parseInt(argv[0])); while ( true ) new TinyHttpdConnection( ss.accept() ); }
Un server http – 2 public class TinyHttpdConnection extends Thread { Socket sock; TinyHttpdConnection ( Socket s ) { sock = s; setPriority( NORM_PRIORITY - 1 ); start(); } public void run() { try { // Pour répondre OutputStream out = sock.getOutputStream(); // Pour traiter la requète String req = new DataInputStream(sock.getInputStream()).readLine(); System.out.println( "Request: "+req );
Un server http – 3 StringTokenizer st = new StringTokenizer( req ); if ((st.countTokens() >= 2) && st.nextToken().equals("GET")){ if ( (req = st.nextToken()).startsWith("/") ) req = req.substring( 1 ); if ( req.endsWith("/") || req.equals("") ) req = req + "index.html"; try { FileInputStream fis = new FileInputStream ( req ); byte [] data = new byte [ fis.available() ]; fis.read( data ); out.write( data ); } catch ( FileNotFoundException e ) new PrintStream( out ).println("404 Not Found"); } else new PrintStream( out ).println( "400 Bad Request" ); sock.close(); // Finir par traiter les Exceptions du try du slide précédent.
Attention ! On préfère hériter de Thread que d'implémenter l'interface Runnable. N'essayer pas ça chez vous : ça marche, mais ce n'est pas sécurisé. Aucune restriction d'accès : tous les fichiers peuvent être accessibles. En java il existe une classe java.lang.SecurityManager dont on hérite pour spécifier les contrôles. Une instance de SecurityManager peut être installée une seule fois dans un Java run-time environment. Après, tous les accès aux ressources système sont controlés : lecture/ecriture/suppression de fichier execution de thread, de processus connexion, création de server, .... La classe contient un ensemble de méthodes « checkXXX ». Si « checkXXX » n'est pas implémentée, la ressource « XXX » n'est pas accessible.
Sécurité extrème class FascistSecurityManager extends SecurityManager { } public class MyApp { public static void main( Strings [] args ) { System.setSecurityManager( new FascistSecurityManager() ); // No access to files, network, windows, etc. ... }
Exemple de sécurité pour http class TinyHttpdSecurityManager extends SecurityManager { public void checkAccess(Thread g) { };// Accès au thread public void checkListen(int port) { }; // ServerSocket public void checkPropertyAccess(String key) { }; // system prop. public void checkAccept(String host,int port){ };// Socket .... public void checkRead( String s ) { if ( new File(s).isAbsolute() || (s.indexOf("..") != -1) ) throw new SecurityException("Access to file : "+s+" denied."); }
Une U.R.L Uniform Ressource Identifier : pointe sur un objet sur Internet. En général : protocole://hostname/location/item ex: http://www.limsi.fr/Individu/allauzen/index.html Le protocole est souvent http ou ftp. hostname désigne « une machine » : un server. Une URL peut désigner un objet static (Web server, ftp) ou dynamique (script, server de news, ... ). L'URL peut également inclure des informations sur le port, sur une partie de l'objet pointé, ... . L'URL peut être relative.
La classe URL Une URL est représentée par une instance de la classe java.net.URL. Ce type d'objet propose des méthodes pour la manipulation de l'information contenue dans l'URL, ainsi que de l'objet pointé. try { URL myUrl = new URL("http://www.limsi.fr/index.html"); URL uneAutre = new URL("http","www.limsi.fr","index.html"); } catch ( MalformedURLException e ) {/* .... */} Les deux URLs pointent sur le même objet. Attention à l'exception.
A partir d'un objet URL getProtocol(), getHost(), getFile() sameFile() d'un point de vue réseau. A la création de l'URL, Java cherche parmi ses protocol handler le bon. S'il en trouve pas : exception. Pour accéder à la ressource, il est toujours possible d'utiliser un InputStream : renvoyé par la méthode openStream(). Par exemple, pour lire une ligne d'un fichier html : try { URL url = new URL("http://server/index.html"); DataInputStream dis = new DataInputStream( url.openStream() ); String line = dis.readLine(); } catch ... l'objet de type InputStream (qui lit des octets) est enveloppé dans un objet DataInputStream, plus approprié (ligne de texte).