Sockets UDP - Eric Cariou
Transcription
Sockets UDP - Eric Cariou
Systèmes distribués Sockets TCP/UDP et leur mise en œuvre en Java Eric Cariou Université de Pau et des Pays de l'Adour Département Informatique [email protected] 1 Plan 1. Sockets UDP 2. Les flux Java 3. Sockets TCP 4. Multicast IP 5. Concurrence 1. Les threads Java 2. Synchronisation en Java 2 Rappel sur les réseaux TCP ou UDP Communication entre systèmes aux extrémités Pas de visibilité des systèmes intermédiaires Application Communication d’extrémité à extrémité TCP/UDP Application TCP/UDP IP IP IP Liaison Liaison Liaison Physique Physique Physique 3 Adressage Adressage pour communication entre applications Adresse « réseau » application = couple de 2 informations Adresse IP et numéro de port Couche réseau : adresse IP Ex : 192.129.12.34 Couche transport : numéro de port TCP ou UDP Ce numéro est en entier d'une valeur quelconque Ports < 1024 : réservés pour les applications ou protocoles systèmes Exemple : 80 = HTTP, 21 = FTP, ... Sur un port : réception ou envoi de données Adresse notée : @IP:port ou nomMachine:port 192.129.12.34:80 : accès au serveur Web tournant sur la machine d'adresse IP 192.129.12.34 4 Socket : prise Sockets Associée, liée à un port C'est donc un point d'accès aux couches réseaux Services d'émission et de réception de données sur la socket via le port En mode connecté (TCP) Connexion = tuyau entre 2 applications distantes Une socket est un des deux bouts du tuyau Chaque application a une socket locale pour gérer la communication à distance Une socket peut-être liée Sur un port précis à la demande du programme Sur un port quelconque libre déterminé par le système 5 Sockets Une socket est Un point d'accès aux couches réseau TCP/UDP Liée localement à un port Adressage de l'application sur le réseau : son couple @IP:port Elle permet la communication avec un port distant sur une machine distante : c'est-à-dire avec une application distante 6 Client/serveur avec sockets Il y a toujours différenciation entre une partie client et une partie serveur Deux rôles distincts au niveau de la communication via TCP/UDP Mais possibilité que les éléments communiquant jouent un autre rôle ou les 2 en même temps Différenciation pour plusieurs raisons Identification : on doit connaître précisément la localisation d'un des 2 éléments communiquants Le coté serveur communique via une socket liée à un port précis : port d'écoute Dissymétrie de la communication/connexion Le client initie la connexion ou la communication 7 Sockets UDP 8 Sockets UDP : principe Mode datagramme Envois de paquets de données (datagrammes) Pas de connexion entre parties client et serveur Pas de fiabilité ou de gestion de la communication Un paquet peut ne pas arrivé (perdu par le réseau) Un paquet P2 envoyé après un paquet P1 peut arriver avant ce paquet P1 (selon la gestion des routes dans le réseau) Principe de communication La partie serveur crée une socket et la lie à un port UDP particulier La partie client crée une socket pour accéder à la couche UDP et la lie sur un port quelconque 9 Sockets UDP : principe Principe de communication (suite) Le serveur se met en attente de réception de paquet sur sa socket Le client envoie un paquet via sa socket en précisant l'adresse du destinataire Couple @IP/port Destinataire = partie serveur @IP de la machine sur laquelle tourne la partie serveur et numéro de port sur lequel est liée la socket de la partie serveur Il est reçu par le serveur (sauf pb réseau) Si le client envoie un paquet avant que le serveur ne soit prêt à recevoir : le paquet est perdu 10 Sockets UDP en Java Java intègre nativement les fonctionnalités de communication réseau au dessus de TCP-UDP/IP Package java.net Classes utilisées pour communication via UDP InetAddress : codage des adresses IP DatagramSocket : socket mode non connecté (UDP) DatagramPacket : paquet de données envoyé via une socket sans connexion (UDP) 11 Sockets UDP en Java Classe InetAddress Constructeurs Pas de constructeurs, on passe par des méthodes statiques pour créer un objet Méthodes public static InetAddress getByName(String host) throws UnknownHostException Crée un objet InetAddress identifiant une machine dont le nom est passé en paramètre L'exception est levée si le service de nom (DNS...) du système ne trouve pas de machine du nom passé en paramètre sur le réseau Si précise une adresse IP sous forme de chaîne (''192.12.23.24'') au lieu de son nom, le service de nom n'est pas utilisé Une autre méthode permet de préciser l'adresse IP sous forme d'un tableau de 4 octets 12 Sockets UDP en Java Classe InetAddress Méthodes (suite) public static InetAddress getLocalHost() throws UnknownHostException Retourne l'adresse IP de la machine sur laquelle tourne le programme, c'est-à-dire l'adresse IP locale public String getHostName() Retourne le nom de la machine dont l'adresse est codée par l'objet InetAddress 13 Sockets UDP en Java Classe DatagramPacket Structure des données en mode datagramme Constructeurs public DatagramPacket(byte[] buf, int length) Création d'un paquet pour recevoir des données (sous forme d'un tableau d'octets) Les données reçues seront placées dans buf length précise la taille max de données à lire Ne pas préciser une taille plus grande que celle du tableau En général, length = taille de buf Variante du constructeur : avec un offset pour ne pas commencer au début du tableau 14 Sockets UDP en Java Classe DatagramPacket Constructeurs (suite) public DatagramPacket(byte[] buf, int length, InetAddress address, int port) Création d'un paquet pour envoyer des données (sous forme d'un tableau d'octets) buf : contient les données à envoyer length : longueur des données à envoyer Ne pas préciser une taille supérieure à celle de buf address : adresse IP de la machine destinataire des données port : numéro de port distant (sur la machine destinataire) où envoyer les données 15 Sockets UDP en Java Classe DatagramPacket Méthodes « get » InetAddress getAddress() int getPort() Si paquet à envoyer : port destinataire sur la machine distante Si paquet reçu : port utilisé par le programme distant pour envoyer le paquet byte[] getData Si paquet à envoyer : adresse de la machine destinataire Si paquet reçu : adresse de la machine qui a envoyé le paquet Données contenues dans le paquet int getLength() Si paquet à envoyer : longueur des données à envoyer Si paquet reçu : longueur des données reçues 16 Sockets UDP en Java Classe DatagramPacket Méthodes « set » void setAddress(InetAdress adr) void setPort(int port) Positionne le port destinataire du paquet pour la machine distante void setData(byte[] data) Positionne l'adresse IP de la machine destinataire du paquet Positionne les données à envoyer int setLength(int length) Positionne la longueur des données à envoyer 17 Sockets UDP en Java Classe DatagramPacket, complément sur les tailles des données envoyées Java n'impose aucune limite en taille pour les tableaux d'octets circulant dans les paquets UDP, mais Pour tenir dans un seul datagramme IP, le datagramme UDP ne doit pas contenir plus de 65467 octets de données Mais en pratique : il est conseillé de ne pas dépasser 8176 octets Un datagramme UDP est rarement envoyé via plusieurs datagrammes IP Car la plupart des systèmes limitent à 8 Ko la taille des datagrammes UDP Pour être certain de ne pas perdre de données : 512 octets max Si datagramme UDP trop grand : les données sont tronquées Si tableau d'octets en réception est plus petit que les données envoyées Les données reçues sont généralement tronquées 18 Sockets UDP en Java Classe DatagramSocket Socket en mode datagramme Constructeurs public DatagramSocket() throws SocketException Crée une nouvelle socket en la liant à un port quelconque libre Exception levée en cas de problème (a priori il doit pas y en avoir) public DatagramSocket(int port) throws SocketException Crée une nouvelle socket en la liant au port local précisé par le paramètre port Exception levée en cas de problème : notamment quand le port est déjà occupé 19 Sockets UDP en Java Classe DatagramSocket Méthodes d'émission/réception de paquet public void send(DatagramPacket p) throws IOException Envoie le paquet passé en paramètre. Le destinataire est identifié par le couple @IP/port précisé dans le paquet Exception levée en cas de problème d'entrée/sortie public void receive(DatagramPacket p) throws IOException Reçoit un paquet de données Bloquant tant qu'un paquet n'est pas reçu Quand paquet arrive, les attributs de p sont modifiés Les données reçues sont copiées dans le tableau passé en paramètre lors de la création de p et sa longueur est positionnée avec la taille des données reçues Les attributs d'@IP et de port de p contiennent l'@IP et le port de la 20 socket distante qui a émis le paquet Sockets UDP en Java Classe DatagramSocket Autres méthodes public void close() public int getLocalPort() Ferme la socket et libère le port à laquelle elle était liée Retourne le port local sur lequel est liée la socket Possibilité de créer un canal (mais toujours en mode non connecté) Pour restreindre la communication avec un seul destinataire distant Car par défaut peut recevoir sur la socket des paquets venant de n'importe où 21 Sockets UDP en Java Classe DatagramSocket Réception de données : via méthode receive Méthode bloquante sans contrainte de temps : peut rester en attente indéfiniment si aucun paquet n'est jamais reçu Possibilité de préciser un délai maximum d'attente public void setSoTimeout(int timeout) throws SocketException L'appel de la méthode receive sera bloquante pendant au plus timeout millisecondes Une méthode receive se terminera alors de 2 façons Elle retourne normalement si un paquet est reçu en moins du temps positionné par l'appel de setSoTimeout L'exception SocketTimeoutException est levée pour indiquer que le délai s'est écoulé avant qu'un paquet ne soit reçu SocketTimeoutException est une sous-classe de IOException 22 Sockets UDP Java – exemple coté client InetAddress adr; DatagramPacket packet; DatagramSocket socket; // adr contient l'@IP de la partie serveur adr = InetAddress.getByName("scinfr222"); // données à envoyer : chaîne de caractères byte[] data = (new String("youpi")).getBytes(); // création du paquet avec les données et en précisant l'adresse du serveur // (@IP et port sur lequel il écoute : 7777) packet = new DatagramPacket(data, data.length, adr, 7777); // création d'une socket, sans la lier à un port particulier socket = new DatagramSocket(); // envoi du paquet via la socket socket.send(packet); 23 Sockets UDP Java – exemple coté serveur DatagramSocket socket; DatagramPacket packet; // création d'une socket liée au port 7777 DatagramSocket socket = new DatagramSocket(7777); // tableau de 15 octets qui contiendra les données reçues byte[] data = new byte[15]; // création d'un paquet en utilisant le tableau d'octets packet = new DatagramPacket(data, data.length); // attente de la réception d'un paquet. Le paquet reçu est placé dans // packet et ses données dans data. socket.receive(packet); // récupération et affichage des données (une chaîne de caractères) String chaine = new String(packet.getData(), 0, packet.getLength()); System.out.println(" recu : "+chaine); 24 Sockets UDP en Java – exemple suite La communication se fait souvent dans les 2 sens Le serveur doit donc connaître la localisation du client Elle est précisée dans le paquet qu'il reçoit du client Réponse au client, coté serveur System.out.println(" ca vient de : "+packet.getAddress()+":"+ packet.getPort()); // on met une nouvelle donnée dans le paquet // (qui contient donc le couple @IP/port de la socket coté client) packet.setData((new String("bien recu")).getBytes()); // on envoie le paquet au client socket.send(packet); 25 Sockets UDP en Java – exemple suite Réception réponse du serveur, coté client // attente paquet envoyé sur la socket du client socket.receive(packet); // récupération et affichage de la donnée contenue dans le paquet String chaine = new String(packet.getData(), 0, packet.getLength()); System.out.println(" recu du serveur : "+chaine); 26 Critique sockets UDP Avantages Simple à programmer (et à appréhender) Inconvénients Pas fiable Ne permet d'envoyer que des tableaux de byte 27 Structure des données échangées Format des données à transmettre Très limité a priori : tableaux de byte Doit donc pouvoir convertir Et attention à la taille réservée : si le récepteur réserve un tableau trop petit par rapport à celui envoyé, une partie des données est perdue Un objet quelconque en byte[] pour l'envoyer Un byte[] en un objet d'un certain type après réception Deux solutions Créer les méthodes qui font cela : lourd et dommage de faire des tâches de si « bas-niveau » avec un langage évolué comme Java Utiliser les flux Java pour conversion automatique (voir suite) 28 Flux Java 29 Flux Java En Java, toutes les entrées/sorties sont gérées via des flux Entrées/sorties standards (clavier/console) Fichiers Sockets ... Flux : tuyaux dans lesquels on envoie ou lit des séries de données Information de base qui transite dans un flux : l'octet 30 Flux Java standards Flux d'entrées/sortie standards System.out System.err Sortie standard, flux de type PrintStream System.out.println(''nombre = ''+nb); Sortie d'erreur strandard, flux de type PrintStream System.in Entrée standard, flux de type InputStream while ((c = (char)System.in.read()) != 'z') System.out.print(c); 31 Hiérarchie de flux Java Java définit une hiérarchie de flux composée de plusieurs dizaines de classes (de types de flux différents) Package java.io Deux classifications transverses Flux est soit d'entrée, soit de sortie Entrée : le programme lit des informations à partir du flux Sortie : le programme écrit des informations dans le flux Nature de l'information transitant sur le flux Binaire : octet par octet Caractère : 2 octets par 2 octets Codage unicode sur 16 bits 32 Hiérarchie de flux Java Hiérarchie principale Flux de base Flux avec tampon Flux d'accès aux fichiers Flux de filtrage Flux d'impression Flux enchaînés par des « pipes » Flux de concaténation de plusieurs flux en un seul Flux de conversion flux caractère/flux binaire Flux de lecture/écriture de différents types int, char ... ou bien encore un objet quelconque (Object) Données codées indépendamment de la plateforme/système 33 Hiérarchie de flux Java Flux binaire, entrée 34 Hiérarchie de flux Java Flux binaire, sortie 35 Hiérarchie de flux Java Flux caractère, entrée 36 Hiérarchie de flux Java Flux caractère, sortie 37 Hiérarchie de flux Java Autres types de flux Package java.util.zip Compression données : GZIPInputStream, ZipInputStream ... Vérification intégrité données (CRC) : CheckedInputStream ... Package javax.crypto Cryptage des données : CipherInputStream ... Et d'autres ... Les flux peuvent être dépendants les uns des autres Un flux est créé à partir d'un autre (par « wrapping ») : il traite les mêmes données mais avec un traitement supplémentaire Codage des données dans un autre type Filtrage des données, mise en tapon ... Un flux est chaîné à un autre par un pipe 38 Méthodes des classes Stream Méthodes générales d'accès aux données du flux Flux en entrée (InputStream) int read() int read(byte[] tab) Lit une suite d'octets en les plaçant dans tab Lit au plus la longueur de tab Retourne le nombre d'octets lu Autres méthodes pour se placer à un endroit donné du flux ... int available() Lecture d'un octet (sous forme de int) dans le flux Retourne le nombre d'octets disponibles en lecture dans le flux void close() Ferme le flux 39 Méthodes des classes Stream Méthodes générales d'accès aux données du flux Flux en sortie (OutputStream) void write(int b) void write(byte[]) Écrit un octet (via un int) dans le flux Écrit le contenu d'un tableau d'octets dans le flux void flush() Force l'écriture dans le flux de toutes les données à écrire void close() Vide le tampon associé au flux en écrivant son contenu Ferme le flux Flux en entrées ou sorties Méthodes générales : accès niveau octet 40 Méthodes des classes Stream Classes de flux spécialisées Offrent des méthodes d'accès plus évoluées que niveau octet Deux types de flux intéressants de ce point de vue Data[Input/Output]Stream Lecture/écriture de types primitifs Java Exemple pour double int, char, boolean, double, long, byte, float, short DataOutputStream : void writeDouble(double b) DataInputStream : double readDouble() Object[Input/Output]Stream Lecture/écriture d'objets de toute nature Très puissant et confortable ObjectOutputStream : void writeObject(Object o) ObjectInputStream : Object readObject() 41 Méthodes des classes Stream Data[Input/Output]Stream (suite) Exemple : écriture d'un objet de la classe Personne (classe programmée n'appartenant pas à la hiérarchie Java) Personne pers = new Personne (''toto'', 24); ObjectOutputStream output = .... ; output.writeObject(pers); Pour pouvoir envoyer un objet dans un flux Sa classe doit implémenter l'interface java.io.Serializable Interface vide qui sert juste à préciser que les objets peuvent être sérialisés C'est-à-dire peuvent être transformés en série de byte et sont donc transmissibles via des flux 42 Méthodes des classes Stream Exceptions niveau flux La plupart des méthodes d'accès aux flux peuvent lever l'exception java.io.IOException Problème quelconque d'entrée/sortie ... Constructeurs Les flux évolués peuvent être construits à partir d'autres flux existants Exemple : créer un ObjectOutputStream à partir d'un FileOutputStream associé au fichier test.bin FileOutputStream fileOut= new FileOutputStream(''test.bin''); ObjectOutputStream objOut = new ObjectOutputStream(fileOut); // peut maintenant enregistrer tout objet dans test.bin via objOut 43 Exemple utilisation de flux Java Exemple concret d'utilisation de flux Écriture d'entiers dans un fichier // ouverture d'un flux en sortie sur le fichier entiers.bin FileOutputStream ficOut = new FileOutputStream("entiers.bin"); // ouverture d'un flux de données en sortie à partir de ce flux DataOutputStream dataOut = new DataOutputStream(ficOut); // écriture des entiers de 10 à 15 dans le fichier for(int i=10;i<16;i++) dataOut.writeInt(i); // fermeture des flux dataOut.close(); ficOut.close(); 44 Exemple utilisation de flux Java Exemple concret d'utilisation de flux Lecture d'entiers à partir d'un fichier // ouverture d'un flux en entrée sur le fichier entiers.bin FileInputStream ficIn = new FileInputStream("entiers.bin"); // ouverture d'un flux de données en entrée à partir de ce flux DataInputStream dataIn = new DataInputStream(ficIn); // tant que des données sont disponibles, on lit des entiers while(dataIn.available() > 0) System.out.println(dataIn.readInt()); // fermeture des flux dataIn.close(); ficIn.close(); 45 Conversion Object <-> byte[] Pour émettre et recevoir n'importe quel objet via des sockets UDP En écriture : conversion de Object en byte[] ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); ObjectOutputStream objectStream = new ObjectOutputStream(byteStream); objectStream.writeObject(object); byte[] byteArray = byteStream.toByteArray(); En lecture : conversion de byte[] en Object ByteArrayInputStream byteStream = new ByteArrayInputStream(byteArray); ObjectInputStream objectStream = new ObjectInputStream(byteStream); object = objectStream.readObject(); 46 Sockets TCP 47 Sockets TCP : principe Fonctionnement en mode connecté Données envoyées dans un « tuyau » et non pas par paquet Flux de données Correspond aux flux Java dans la mise en oeuvre Java des sockets TCP Fiable : la couche TCP assure que Les données envoyées sont toutes reçues par la machine destinataire Les données sont reçues dans l'ordre où elles ont été envoyées 48 Sockets TCP : principe Principe de communication Le serveur lie une socket d'écoute sur un certain port bien précis et appelle un service d'attente de connexion de la part d'un client Le client appelle un service pour ouvrir une connexion avec le serveur Du coté du serveur, le service d'attente de connexion retourne une socket de service (associée à un port quelconque) Il récupère une socket (associée à un port quelconque par le système) C'est la socket qui permet de dialoguer avec ce client Comme avec sockets UDP : le client et le serveur communiquent en envoyant et recevant des données via leur socket 49 Sockets TCP en Java Classes du package java.net utilisées pour communication via TCP InetAddress : codage des adresses IP Même classe que celle décrite dans la partie UDP et usage identique Socket : socket mode connecté ServerSocket : socket d'attente de connexion du coté server 50 Sockets TCP en Java Classe Socket Socket mode connecté Constructeurs public Socket(InetAddress address, int port) throws IOException public Socket(String address, int port) throws IOException,UnknownHostException Crée une socket locale et la connecte à un port distant d'une machine distante identifié par le couple address/port Idem mais avec nom de la machine au lieu de son adresse IP codée Lève l'exception UnknownHostException si le service de nom ne parvient pas à identifier la machine Variante de ces 2 constructeurs pour préciser en plus un port local sur lequel sera liée la socket créée 51 Sockets TCP en Java Classe Socket Méthodes d'émission/réception de données Contrairement aux sockets UDP, les sockets TCP n'offre pas directement de services pour émettre/recevoir des données On récupère les flux d'entrée/sorties associés à la socket OutputStream getOutputStream() InputStream getInputStream() Retourne le flux de sortie permettant d'envoyer des données via la socket Retourne le flux d'entrée permettant de recevoir des données via la socket Fermeture d'une socket public close() Ferme la socket et rompt la connexion avec la machine distante 52 Sockets TCP en Java Classe Socket Méthodes « get » int getPort() InetAddress getAddress() Renvoie l'adresse IP de la machine distante int getLocalPort() Renvoie le port distant avec lequel est connecté la socket Renvoie le port local sur lequel est liée la socket public void setSoTimeout(int timeout) throws SocketException Positionne l'attente maximale en réception de données sur le flux d'entrée de la socket Si temps dépassé lors d'une lecture : exception SocketTimeoutException est levée Par défaut : temps infini en lecture sur le flux 53 Sockets TCP en Java Classe ServerSocket Socket d'attente de connexion, coté serveur uniquement Constructeurs public ServerSocket(int port) throws IOException Crée une socket d'écoute (d'attente de connexion de la part de clients) La socket est liée au port dont le numéro est passé en paramètre L'exception est levée notamment si ce port est déjà lié à une socket Méthodes Socket accept() throws IOException Attente de connexion d'un client distant Quand connexion est faite, retourne une socket permettant de communiquer avec le client : socket de service void setSoTimeout(int timeout) throws SocketException Positionne le temps maximum d'attente de connexion sur un accept Si temps écoulé, l'accept lève l'exception SocketTimeoutException 54 Par défaut, attente infinie sur l'accept Sockets TCP Java – exemple coté client Même exemple qu'avec UDP Connexion d'un client à un serveur Envoi d'une chaîne par le client et réponse sous forme d'une chaîne par le serveur Coté client // adresse IP du serveur InetAddress adr = InetAddress.getByName("scinfr222"); // ouverture de connexion avec le serveur sur le port 7777 Socket socket = new Socket(adr, 7777); 55 Sockets TCP Java – exemple coté client Coté client (suite) // construction de flux objets à partir des flux de la socket ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream()); ObjectInputStream input = new ObjectInputStream(socket.getInputStream()); // écriture d'une chaîne dans le flux de sortie : c'est-à-dire envoi de // données au serveur output.writeObject(new String("youpi")); // attente de réception de données venant du serveur (avec le readObject) // on sait qu'on attend une chaîne, on peut donc faire un cast directement String chaine = (String)input.readObject(); System.out.println(" recu du serveur : "+chaine); 56 Sockets TCP Java – exemple coté serveur // serveur positionne sa socket d'écoute sur le port local 7777 ServerSocket serverSocket = new ServerSocket(7777); // se met en attente de connexion de la part d'un client distant Socket socket = serverSocket.accept(); // connexion acceptée : récupère les flux objets pour communiquer // avec le client qui vient de se connecter ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream()); ObjectInputStream input = new ObjectInputStream(socket.getInputStream()); // attente les données venant du client String chaine = (String)input.readObject(); System.out.println(" recu : "+chaine); 57 Sockets TCP Java – exemple coté serveur Coté serveur (suite) // affiche les coordonnées du client qui vient de se connecter System.out.println(" ca vient de : " +socket.getInetAddress()+":"+socket.getPort()); // envoi d'une réponse au client output.writeObject(new String("bien recu")); Quand manipule des flux d'objets Souvent utile de vérifier le type de l'objet reçu Utilise instanceof Exemple String chaine; Personne pers; Object obj = input.readObject(); if (obj instanceof String) chaine = (String)obj; if (obj instanceof Personne) pers = (Personne)obj; 58 Sockets TCP Critique sockets TCP Avantages Niveau d'abstraction plus élevé qu'avec UDP Mode connecté avec phase de connexion explicite Flux d'entrée/sortie Fiable Inconvénients Plus difficile de gérer plusieurs clients en même temps Nécessite du parallélisme avec des threads (voir suite cours) Mais oblige une bonne structuration coté serveur 59 Sockets UDP ou TCP ? Choix entre UDP et TCP A priori simple TCP est fiable et mieux structuré Mais intérêt tout de même pour UDP dans certains cas Si la fiabilité n'est pas essentielle Si la connexion entre les 2 applications n'est pas utile Exemple Un thermomètre envoie toutes les 5 secondes la température de l'air ambiant à un afficheur distant Pas grave de perdre une mesure de temps en temps Pas grave d'envoyer les mesures même si l'afficheur est absent 60 Sockets UDP ou TCP ? Exemple de protocole utilisant UDP : NFS Network File System (NFS) Accès à un système de fichiers distant A priori TCP mieux adapté car besoin de fiabilité lors des transferts des fichiers, mais NFS est généralement utilisé au sein d'un réseau local UDP est plus basique et donc plus rapide Peu de pertes de paquets TCP gère un protocole assurant la fiabilité impliquant de nombreux échanges supplémentaires entre les applications (envoi d'acquittement...) Peu de perte de paquet en UDP en local : peu directement gérer la fiabilité au niveau NFS ou applicatif et c'est moins couteux en temps Dans ce contexte, il n'est pas pénalisant d'utiliser UDP au lieu de TCP pour NFS NFS fonctionne sur ces 2 couches 61 Multicast IP 62 Multicast On a vu comment faire communiquer des applications 1 à 1 via des sockets UDP ou TCP UDP offre un autre mode de communication : multicast Plusieurs récepteurs pour une seule émission d'un paquet Broadcast, multicast Broadcast (diffusion) : envoi de données à tous les éléments d'un réseau Multicast : envoie de données à un sous-groupe de tous les éléments d'un réseau Multicast IP Envoi d'un datagramme sur une adresse IP particulière Plusieurs éléments lisent à cette adresse IP 63 Multicast Adresse IP multicast Classe d'adresse IP entre 224.0.0.0 et 239.255.255.255 Classe D Adresses entre 225.0.0.0 et 238.255.255.255 sont utilisables par un programme quelconque Les autres sont réservées Une adresse IP multicast n'identifie pas une machine sur un réseau mais un groupe multicast Socket UDP multicast Avant envoi de paquet : on doit rejoindre un groupe Identifié par un couple : @IP multicast/numéro port Un paquet envoyé par un membre du groupe est reçu par tous les membres de ce groupe 64 Multicast Utilités du multicast UDP/IP Evite d'avoir à créer X connexions et/ou d'envoyer X fois la même donnée à X machines différentes En pratique Utilisé pour diffuser des informations Diffusion de flux vidéos à plusieurs récepteurs Pour récupérer des informations sur le réseau Chaine de télévision, diffusion d'une conférence Le même flux est envoyé à tous au même moment 224.0.0.12 : pour localiser un serveur DHCP Limites Non fiable et non connecté comme UDP 65 Multicast UDP en Java Classe java.net.MulticastSocket Spécialisation de DatagramSocket Constructeurs : identiques à ceux de DatagramSocket public DatagramSocket() throws SocketException Crée une nouvelle socket en la liant à un port quelconque libre Exception levée en cas de problème (a priori il doit pas y en avoir) public DatagramSocket(int port) throws SocketException Crée une nouvelle socket en la liant au port précisé par le paramètre port : c'est le port qui identifie le groupe de multicast Exception levée en cas de problème 66 Multicast UDP en Java Classe java.net.MulticastSocket (suite) Gestion des groupes public void joinGroup(InetAddress mcastaddr) throws IOException Rejoint le groupe dont l'adresse IP multicast est passée en paramètre L'exception est levée en cas de problèmes, notamment si l'adresse IP n'est pas une adresse IP multicast valide public void leaveGroup(InetAddress mcastaddr) throws IOException Quitte un groupe de multicast L'exception est levée si l'adresse IP n'est pas une adresse IP multicast valide Pas d'exception levée ou de problème quand on quitte un groupe auquel on appartient pas 67 Multicast UDP en Java Classe java.net.MulticastSocket (suite) Emission/réception de données On utilise les services send() et receive() avec des paquets de type DatagramPacket tout comme avec une socket UDP standard Exemple, exécution dans l'ordre : Connexion à un groupe Envoi d'un paquet Réception d'un paquet Quitte le groupe 68 Multicast UDP en Java Exemple de communication via socket multicast UDP // adresse IP multicast du groupe InetAddress group = InetAddress.getByName("228.5.6.7"); // socket UDP multicast pour communiquer avec groupe 228.5.6.7:4000 MulticastSocket socket = new MulticastSocket(4000); // données à envoyer byte[] data = (new String(''youpi'')).getBytes(); // paquet à envoyer (en précisant le couple @IP/port du groupe) DatagramPacket packet = new DatagramPacket(data, data.length, group, 4000); // on joint le groupe socket.joinGroup(group); 69 Multicast UDP en Java Exemple (suite) // on envoie le paquet socket.send(packet); // attend un paquet en réponse socket.receive(packet); // traite le résultat ... // quitte le groupe socket.leaveGroup(group); Notes Il est possible que le receive récupère le paquet que le send vient juste d'envoyer Besoin d'un autre receive pour réponse venant d'un autre élément 70 Concurrence dans une application Threads Java 71 Concurrence Par principe, les éléments distants communiquants sont actifs en parallèle Plusieurs processus concurrents Avec processus en pause lors d'attente de messages Exemple de flux d'exécution pour notre exemple de client/serveur précédent temps 72 Sockets TCP – gestion plusieurs clients Particularité coté serveur en TCP Une socket d'écoute sert à attendre les connexions des clients A la connexion d'un client, une socket de service est initialisée pour communiquer avec ce client Communication avec plusieurs clients pour le serveur Envoi de données à un client UDP : on précise l'adresse du client dans le paquet à envoyer TCP : utilise la socket correspondant au client Réception de données venant d'un client quelconque UDP : se met en attente d'un paquet et regarde de qui il vient TCP : doit se mettre en attente de données sur toutes les sockets actives 73 Sockets TCP – gestion plusieurs clients Communication avec plusieurs clients (suite) Contrainte Lecture sur une socket : opération bloquante Attente de connexion : opération bloquante Jusqu'à la prochaine connexion d'un client distant Avec un seul flot d'exécution (processus/thread) Si ne sait pas quel est l'ordonnancement des arrivées des données des clients ou de leur connexion au serveur Tant que des données ne sont pas reçues Impossible à gérer Donc nécessité de plusieurs processus ou threads Un processus en attente de connexion sur le port d'écoute Nouvelle connexion : un nouveau processus est créé pour gérer la communication avec le nouveau client 74 Sockets TCP – gestion plusieurs clients Boucle de fonctionnement général d'un serveur pour gérer plusieurs clients while(true) socketClient = acceptConnection() newThread(socketClient) Exemple avec 2 clients -> 75 Gestion plusieurs clients Java offre nativement un mécanisme permettant de gérer des flux d'exécution parallèle Les threads Rappel différence processus/thread Le processus est créé comme une copie d'un processus existant Deux processus distincts avec leur mémoire propre Le thread s'exécute au sein d'un processus existant Nouveau flux d'exécution interne Partage des données du processus 76 Threads en Java Pour créer et lancer un nouveau thread, 2 modes Etendre la classe java.lang.Thread Redéfinir la méthode public void run() Pour lancer le thread Qui contient la séquence de code qu'exécutera le thread Instancier normalement la classe définie Appeler ensuiter la méthode start() sur l'objet créé Implémenter l'interface java.lang.Runnable Définir la méthode public void run() de cette interface Qui contient la séquence de code qu'exécutera la thread Pour lancer le thread Instancier normalement la classe définie Créer une instance de la classe Thread en passant cet objet en paramètre Lancer la méthode start() du thread instancié 77 Thread Java – exemple Classe CalculFactoriel calcule un factoriel et affiche le résultat à l'écran Via un thread à part public class CalculFactoriel extends Thread { protected int nb; public void run(){ int res = 1; for (int i=1; i<=nb; i++) res = res * i; System.out.println(''factoriel de ''+nb+''=''+res); } public CalculFactoriel(int nb) { this.nb = nb; } } 78 Thread Java – exemple Lancement du calcul des factoriels de 1 à 10 en parallèle ... CalculFactoriel cf; for (int i=10; i >= 1; i--) { cf = new CalculFactoriel(i); cf.start(); } ... Deux phases pour lancer un calcul On instantie normalement la classe CalculFactoriel On appelle la méthode start() sur l'objet créé La séquence d'instructions de la méthode run() de la classe CalculFactoriel est exécutée via un nouveau thread créé 79 Thread Java – variante exemple Même exemple mais sans spécialiser la classe Thread Implémentation de l'interface Runnable public class CalculFactoriel implements Runnable { protected int nb; public void run(){ int res = 1; for (int i=1; i<=nb; i++) res = res * i; System.out.println(''factoriel de ''+nb+''=''+res); } public CalculFactoriel(int nb) { this.nb = nb; } } 80 Thread Java – variante exemple Lancement des threads ... CalculFactoriel cf; for (int i=10; i >= 1; i--) { cf = new CalculFactoriel(i); (new Thread(cf)).start(); } ... On lance un Thread générique qui exécutera la méthode run() de l'objet de type Runnable passé en paramètre du constructeur 81 Thread Java – création 2 méthodes pour créer et exécuter un thread Laquelle choisir ? A priori peu de différence Sauf dans le cas où la classe doit hériter d'une autre classe Cas typique d'une applet public MaClasse extends Applet implements Runnable Doit alors forcément utiliser l'interface Runnable 82 Thread Java – résultat exemple (un) résultat de l'exécution du programme factoriel factoriel factoriel factoriel factoriel factoriel factoriel factoriel factoriel factoriel Les résultats des calculs sont affichés dans l'ordre de leur lancement de de de de de de de de de de 10=3628800 9=362880 8=40320 7=5040 6=720 5=120 4=24 3=6 2=2 1=1 Pourtant les calculs de petites valeurs sont normalement plus courts car moins de passages dans la boucle ... 83 Ordonnancement des threads Ordonnancement des processus/threads Sur une machine, nombre de flots d'exécution en réel parallélisme = nombre de processeurs Les processus/threads doivent partager les supports d'exécution pour s'exécuter Pour simuler un parallélisme d'exécution avec un seul processeur Un processus/thread n'est pas exécuté du début à la fin en une seule étape Un processus/thread exécute une partie de ses instructions pendant un temps donné avant de passer la main à un autre processus Plus tard, il retrouvera la main et continuera son exécution 84 Ordonnancement des threads Dépendance thread/processus Un thread est créé par et dans un processus Selon le système d'exploitation, l'ordonnancement se fait Uniquement au niveau processus Au niveau de tous les thread et processus Le système s'occupe de gérer uniquement le parallélisme des processus Un processus gère en interne l'ordonnancement de ses propres threads Les threads des processus et les processus sont ordonnancés par le système Approche mixte L'ordonnancement se fait au niveau processus mais certains threads particuliers peuvent être ordonnancés par le système au même niveau que les processus 85 Ordonnancement des threads Deux types d'ordonnancement par le système (ou par le processus pour ordonnancer ses threads) Préemptif Le système interrompt l'exécution des processus/threads pour partager l'accès au processeur Le système décide quel est le prochain processus/thread qui continuera son exécution Coopératif Un processus/thread ne libère le processeur que Quand il est bloqué momentanément (entrée/sortie ...) De sa propre initiative Le système décide alors quel est le prochain processus/thread qui continuera son exécution 86 Ordonnancement des threads Java Ordonnancement des threads en Java Exécution d'une machine virtuelle Java Via un processus du système d'exploitation Qui exécute plusieurs threads Java Le thread principal Correspondant au static void main(String argv[]) Les threads créés par le programme Les threads gérant l'interface graphique Garbage collector ... Particularité de Java Langage multi-plateformes (windows, linux, solaris, ...) L'ordonnancement des processus/threads dépend du système d'exploitation 87 Ordonnancement des threads Java Principe fondamental On ne doit pas se baser sur un modèle d'ordonnancement particulier pour développer une application multi-threadée en Java Par principe, on considèrera le modèle le plus contraignant Généralement c'est l'ordonnancement coopératif des threads Java Si on veut un parallélisme « correct », tout thread doit relacher la main de temps en temps Sans oublier qu'en cas de synchronisation/communication obligatoire entre threads, il faut que tout thread ait la main régulièrement 88 Ordonnancement des threads Les threads peuvent avoir des priorités différentes Un thread plus prioritaire a la main en priorité Si un thread de plus haute priorité que le thread courant actif veut la main, il la récupère alors de suite Toujours via un ordonnancement préemptif Accès aux priorités, méthodes de la classe Thread public int getPriority() : retourne le niveau de priorité du thread public void setPriority(int priority) : change le niveau de priorité du thread Trois constantes de la classe Thread pour définir les priorités MAX_PRIORITY : niveau de priorité maximal possible (10) MIN_PRIORITY : niveau de priorité minimal possible (1) NORM_PRIORITY : niveau de priorité par défaut (5) 89 Ordonnancement des threads Java Retour sur l'exemple du calcul de factoriel Une fois que la méthode run() d'un thread est commencée, on doit donc supposer que ce thread garde au pire le processeur jusqu'à la fin de sa méthode run() Pour avoir un meilleur parallélisme, il faut qu'un thread passe la main à un autre thread de temps en temps Dans la classe java.lang.Thread public static void yield() Le thread s'interrompt et passe la main à un autre thread Modification de l'exemple Ajout d'un yield() après chaque calcul dans run() for (int i=1; i<=nb; i++) { res = res * i; Thread.yield(); } 90 Thread Java – nouveau résultat exemple (un) résultat d'exécution de l'exemple après la modification factoriel factoriel factoriel factoriel factoriel factoriel factoriel factoriel factoriel factoriel Bien que lancés en dernier, les calculs les plus courts se terminent en premier 1=1 2=2 3=6 4=24 5=120 6=720 7=5040 8=40320 9=362880 10=3628800 Ordonnancement plus « naturel » que le précédent de de de de de de de de de de Correspond à ce que l'on aurait avec un parrallélisme physique complet Mais aurait pu avoir un ordre moins « parfait » 1, 2, 4, 3, 5, 7, 6, 8, 9, 10 par exemple 91 Ordonnancement des threads Un thread passe la main à un autre dès qu'il est bloqué ou en attente, c'est-à-dire dans les cas suivants Il est bloqué en attente sur une entrée/sortie (flux) Il est bloqué sur l'accès à un objet synchronisé Il se met en attente avec un wait() Il fait une pause pendant une certaine durée avec un sleep() Il a executé un yield() pour céder explicitement la main Il se met en attente de la terminaison d'un autre thread avec un join() Il se termine Un thread de plus haute priorité demande la main Une application Java se termine quand Le main() et tous les run() de tous les threads créés sont terminés 92 Interactions entre threads Les threads sont des objets comme les autres Ils possèdent des références sur d'autres objets Un thread peut appeler des méthodes sur ces objets On peut appeler des méthodes sur le thread Communication/interaction possible via ces objets ou les méthodes du thread Avec mécanisme possible d'accès en exclusion mutuelle Relations entre les cycles de vie des threads Un thread peut lancer un autre thread Un thread peut attendre qu'un ou plusieurs threads se terminent Un thread peut se bloquer et attendre d'être réveillé par un autre thread 93 Interactions entre threads Communication par objet partagé Les threads s'exécutent dans la même machine virtuelle, dans le même espace mémoire Accès possible aux mêmes objets Modification de l'exemple précédent pour ne plus afficher les résultats mais les stocker dans un tableau auquel tous les threads ont accès public class CalculFactorial { protected int[] tab; protected int nb; } public void run(){ int res = 1; for (int i=1; i<=nb; i++) res = res * i; //enregistre le résultat dans tableau tab[nb - 1] = res; 94 Interactions entre threads Modification du constructeur pour passer le tableau partagé en paramètre public CalculFactoriel(int nb, int[] tab) { this.nb = nb; this.tab = tab; } Nouveau lancement des threads dans le thread principal int[] resultats = new int[10]; CalculFactoriel cf; for (int i=10; i >= 1; i--) { cf = new CalculFactoriel(i, resultats); (new Thread(cf)).start(); } Avant d'afficher les résultats : doit attendre que tous les threads soient terminés 95 Interactions entre threads Un thread peut attendre qu'un thread se termine via la méthode join() appellée sur le thread dont on attend la fin Pour l'exemple, le thread principal doit attendre que tous les threads lancés soient terminés int[] resultats = new int[10]; CalculFactoriel[] tabCF = new CalculFactoriel[10]; CalculFactoriel cf; // lance les threads for (int i=10; i>=1; i--) { cf = new CalculFactoriel(i, resultats); tabCF[i-1] = cf; cf.start(); } // attend la fin de chaque thread for (int i=0; i < 10; i++) { try { tabCF[i].join(); } } catch(InterruptedException e) {System.err.println(e);} 96 Interactions entre threads Modification exemple (suite) Une fois la boucle avec les join() passée, on est certain que tous les threads de calcul sont finis Peut alors afficher les résultats for (int i=1; i<=10; i++) System.out.println(" factoriel de " +i+"="+resultats[i-1]); Trois méthodes de la classe Thread pour attendre la terminaison d'un thread public void join() : attend la fin du thread public void join(int milli) : attend au plus milli millisecondes public void join(int milli, int nano) : attend au plus milli millisecondes et nano nanosecondes 97 Interactions entre threads Méthodes join() (suite) Les 3 méthodes join() peuvent lever l'exception java.lang.InterruptedException Si exception levée : signifie que l'attente du thread a été interrompue et qu'il reprend son activité Pour arrêter l'attente d'un thread : appel de la méthode public void interrupt() sur le thread Interrogation sur l'état d'un thread public boolean isInterrupted() : retourne vrai si le thread a été interrompu dans son attente public boolean isAlive() : retourne vrai si le thread est en vie (démarré mais pas encore terminé) 98 Synchronisation sur objets Tableau partagé de l'exemple Chaque thread écrit dans sa case du tableau Pas de risque de conflit dans ce cas Mais attention aux accès concurrents à des objets partagés Peut conduire à des incohérences Si 2 threads modifient en même temps le même objet par ex. En pratique, sur une machine mono-processeur, un seul thread est actif en même temps Mais un thread peut commencer une méthode, passer la main à un autre thread qui modifiera l'état de l'objet Le premier thread reprend alors l'exécution de la méthode avec un état différent et incohérent 99 Synchronisation sur objets Exemple de code pouvant poser problème public class CalculPuissance { protected int puissance = 1; public int calculPuissance(int val) { int res = val; for (int i=1; i<puissance; i++) res = res * val; return res; } public setPuissance(int p) { puissance = p; } } Doit prendre en compte le cas d'ordonnancement le plus mauvais Ordonnancement préemptif des threads dans ce cas précis 100 Synchronisation sur objets Exemple (suite) Lancement d'un calcul de puissance CalculPuissance cp = new CalculPuissance(); ... // passage de la référence de cp à d'autres threads cp.setPuissance(3); int resultat = cp.calculPuissance(2); System.out.println(" puissance 3 de 2 = "+resultat); Problème Si pendant l'exécution de calculPuissance(), un autre thread appelle setPuissance(), le calcul sera faux ! Exemple avec un autre thread appelant setPuissance() avec la valeur 4 pendant l'exécution de calculPuissance() puissance 3 de 2 = 16 Valeur 16 renvoyée au lieu de 8 ... Car l'attribut puissance est passé à la valeur 4 au milieu de la boucle 101 Synchronisation sur objets Primitive synchronized Elle s'applique sur un objet (n'importe lequel) Exclusion mutuelle sur une séquence de code Il est impossible que 2 threads exécutent en même temps une section de code marquée synchronized pour un même objet Sauf si un thread demande explicitement à se bloquer avec un wait() Deux utilisations de synchronized Sur la méthode d'une classe (s'applique à tout son code pour un objet de cette classe) public synchronized int calculPuissance(int val) Sur un objet quelconque synchronized(cp) { // zone de code protégée sur l'objet cp } 102 Synchronisation sur objets Retour sur l'exemple Suppression de l'erreur potentielle de calcul On rajoute synchronized dans la définition des méthodes public synchronized int calculPuissance(int val) { int res = val; for (int i=1; i<puissance; i++) res = res * val; return res; } public synchronized setPuissance(int p) { puissance = p; } Il est alors impossible qu'un thread modifie la valeur de puissance lorsqu'un calcul est en cours Car synchronized interdit que setPuissance() soit exécutée tant que l'exécution d'un calculPuissance() n'est pas finie 103 Synchronisation sur objets Exemple du calcul de puissance (suite) Il reste un problème potentiel de cohérence, pour la séquence de lancement du calcul CalculPuissance cp = new CalculPuissance(); ... // passage de la référence de cp à d'autres threads cp.setPuissance(3); // un autre thread peut appeler ici setPuissance // avec la valeur de 4 avant que le calcul soit lancé cp.setPuissance(4); // exécuté dans un autre thread int resultat = cp.calculPuissance(2); System.out.println(" puissance 3 de 2 = "+resultat); Le résultat affiché sera là encore 16 au lieu de 8 Calcul effectué correctement cette fois mais ce n'est pas celui qui était voulu par le thread ! 104 Synchronisation sur objets Exemple du calcul de puissance (suite) Pour éviter ce problème, il faut protéger la séquence de positionnement de la puissance puis du calcul CalculPuissance cp = new CalculPuissance(); int resultat; ... // passage de la référence de cp à d'autres threads synchronized(cp) { cp.setPuissance(3); resultat = cp.calculPuissance(2); } System.out.println(" puissance 3 de 2 = "+resultat); Avec ce code, il est impossible qu'un autre thread exécute sur l'objet cp la méthode setPuissance() entre le setPuissance() et le calculPuissance() 105 Synchronisation sur objets Exemple du calcul de puissance (fin) Avec ce nouveau code, il y a trois sections de code protégées sur l'objet cp, avec un accès en exécution en exclusion mutuelle Si un thread est en train d'exécuter une de ces 3 sections protégées sur l'objet cp Le code de la méthode setPuissance() Le code de la méthode calculPuissance() La séquence synchronized(cp) { cp.setPuissance(3); resultat = cp.calculPuissance(2); } Aucun autre thread ne peut exécuter une des 3 sections protégées tant que le premier thread n'a pas fini d'exécuter sa section protégée Note La séquence de code inclue dans le synchronized(cp) {...} ne contient que des références à cp mais ce n'est pas une obligation 106 Synchronisation sur objets Pour des variables de types primitifs (int ...) en accès concurrent, on utilise volatile Le problème n'est pas forcément dans la possible incohérence en lecture/écriture Mais vient du fonctionnement des threads Localement, un thread gère une copie d'une variable partagée La déclarer comme volatile force à garder la cohérence entre la copie locale et la variable partagée Exemple protected volatile int nb; public int incNb() { return nb++; } Assure que si un thread exécute incNb() il utilise la valeur de nb la plus à jour 107 Synchronisation entre threads Problème courant Besoin d'un point de synchronisation entre threads Un thread fait un calcul et un autre thread attend que le résultat de ce calcul soit disponible pour continuer son exécution Solution basique : déclarer un booléen available qui sera mis à vrai quand le résultat est disponible public class ThreadCalcul extends Thread { protected boolean available; protected int result; public boolean getAvailable() { return available;} public int getResult() { return result; } public void run() { // faire calcul, mettre result à jour et préciser // que le résultat est disponible available = true; // continuer l'exécution du thread } } 108 Synchronisation entre threads Du coté du thread attendant le résultat Solution basique Vérifier en permanence la valeur de available ThreadCalcul calcul; // calcul lancé avec référence sur bon thread ... // boucle attendant que le résultat soit disponible while (!calcul.getAvailable()) { // fait rien, juste attendre que available change } int res = calcul.getResult(); Problème Attente active Le thread qui fait la boucle peut ne jamais lacher la main L'autre thread ne peut donc pas faire le calcul ! 109 Synchronisation entre threads Pour éviter problème de l'attente active Soit le thread passe la main avec un yield() dans la boucle Mais reste très actif pour pas grand chose ... Dans la boucle, le thread peut faire des pauses Pause d'un thread : sleep dans la classe Thread public static void sleep(long millis[, int nanos]) throws InterruptedException Le thread courant fait une pause de millis millisecondes [et nanos nanosecondes] Pendant cette pause, un autre thread peut alors prendre la main L'exception InterruptedException est levée si le thread a été interrompu pendant sa pause 110 Synchronisation entre threads Modification de la boucle d'attente avec un sleep while (!calcul.getAvailable()) { try { Thread.sleep(100); } catch (java.lang.InterruptedException e) { ... } } } int res = calcul.getResult(); Problèmes Combien de temps doit durer la pause ? On est pas averti dès que le calcul est fini Solution idéale Se mettre en pause et être réveillé dès que le résultat est disponible Programmation en mode « réactif » : réaction/réveil sur événements, jamais d'attente ou de vérification active 111 Synchronisation entre threads Synchronisation par moniteur Dans une section de code protégée par un synchronized, trois primitives de synchronisation sur un objet public void wait() throws InterruptedException public void notify() Le thread se bloque Il permet alors à un autre thread d'exécuter une séquence de code protégée sur l'objet C'est le cas où un thread peut exécuter une séquence protégée alors qu'un autre thread n'a pas terminé son exécution Il existe 2 variantes permettant de rester bloquer au plus un certain temps Débloque un thread bloqué (pris au hasard si plusieurs thread bloqués) sur un wait() sur cet objet public void notifyAll() Débloque tous les threads bloqués sur un wait() sur cet objet 112 Synchronisation entre threads Synchronisation par moniteur wait(), notify() et notifyAll() sont des méthodes de la classe java.lang.Object Application à l'exemple précédent Thread faisant le calcul public class ThreadCalcul extends Thread { protected boolean available; protected int result; public synchronized boolean getAvailable() { return available; } 113 Synchronisation entre threads Thread faisant le calcul (suite) public void run() { // faire le calcul, mettre result à jour et préciser // que le résultat est disponible synchronized(this) { available = true; this.notifyAll(); }// continuer l'exécution du thread } // opération de récupération du résultat // si pas encore disponible, on attend qu'il le soit public synchronized getResult() { while (!this.getAvailable) try { this.wait(); } catch (InterruptedException e) {... } } } Thread attendant le résultat int res = calcul.getResult(); Si le résultat n'est pas disponible, on sera bloqué en attendant le notify executé par le thread de calcul 114 Thread Résumé sur les threads Objet ayant son propre flot d'exécution Etats, opérations associés aux threads Créé : instantiation standard d'un objet Java Démarré et actif : après appel de la méthode start() En pause : méthode sleep() Bloqué sur un objet synchronisé : méthode wait() Réveillé par un notify() sur le même objet Attente de la terminaison d'un autre thread : méthode join() Interrompu pendant une pause sur un wait(), sleep() ou un join() par l'appel de interrupt() L'exception InterruptedException est levée Terminé : arrivé à la fin de sa méthode run() 115 Résumé sur les threads Pourquoi et quand utiliser des threads ? Sur une machine mono-processeur pas de gain de performances a priori en parallélisant Certains cas imposent des threads Pour entreés/sorties, les lectures sont généralement bloquantes Les interfaces graphiques utilisent également des threads, généralement de hautes priorités On dédie un ou plusieurs threads aux réceptions pour gérer des réceptions multiples, ainsi que d'éviter de bloquer le programme Les threads de lecture communiquent par synchronisation/objets communs avec les autres threads Peut gérer des événements graphiques (clics ...) venant de l'utilisateur alors que le programme effectue d'autres traitements Peut aussi être une meilleure structuration du fonctionnement de l'application 116 Résumé sur les threads Points délicats en programmation multi-threadée Eviter les famines : un thread n'a jamais la main Ne jamais utiliser d'attente active S'assurer qu'un thread passe la main suffisamment Ne pas avoir des threads de haute priorité beaucoup trop actifs Eviter les interblocages Un thread T1 attend le résultat d'un thread T2 qui attend lui le résultat du thread T1 Si chacun se bloque sur un wait(), aucun ne pourra faire le notify() réveillant l'autre Interblocage peut se passer via une chaîne de plusieurs threads interdépendants Pas toujours simple à détecter si dépendances complexes 117 Résumé sur les threads Problèmes de performances en cas d'utilisation massive de threads Relativement coûteux de créer un thread à chaque requête En temps de création et de destruction par le garbage collector Pool de thread : ensemble de threads déjà créés et qui peuvent être réutilisés pour traiter de nouvelles requêtes La méthode run() est une boucle qui traite une requête à chaque passage Avec la synchronisation (wait/notify) on peut relancer un passage dans la boucle pour traiter une nouvelle requête Attention à la taille du pool selon le nombre de requêtes simultanées à traiter Eviter de définir des méthodes en synchronized prenant un temps relativement long à s'exécuter et étant souvent appelées 118