Le langage Java permet une communication entre machines qui s'appuie sur le protocole IP (Internet Protocol), protocole de base du réseau Internet. Il y a plusieurs façons de faire communiquer des machines.
Nous pouvons simplement télécharger une ressource Internet référencée par une URL (Uniform Resource Locator) ; nous avons déjà utilisé cette possibilité dans le chapitre consacré aux applets. Nous pouvons employer pour cela la classe java.net.URL. Si nous voulons disposer d'un certain contrôle sur le téléchargement de données, nous pouvons obtenir des fonctionnalités supplémentaires grâce à la classe java.net.URLConnection qui sera utilisée dans le chapitre communication Applet-Servlet.
Nous pouvons communiquer également par le mode "connecté" en utilisant le protocole TCP (Transmission Control Protocol). Nous établissons entre deux machines une connexion par laquelle nous ouvrons des flux de données, connexion que l'on conserve tout le temps de la communication. Les classes ServerSocket et Socket du paquetage java.net doivent être employées pour ce type de communication. Des classes du paquetage java.io sont alors utilisées pour établir les flux de données.
Pour en savoir plus sur les
Applets.
Pour en savoir flux sur les flux de données.
Avant d'écrire notre premier programme sur les réseaux, nous allons utiliser le client Telnet pour établir des connexions à certaines machines qui proposent des services sur le réseau.
Attention, par défaut le client Telnet n'est pas actif sous Windows Vista. Il est nécessaire de l'activer dans la rubrique Fonctionnalités de Windows du Panneau de configuration.
Vous vous êtes peut-être déjà servi de Telnet pour communiquer avec un ordinateur distant en utilisant les commandes de son propre système d'exploitation. Vous pouvez également l'utiliser pour établir une connexion à un service quelconque sur des ordinateurs reliés à votre réseau ou sur Internet (Si le pare-feu le permet). Dans ce cas là, il suffit de respecter le protocole lié au service demandé.
Pour établir la connexion avec la machine distante sur le réseau (tube de communication), il suffit de connaître le nom de la machine (nom de l'hôte) et le numéro du service (numéro de port). Ces tubes de communication sont appelés des Sockets.
Nous allons essayer de nous connecter au service date proposé par un serveur adapté sur Internet :
open time.nist.gov 13 ou open 192.43.244.18 13
Le programme du serveur fonctionne en permanence sur la machine distante, attendant un paquet du réseau qui essaierait de communiquer avec le port 13. Lorsque le système d'exploitation de l'ordinateur distant reçoit le paquet contenant une requête de connexion sur le port 13, le processus d'écoute du serveur est activé et la connexion est établie. Cette connexion demeure jusqu'à ce qu'elle soit arrêtée par l'une des deux parties.
Lorsque vous avez ouvert une session Telnet sur le port 13 de l'ordinateur distant, une partie indépendante du logiciel réseau à converti la chaîne de texte time.nist.gov en une adresse IP associée, c'est à dire 192.43.244.18. Puis le logiciel a envoyé une requête de connexion à cet ordinateur, en spécifiant le port 13.
Une fois que cette connexion a été établie, le programme de l'ordinateur distant a renvoyé un ensemble de données, puis il a terminé la connexion. Bien sûr, dans le cas plus général, les clients et les serveurs entament des dialogues beaucoup plus poussés avant que la connexion ne soit interrompue.
Notre premier exemple de programme réseau effectue la même chose que ce que nous venons de faire avec Telnet, c'est à dire se connecter au port 13 du service date sur le serveur time.nist.gov.
Le résultat renvoyé par le service est un simple texte qu'il est possible de formater afin de le rendre plus compréhensible pour nous.
package reseau; import java.awt.*; import java.awt.event.*; import java.io.IOException; import java.net.*; import java.util.Scanner; import javax.swing.*; public class Client extends JFrame { private JTextField résultat = new JTextField( ); private JToolBar outils = new JToolBar(); public Client() { super( ); résultat.setBackground(Color.YELLOW); add(outils, BorderLayout.NORTH); add(résultat); outils.add(new AbstractAction( ) { public void actionPerformed(ActionEvent e) { try { Socket connexion = new Socket( , 13); Scanner réponse = new Scanner(connexion.getInputStream()); if (réponse.hasNextLine()) { réponse.next(); Scanner date = new Scanner(réponse.next()); date.useDelimiter( ); int année = 2000+date.nextInt(); String mois = date.next(); String jour = date.next(); résultat.setText( +jour+ +mois+ +année+ +réponse.next()); } } catch (UnknownHostException ex) { setTitle( ); } catch (IOException ex) { setTitle( ); } } }); pack(); setLocationRelativeTo(null); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } public static void main(String[] args) { new Client(); } }
Socket connexion = new Socket( , 13);
Evidemment, la classe Socket est très simple d'emploi, car Java masque toute la complexité inhérente à l'établissement d'une connexion réseau et à l'envoi de données. Le paquetage java.net vous fournit quand à lui une interface de programmation tout-à-fait semblable à celle que vous obtiendriez avec un simple fichier.
Dans le cas qui nous préoccupe, seul le serveur dialogue, sans information de retour. La réponse envoyée est un simple texte qui peut toutefois être interprété très simplement en prenant le flux de lecture de texte représenté par la classe Scanner.
Scanner réponse = new Scanner(connexion.getInputStream());
Socket connexion = new Socket( , 13);
Scanner réponse = new Scanner(connexion.getInputStream());
Pour en savoir plus sur les
flux et les fichiers
§
Le principe de base d'un dialogue sur le réseau, c'est de lire le flux d'octets provenant du service jusqu'à ce que l'ensemble de l'information soit récupérée. Dans ce cas de figure, il est tout-à-fait possible que le temps de transfert soit très long, qu'il soit même beaucoup trop important, suite à un problème éventuel de connexion réseau.
Socket connexion = new Socket( , 13);
connexion.setSoTimeout(10000); // 10 secondes d'attente maximum
Si le timeout est atteint avant la fin de lecture complète de l'information attendue, une InterruptedIOException est levée. Vous pouvez ainsi intercepter cette exception et réagir au timeout.
Socket connexion = new Socket();
connexion.connect("time.nist.gov" , 10000);
Dans cette étude, nous ne traitons que du protocole TCP (Transmission Control Protocol). Ce protocole établit une connexion fiable entre deux ordinateurs. La plate-forme Java supporte également le pseudo-protocole UDP (User Datagram Protocol), qui peut être employé pour envoyer des paquets (aussi appelés datagrammes) plus rapidement qu'avec TCP. L'inconvénient majeur de l'UDP est que les paquets peuvent arriver dans le désordre, voire même égarés. C'est donc à l'ordinateur qui reçoit ces paquets de les remettre dans le bon ordre, et de demander un nouvel envoi des paquets perdus. L'UDP est donc plus adapté à des applications pour lesquelles les paquets peuvent être égarés, comme des flux de données audio ou vidéo, ou de mesures continues.
Lorsqu'un client envoi une requête au serveur, ce dernier doit pouvoir déterminer la fin de celle-ci. Pour cette raison, de nombreux protocoles Internet (comme SMTP) sont orienté vers la ligne. D'autres protocoles contiennent un en-tête qui spécifie la taille des données de la requête. En effet, indiquer la fin des données de la requête est plus difficile qu'écrire des données dans un fichier. En effet, il suffit de femer le fichier. En fermant une socket, vous vous déconnectez immédiatement du serveur.
La demi-fermeture résout ce problème. Vous pouvez fermer le flux de sortie d'une socket, au moyen de la méthode shutdownOutput(), ce qui indique au serveur la fin des données de la requête, mais permet de conserver le flux d'entrée ouvert afin que vous puissiez lire la réponse. Dans ce cas, le serveur lit simplement l'entrée jusqu'à ce qu'il atteigne la fin du flux d'entrée. A ce sujet, il existe également la méthode shutdownInput() qui permet quant-à elle de fermer le flux d'entrée et de concerver le flux de sortie (utilisation plus rare).
Bien entendu, ce protocole n'est utile que pour des services à opération unique comme le HTTP où le client se connecte, émet une requête, intercepte la réponse, puis se déconnecte.
A titre d'exemple, je vous propose de créer une petite application qui nous permet de consulter la page d'accueil d'un site quelconque, sans interprétation, c'est-à-dire en visualisant uniquement le balisage HTML, ce que les navigateurs appellent couramment le code source :
GET / HTTP/1.0 suivi de deux retours chariots
package reseau; import java.awt.*; import java.awt.event.*; import java.io.*; import java.net.*; import java.util.Scanner; import javax.swing.*; public class Client extends JFrame { private JTextField adresse = new JTextField(); private JFormattedTextField port = new JFormattedTextField(80); private JTextArea éditeur = new JTextArea(20, 38); private JToolBar outils = new JToolBar(); public Client() { super( ); éditeur.setBackground(Color.YELLOW); éditeur.setEditable(false); adresse.setColumns(20); port.setColumns(4); add(outils, BorderLayout.NORTH); add(new JScrollPane(éditeur)); outils.add(new JLabel( )); outils.add(adresse); outils.addSeparator(); outils.add(new JLabel( )); outils.add(port); outils.add(new AbstractAction( ) { public void actionPerformed(ActionEvent e) { try { Socket connexion = new Socket(adresse.getText(), (Integer) port.getValue()); PrintWriter requête = new PrintWriter(connexion.getOutputStream(), true); Scanner réponse = new Scanner(connexion.getInputStream()); requête.println(\n ); connexion.shutdownOutput(); éditeur.setText( ); while (réponse.hasNextLine()) éditeur.append(réponse.nextLine()+\n ); } catch (IOException ex) { JOptionPane.showMessageDialog(Client.this, adresse.getText(), , JOptionPane.ERROR_MESSAGE); } } }); pack(); setLocationRelativeTo(null); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } public static void main(String[] args) { new Client(); } }
En général, vous n'avez pas besoin de vous préoccuper des adresses Internet, qui sont composés de quatre (ou six pour IPv6) octets de la forme 192.43.244.18. Cependant, vous pouvez avoir recours à la classe InetAddress si vous souhaitez traduire un nom d'ordinateur en adresse Internet et inversement.
Les classes Socket et ServerSocket disposent d'une méthode getInetAddress() qui renvoie un objet InetAddress.
§
InetAddress adresseDistante = "time.nist.gov");
byte[ ] octets = adresseDistante);
InetAddress[ ] adresses = "java.sun.com");
InetAddress adresseLocale = );
Je vous propose de reprendre et de modifier l'exemple du client qui fait appel au service de date. Nous proposons cette fois-ci un afiichage supplémentaire : les @IP des ordinateurs distant et local.
Affichage des différentes adresses des machines sur le réseau, en plus de la date.
package reseau; import java.awt.*; import java.awt.event.*; import java.io.IOException; import java.net.*; import java.util.Scanner; import javax.swing.*; public class Client extends JFrame { private JTextField résultat = new JTextField( ); private JToolBar outils = new JToolBar(); public Client() { super( ); résultat.setBackground(Color.YELLOW); add(outils, BorderLayout.NORTH); add(résultat); outils.add(new AbstractAction( ) { public void actionPerformed(ActionEvent e) { try { Socket connexion = new Socket( , 13); Scanner réponse = new Scanner(connexion.getInputStream()); if (réponse.hasNextLine()) { réponse.next(); Scanner date = new Scanner(réponse.next()); date.useDelimiter( ); int année = 2000+date.nextInt(); String mois = date.next(); String jour = date.next(); String adresseDistante = connexion.getInetAddress().getHostAddress(); String adresseLocale = connexion.getInetAddress().getLocalHost().getHostAddress(); résultat.setText( +adresseDistante+ +adresseLocale+ +jour+ +mois+ +année+ +réponse.next()); } } catch (UnknownHostException ex) { setTitle( ); } catch (IOException ex) { setTitle( ); } } }); pack(); setLocationRelativeTo(null); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } public static void main(String[] args) { new Client(); } }
Maintenant que nous avons implémenté un client simple qui reçoit des données d'un serveur sur Internet (ou sur le réseau local), intéressons-nous à la fabrication d'un serveur simple qui devra envoyer des informations sur Internet (ou sur le réseau local). Une fois que ce serveur (service) sera lancé, il devra attendre qu'un client se connecte à l'un de ces port. Nous choisissons le port 8189, qui n'est utilisé par aucun service standard.
ServerSocket service = new 8189);
Socket client = service.accept(); // méthode blocante
Scanner requête = new Scanner(client.getInputStream());
PrintWriter réponse = new PrintWriter(client.getOutputStream());
Tout ce que le serveur envoie à son flux de sortie devient un flux d'entrée
pour le programme client, et toutes les sorties du client deviennent les entrées
du serveur.
§
Pour l'instant, dans ces exemples nous transmettons du texte. Nous pourrions bien sûr transmettre des données binaires, il faudrait alors utiliser respectivement les classes DataInputStream et DataOutputStream. Pour transmettre des objets en série, il faudrait utiliser ObjectInputStream et ObjectOutputStream.
package reseau; import java.io.*; import java.net.*; import java.util.Scanner; public class ServeurEcho { public static void main(String[] args) throws IOException { ServerSocket service = new ServerSocket(8189); Socket client = service.accept(); // attend la connexion du client Scanner requête = new Scanner(client.getInputStream()); PrintWriter réponse = new PrintWriter(client.getOutputStream(), true); réponse.println( ); while (requête.hasNextLine()) { String ligne = requête.nextLine(); réponse.println( +ligne); if (ligne.trim().equalsIgnoreCase( )) break; } client.close(); // fermeture de la connexion du client } }
Un serveur réel ferait des claculs intermédiaires sur les données reçues et renverrait sa réponse.
§
Nous pouvons tester ce programme en utilisant Telnet.
Une fois que le serveur est lancé, établissons la connexion au
port 8189 sur l'ordinateur qui propose le service :
Lorsque nous nous connectons, nous obtenons le message d'invite "Bonjour,
tapez OK pour sortir". Saisissons n'importe quoi, le serveur nous
répond en nous renvoyant notre message précédé de "Echo : ". Pour nous déconnecter,
il suffit de taper "ok" ou "OK".
Le programme du serveur sera alors également arrêté.
package reseau; import java.io.*; import java.net.*; import java.text.DateFormat; import java.util.Date; public class ServeurDate { public static void main(String[] args) throws IOException { ServerSocket service = new ServerSocket(13); Socket client = service.accept(); // attend la connexion du client PrintWriter réponse = new PrintWriter(client.getOutputStream(), true); réponse.println(DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.MEDIUM).format(new Date())); client.close(); } }
Le serveur de l'exemple précédent possède un inconvénient. Supposons que nous voulions permettre à plusieurs clients de se connecter en même temps à notre serveur. Typiquement, un serveur est exécuté en permanence sur un ordinateur dédié à cette tâche, et plusieurs clients sur Internet peuvent se connecter à ce serveur simultanément. Si le serveur ne peut pas gérer plusieurs connexions en même temps, un utilisateur pourra le monopoliser en restant connecté longtemps. Mais nous pouvons faire beaucoup mieux grâce à la magie des threads.
Pour en savoir plus sur
les threads.
§
Dans l'absolue, il est généralement préférable d'implémenter l'interface Runnable plutôt que de faire un héritage directement depuis la classe Thread. De même, il vaut mieux utiliser la classe Scanner en lieu et place de la classe BufferedReader.
Pour faire la synthèse de ce que nous venons de voir, nous allons créer deux systèmes client-serveur plus utiles :
Le premier système, permet de réaliser des conversions entre les €uros et les francs.
Au début le client doit préciser la localisation du service. Une fois que le connexion est établie, elle reste opérationnelle jusqu'à ce que le client clôture l'application graphique.
package reseau; import java.io.*; import java.net.*; public class ServeurConversion { public static void main(String[] args) throws IOException { ServerSocket service = new ServerSocket(1234); System.out.println( ); while (true) { System.out.println( ); Socket client = service.accept(); // attend la connexion d'un client new Thread(new Connexion(client)).start(); } } } class Connexion implements Runnable { private Socket client; private final double TAUX = 6.55957; private double €uro, franc; public Connexion(Socket client) { this.client = client; } public void run() { String adresse = client.getInetAddress().getHostAddress(); try { DataInputStream requête = new DataInputStream(client.getInputStream()); DataOutputStream réponse = new DataOutputStream(client.getOutputStream()); System.out.println( +adresse+ ); while (true) { String demande = requête.readUTF(); if (demande.equals( )) { €uro = requête.readDouble(); franc = €uro * TAUX; réponse.writeDouble(franc); } if (demande.equals( )) { franc = requête.readDouble(); €uro = franc / TAUX; réponse.writeDouble(€uro); } } } catch (Exception ex) { System.err.println( +adresse+ ); } } }
package reseau; import java.awt.*; import java.awt.event.*; import java.io.*; import java.net.*; import java.text.*; import javax.swing.*; public class ClientConversion extends JFrame { private JTextField adresseIP = new JTextField(12); private JFormattedTextField euro = new JFormattedTextField(NumberFormat.getCurrencyInstance()); private JFormattedTextField franc = new JFormattedTextField(new DecimalFormat( )); private JToolBar outils = new JToolBar( ); private Socket connexion; private DataOutputStream requête; private DataInputStream réponse; public ClientConversion() { super( ); euro.setValue(0.0); franc.setValue(0.0); add(outils, BorderLayout.NORTH); outils.add(new JLabel( )); outils.add(adresseIP); outils.add(new AbstractAction( ) { public void actionPerformed(ActionEvent e) { try { connexion = new Socket(adresseIP.getText(), 1234); requête = new DataOutputStream(connexion.getOutputStream()); réponse = new DataInputStream(connexion.getInputStream()); } catch (UnknownHostException ex) { setTitle( ); } catch (IOException ex) { setTitle( ); } } }); outils.add(new AbstractAction( ) { public void actionPerformed(ActionEvent e) { try { requête.writeUTF( ); Number valeur = (Number) euro.getValue(); requête.writeDouble(valeur.doubleValue()); franc.setValue(réponse.readDouble()); } catch (IOException ex) { setTitle( );} } }); outils.add(new AbstractAction( ) { public void actionPerformed(ActionEvent e) { try { requête.writeUTF( ); Number valeur = (Number) franc.getValue(); requête.writeDouble(valeur.doubleValue()); euro.setValue(réponse.readDouble()); } catch (IOException ex) { setTitle( );} } }); add(euro); add(franc, BorderLayout.SOUTH); pack(); setLocationRelativeTo(null); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } public static void main(String[] args) { new ClientConversion(); } }
A titre d'exemple, pour valider à la fois le fonctionnement du réseau, mais égalemenent la bonne gestion des flux, je vous propose de visualiser automatiquement une photo numérique sur le serveur qui est envoyé par un client sur le réseau local. Nous devons donc élaborer deux applications :
Application cliente qui envoi les photos ............................................. Serveur qui reçoit les photos
package photos; import javax.swing.JComponent; import java.awt.Graphics; import java.awt.image.BufferedImage; class PanneauImage extends JComponent { private BufferedImage image; private double ratio; public void change(BufferedImage image) { if (image!=null) { this.image = image; ratio = (double)image.getWidth()/image.getHeight(); repaint(); } } @Override protected void paintComponent(Graphics surface) { if (image!=null) surface.drawImage(image, 0, 0, this.getWidth(), (int)(this.getWidth()/ratio), null); } }
package photos; import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.awt.image.BufferedImage; import java.io.*; import java.net.*; import javax.imageio.*; public class ClientPhotos extends JFrame implements ActionListener { private String répertoire = "J:/Stockage/"; private String[] liste; private PanneauImage panneau = new PanneauImage(); private JComboBox choix; private JButton envoyer = new JButton("Envoyer la photo"); public ClientPhotos() { liste = new File(répertoire).list(); choix = new JComboBox(liste); panneau.change(récupérer()); choix.addActionListener(this); envoyer.addActionListener(this); setSize(500, 400); setTitle("Envoi de photos"); add(choix, BorderLayout.NORTH); add(envoyer, BorderLayout.SOUTH); add(panneau); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } private BufferedImage récupérer() { try { BufferedImage photo = ImageIO.read(new File(répertoire+choix.getSelectedItem())); return photo; } catch (Exception ex) { setTitle("Problème de localisation des photos"); return null; } } public static void main(String[] args) { new ClientPhotos(); } public void actionPerformed(ActionEvent e) { if (e.getSource() == choix) { panneau.change(récupérer()); } else if (e.getSource() == envoyer) { try { File fichier = new File(répertoire+choix.getSelectedItem()); byte[] octets = new byte[(int)fichier.length()]; FileInputStream photo = new FileInputStream(fichier); photo.read(octets); envoyer(octets); } catch (IOException ex) { setTitle("Problème avec le fichier"); } } } private void envoyer(byte[] octets) { try { Socket connexion = new Socket("localhost", 7777); ObjectOutputStream fluxRéseau = new ObjectOutputStream(connexion.getOutputStream()); fluxRéseau.writeObject(octets); connexion.close(); } catch (IOException ex) { setTitle("Problème avec le serveur"); } } }
package photos; import java.io.*; import java.net.*; import javax.swing.JFrame; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; public class ServeurPhotos extends JFrame { private static PanneauImage panneau = new PanneauImage(); public ServeurPhotos() { setSize(500, 400); setTitle("Visionneuse"); add(panneau); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } public static void main(String[] args) throws Exception { new ServeurPhotos(); activerService(); } public static void activerService() throws Exception { ServerSocket service = new ServerSocket(7777); while (true) { Socket client = service.accept(); ObjectInputStream fluxRéseau = new ObjectInputStream(client.getInputStream()); byte[] octets = (byte[]) fluxRéseau.readObject(); ByteArrayInputStream fluxImage = new ByteArrayInputStream(octets); BufferedImage photo = ImageIO.read(fluxImage); panneau.change(photo); } } }
Lorsque nous nous connectons à une socket, le thread courant se bloque jusqu'à ce que le connexion s'établisse ou lorsque le timeout arrive à expiration. De la même manière, lorsque nous faisons une lecture ou une écriture d'informations au travers de la socket, le thread courant se bloque jusqu'à que l'opération de transfert soit définitivement terminée ou, encore une fois lorsque le timeout arrive à expiration.
Dans une application normale, ou l'interactivité avec l'utilisateur est prépondérante, nous devons lui proposer, à tout moment, d'annuler la communication avec le service distant notamment si la connexion a du mal à s'établir. Cependant, nous ne pouvons pas mettre en oeuvre un thread concurrent, en faisant appel notamment à la méthode interrupt() tant que la socket n'est pas en fonctionnement.
SocketChannel canal = new InetSocketAddress(hôte, port));
Un canal ne possède pas de flux associés. Par contre, il dispose de méthodes read() et write() qui utilisent un objet Buffer. Ces méthodes sont déclarées respectivement dans les interfaces ReadableByteChannel et WritableByteChannel.
Scanner lecture = new Scanner(canal);
OutputStream écriture = Channels.newOutputStream(canal);
InputStream lecture = Channels.newInputStream(canal);
Voilà tout ce que vous deviez savoir sur ce sujet. A chaque fois qu'un thread est interrompu durant l'opération d'ouverture, de lecture ou d'écriture sur une socket, le système n'est plus bloqué, mais se termine par une exception.
Pour accéder à un serveur Web depuis un programme client Java, il serait préférable de travailler à plus haut niveau, plutôt que de mettre en place une gestion par socket, avec les flux d'entrée-sortie associés. Le plus difficile, lorsque nous passons par une architecture classique, est la mise en oeuvre des requêtes issues du protocole HTTP. Dans ce chapitre, nous allons découvrir comment mettre en place un système très performant, grâce à des classes adaptées et tout-à-fait compétentes dans ce genre de protocole.
Les classes URL et URLConnection encapsulent la plupart des problèmes liés à la récupération d'informations sur un site distant.
URL adresse = new URL("http://www.unsite.fr");
La plate-forme Java prend en charge les ressources suivantes : http: https: ftp: file: et jar:
§
InputStream flux = adresse.openStream();
Scanner requête = new Scanner(flux);
Pour savoir à quel moment une classe URI peut être nécessaire, il faut envisager le complexité des URL. Par exemple :
http://maps.google.fr/maps?hl=fr&tab=wl
ftp://utilisateur:motdepasse@ftp.site.com/pub/file.txt
La spécification de l'URI donne les règles de la création de ces identifiants :
[schéma:]spécification du schéma[#fragment]
Ici le texte [ ... ] dénote une partie optionnelle et les caractères : et # figurent littéralement dans l'identifiant.
§
mailto:emmanuel.remy@wanadoo.fr
http://emmanuel.remy@wanadoo.fr
../../java/net/Socket.html#Socket()
[//autorité][chemin][?requête]
[utilisateur@]hôte[:port]
1° http://maps.google.fr/maps:80?hl=fr&tab=wl#partie
2° ftp://utilisateur:motdepasse@ftp.site.com/pub/file.txt
1° http
2° ftp
1° //maps.google.fr/maps:80?hl=fr&tab=wl
2° //utilisateur:motdepasse@ftp.site.com/pub/file.txt
1° //maps.google.fr:80
2° //utilisateur:motdepasse@ftp.site.com
1°
2° utilisateur:motdepasse
1° maps.google.fr
2° ftp.site.com
1° 80
2° -1
1° /maps
2°/pub/file.txt
1° hl=fr&tab=wl
2° //utilisateur:motdepasse@ftp.site.com/pub/file.txt
1° partie
2°
1° http
2° ftp
1° 80
2° 21
http://docs.masociété.com/api/java/net/ServerSocket.html
et d'une URI relative comme :../../java/net/Socket.html#Socket()
Vous pouvez alors associer les deux dans une URI absolue :http://docs.masociété.com/api/java/net/Socket.html#Socket()
Cette procédure s'appelle la résolution d'une URL relative. La procédure opposée est appelée revitalisation.
§
http://docs.masociété.com/api
et d'une URI :http://docs.masociété.com/api/java/lang/String.html
l'URI relative serait :java/lang/String.html
relative = base.relativize(combinée)
combinée = base.resolve(relative)
Beaucoup de méthodes sont communes pour les deux classes URL et URI. La classe URI propose des méthodes supplémentaires qui vont au dela de la localisation spécifique des URL.
§
Si vous désirez obtenir plus d'informations sur une ressource particulière, il vous faudra recourrir à la classe URLConnection, qui fournit un contrôle plus précis que la classe URL de base. Lorsque vous travaillez avec un objet URLConnection, vous devez respecter un processus bien précis, qui voici :
URL adresse = new URL("http://www.unsite.fr");
URLConnection connexion = adresse.openConnexion();
Nous reviendrons sur ces méthodes un peu plus loin dans cette section.
§
connexion.connect();
En plus d'établir une connexion de socket avec un serveur, cette méthode demande aussi au serveur des informations d'en-tête.
§
Attention, les méthodes getInputStream() et getOutputStream() de la classe URLConnection ne sont pas identiques à celles de la classe Socket. La classe URLConnection est assez magique en coulisses, en particulier pour la gestion des en-têtes de requêtes et de réponses. Pour cette raison, il est important que vous suiviez bien les étapes de configuration de la connexion.
connexion.setDoOutput(true);
Pour en savoir plus sur la communication entre applet et servlet.
§
GET www.serveur.com/index.html HTTP/1.0
Referer: http://www.quelquechose.com/liens.html
Proxy-Connection: Keep-Alive
User-Agent: Mozilla/4.76 (Windows ME; U) Opera 9.26 [fr]
Host: www.serveur.com
Accept: text/html, image/gif, image/jpeg, image/png, */*
Accept-Language: fr
Accept-Charset: iso-8859-1, *, utf-8
Cookie: orangemilano=192218887821987
Ces paramètres nont aucun effet en dehors des applets.
§
String entrée = nom + ":" + motDePasse;
String encodage = new sun.misc.BASE64Encoder().encode(entrée);
connexion.setRequestProperty("Autorization", "Basic "+encodage);
String clé = connexion.getHeaderFieldKey(n);
Il n'existe aucune méthode pour renvoyer directement le nombre de champs d'un en-tête. Il suffit alors d'appeler getHeaderFieldKey() jusqu'à obtenir null.
§
String valeur = connexion.getHeaderField(n);
Il existe maintenant une méthode identique qui permet de spécifier, sous forme de chaîne de caractères, l'intitulé de la clé :
String valeur = connexion.getHeaderField("Content-Length");
Map champsEnTête = connexion.getHeaderFields();
Date: Wed, 3 Jun 2009 11:34:38 GMT
Server: Apache/2.2.2 (Unix)
Last-Modified: Thu, 17 Apr 2009 22:42:03 GMT
Accept-Ranges: bytes
Content-Length: 4813
Connection: close
Content-Type: text/html
Nom du champ | Méthode | Type de retour |
---|---|---|
Date | getDate() | long |
Expires | getExpiration() | long |
Last-Modified | getLastModified() | long |
Content-Length | getContentLength() | int |
Content-Type | getContentType() | String |
Content-Encoding | getContentEncoding() | String |
Les méthodes dont le type de retour est long renvoient le nombre de secondes écoulées depuis le 1er janvier 1970, GMT.
§
Pour conclure tout ce chapitre, je vous propose de mettre en oeuvre un tout petit navigateur juste capable d'affiché le contenu d'une page Web. Nous profiterons de ce navigateur pour ajouter un onglet qui nous donnera toutes les indications de consultation du serveur Web distant :
Fonctionnement global du navigateur
package navigateur; import javax.swing.*; import java.net.*; import java.text.MessageFormat; public class Navigateur extends JFrame { private JTabbedPane onglets = new JTabbedPane(); private JTextArea caractéristiques = new JTextArea(); public Navigateur(URL url) throws Exception { super(url.toExternalForm()); onglets.addTab( , new JScrollPane(new JEditorPane(url))); onglets.addTab( , caractéristiques); analyse(url); add(onglets); setSize(800, 700); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } private void analyse(URL url) throws Exception { caractéristiques.append( +url.getAuthority()+'\n'); caractéristiques.append( +url.getHost()+'\n'); caractéristiques.append( +url.getPath()+'\n'); caractéristiques.append( +url.getProtocol()+'\n'); caractéristiques.append( +url.getQuery()+'\n'); caractéristiques.append( +url.getRef()+'\n'); caractéristiques.append( +url.getUserInfo()+'\n'); caractéristiques.append( +url.getDefaultPort()+'\n'); caractéristiques.append( +url.getPort()+'\n'); caractéristiques.append( +url.toURI().getFragment()+'\n'); caractéristiques.append( +url.toURI().getScheme()+'\n'); caractéristiques.append( +url.toURI().getSchemeSpecificPart()+'\n'); URLConnection connexion = url.openConnection(); String motifDate = ; String motifNombre = ; caractéristiques.append( +connexion.getContentType()+'\n'); caractéristiques.append( +MessageFormat.format(motifNombre, connexion.getContentLength())+'\n'); caractéristiques.append( +connexion.getContentEncoding()+'\n'); caractéristiques.append( +MessageFormat.format(motifDate, connexion.getDate())+'\n'); caractéristiques.append( +MessageFormat.format(motifDate, connexion.getLastModified())+'\n'); caractéristiques.append( +MessageFormat.format(motifDate, connexion.getExpiration())+'\n'); caractéristiques.append( +connexion.getHeaderField( )+'\n'); caractéristiques.append( +connexion.getHeaderField( )+'\n'); } public static void main(String[] args) { String adresse = JOptionPane.showInputDialog( ); try { new Navigateur(new URL(adresse)); } catch (Exception ex) { JOptionPane.showMessageDialog(null, +adresse);} } }
Nous disposons maintenant de tous les outils nécessaire pour mettre en oeuvre une petite application "Chat". Nous en limiterons les performances avec la possibilité de se connecter à un instant donné à un seul interlocuteur.
Je rappelle qu'un Chat est la fois client et serveur. Au départ, lorsque
le logiciel est lancé, le service est mise en route et attend une connexion
éventuelle venant de l'extérieur. Lorsque le contact s'établie,
vous pouvez envoyer vos messages dans la zone de saisie. Votre interlocuteur
le reçoit alors dans la zone principale de la fenêtre. Vous pouvez
demander vous-même à vous connecter avec votre interlocuteur, il
suffit de placer alors le nom de l'hôte dans la zone prévue à
cet effet.