IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Penser en Java 2nde édition - Sommaire |  Préface |  Avant-propos | Chapitre : 1  2  3  4  5  6  7  8  9  10  11  12  13  14  15 |  Annexe : A B C D  | Tables des matières - Thinking in Java

  Chapitre 15 - Informatique distribuée

pages : 1 2 3 4 5 6 7 8 9 10 11 12 

La variable threadCount garde la trace du nombre de JabberClientThread existant actuellement. Elle est incrémentée dans le constructeur et décrémentée lorsque run( ) termine (ce qui signifie que le thread est en train de se terminer). Dans MultiJabberClient.main( ) le nombre de threads est testé, on arrête d'en créer s'il y en a trop. Dans ce cas la méthode s'endort. De cette manière, certains threads peuvent se terminer et de nouveaux pourront être créés. Vous pouvez faire l'expérience, en changeant la valeur de MAX_THREADS, afin de savoir à partir de quel nombre de connexions votre système commence à avoir des problèmes.

Les Datagrammes

Les exemples que nous venons de voir utilisent le Protocole de Contrôle de Transmission Transmission Control Protocol (TCP, connu également sous le nom de stream-based sockets- sockets basés sur les flux, NdT), qui est conçu pour une sécurité maximale et qui garantit que les données ne seront pas perdues. Il permet la retransmission des données perdues, il fournit plusieurs chemins au travers des différents routeurs au cas où l'un d'eux tombe en panne, enfin les octets sont délivrés dans l'ordre où ils ont été émis. Ces contrôles et cette sécurité ont un prix : TCP a un overhead élevé.

Il existe un deuxième protocole, appelé User Datagram Protocol (UDP), qui ne garantit pas que les paquets seront acheminés ni qu'ils arriveront dans l'ordre d'émission. On l'appelle protocole peu sûr « (TCP est un protocole ]sûr), ce qui n'est pas très vendeur, mais il peut être très utile car il est beaucoup plus rapide. Il existe des applications, comme la transmission d'un signal audio, pour lesquelles il n'est pas critique de perdre quelques paquets çà et là mais où en revanche la vitesse est vitale. Autre exemple, considérons un serveur de date et heure, pour lequel on n'a pas vraiment à se préoccuper de savoir si un message a été perdu. De plus, certaines applications sont en mesure d'envoyer à un serveur un message UDP et ensuite d'estimer que le message a été perdu si elles n'ont pas de réponse dans un délai raisonnable.

En règle générale, la majorité de la programmation réseau directe est réalisée avec TCP, et seulement occasionnellement avec UDP. Vous trouverez un traitement plus complet sur UDP, avec un exemple, dans la première édition de ce livre (disponible sur le CD ROM fourni avec ce llvre, ou en téléchargement libre depuis www.BruceEckel.com).

Utiliser des URLs depuis un applet

Un applet a la possibilité d'afficher n'importe quelle URL au moyen du navigateur sur lequel il tourne. Ceci est réalisé avec la ligne suivante :

getAppletContext().showDocument(u);

dans laquelle u est l'objet URL. Voici un exemple simple qui nous redirige vers une autre page Web. Bien que nous soyons seulement redirigés vers une page HTML, nous pourrions l'être de la même manière vers la sortie d'un programme CGI.

//: c15:ShowHTML.java
// <applet code=ShowHTML width=100 height=50>
// </applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.net.*;
import java.io.*;
import com.bruceeckel.swing.*;

public class ShowHTML extends JApplet {
  JButton send = new JButton("Go");
  JLabel l = new JLabel();
  public void init() {
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    send.addActionListener(new Al());
    cp.add(send);
    cp.add(l);
  }
  class Al implements ActionListener {
    public void actionPerformed(ActionEvent ae) {
      try {
        // Ceci pourrait être un programme CGI
        // au lieu d'une page HTML.
        URL u = new URL(getDocumentBase(),
          "FetcherFrame.html");
        // Afficher la sortie de l'URL en utilisant
        // le navigateur Web, comme une page ordinaire:
        getAppletContext().showDocument(u);
      } catch(Exception e) {
        l.setText(e.toString());
      }
    }
  }
  public static void main(String[ « args) {
    Console.run(new ShowHTML(), 100, 50);
  }
} ///:~

Il est beau de voir combien la classe URL nous évite la complexité. Il est possible de se connecter à un serveur Web sans avoir à connaître ce qui se passe à bas niveau.

Lire un fichier depuis un serveur

Une variante du programme ci-dessus consiste à lire un fichier situé sur le serveur. Dans ce cas, le fichier est décrit par le client :

//: c15:Fetcher.java
// <applet code=Fetcher width=500 height=300>
// </applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.net.*;
import java.io.*;
import com.bruceeckel.swing.*;

public class Fetcher extends JApplet {
  JButton fetchIt= new JButton("Fetch the Data");
  JTextField f =
    new JTextField("Fetcher.java", 20);
  JTextArea t = new JTextArea(10,40);
  public void init() {
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    fetchIt.addActionListener(new FetchL());
    cp.add(new JScrollPane(t));
    cp.add(f); cp.add(fetchIt);
  }
  public class FetchL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      try {
        URL url = new URL(getDocumentBase(),
          f.getText());
        t.setText(url + "\n");
        InputStream is = url.openStream();
        BufferedReader in = new BufferedReader(
          new InputStreamReader(is));
        String line;
        while ((line = in.readLine()) != null)
          t.append(line + "\n");
      } catch(Exception ex) {
        t.append(ex.toString());
      }
    }
  }
  public static void main(String[ « args) {
    Console.run(new Fetcher(), 500, 300);
  }
} ///:~

La création de l'objet URL est semblable à celle de l'exemple précédent. Comme d'habitude, getDocumentBase( ) est le point de départ, mais cette fois le nom de fichier est lu depuis le JTextField. Un fois l'objet URL créé, sa version String est affichée dans le JTextArea afin que nous puissions la visualiser. Puis un InputStream est créé à partir de l'URL, qui dans ce cas va simplement créer un flux de caractères vers le fichier. Après avoir été convertie vers un Reader et bufferisée, chaque ligne est lue et ajoutée au JTextArea. Notons que le JTextArea est placé dans un JScrollPane afin que le scrolling soit pris en compte automatiquement.

En savoir plus sur le travail en réseau

En réalité, bien d'autres choses à propos du travail en réseau pourraient être traitées dans cette introduction. Le travail en réseau Java fournit également un support correct et étendu pour les URLs, incluant des handlers de protocole pour les différents types de contenu que l'on peut trouver sur un site Internet. Vous découvrirez d'autres fonctionnalités réseau de Java, complètement et minutieusement décrites dans Java Network Programming de Elliotte Rusty Harold (O.Reilly, 1997).

Se connecter aux bases de données : Java Database Connectivity (JDBC)

On a pu estimer que la moitié du développement de programmes implique des opérations client/serveur. Une des grandes promesses tenues par Java fut sa capacité à construire des applications de base de données client/serveur indépendantes de la plate-forme. C'est ce qui est réalisé avec Java DataBase Connectivity (JDBC).

Un des problèmes majeurs rencontrés avec les bases de données fut la guerre des fonctionnalités entre les compagnies fournissant ces bases de données. Il existe un langage standard de base de données, Structured Query Language (SQL-92), mais il est généralement préférable de connaître le vendeur de la base de données avec laquelle on travaille, malgré ce standard. JDBC est conçu pour être indépendant de la plate-forme, ainsi lorsqu'on programme on n'a pas à se soucier de la base de données qu'on utilise. Il est toutefois possible d'effectuer à partir de JDBC des appels spécifiques vers une base particulière afin de ne pas être limité dans ce que l'on pourrait faire.

Il existe un endroit pour lequel les programmeurs auraient besoin d'utiliser les noms de type SQL, c'est dans l'instruction SQL TABLE CREATE lorsqu'on crée une nouvelle table de base de données et qu'on définit le type SQL de chaque colonne. Malheureusement il existe des variantes significatives entre les types SQL supportés par différents produits base de données. Des bases de données différentes supportant des types SQL de même sémantique et de même structure peuvent appeler ces types de manières différentes. La plupart des bases de données importantes supportent un type de données SQL pour les grandes valeurs binaires : dans Oracle ce type s'appelle LONG RAW, Sybase le nomme IMAGE, Informix BYTE, et DB2 LONG VARCHAR FOR BIT DATA. Par conséquent, si on a pour but la portabilité des bases de données il faut essayer de n'utiliser que les identifiants de type SQL génériques.

La portabilité est une question d'actualité lorsqu'on écrit pour un livre dont les lecteurs vont tester les exemples avec toute sorte de stockage de données inconnus. J'ai essayé de rendre ces exemples aussi portables qu'il était possible. Remarquez également que le code spécifique à la base de données a été isolé afin de centraliser toutes les modifications que vous seriez obligés d'effectuer pour que ces exemples deviennent opérationnels dans votre environnement.

JDBC, comme bien des APIs Java, est conçu pour être simple. Les appels de méthode que l'on utilise correspondent aux opérations logiques auxquelles on pense pour obtenir des données depuis une base de données, créer une instruction, exécuter la demande, et voir le résultat.

Pour permettre cette indépendance de plate-forme, JDBC fournit un gestionnaire de driver qui maintient dynamiquement les objets driver nécessités par les interrogations de la base. Ainsi si on doit se connecter à trois différentes sortes de base, on a besoin de trois objets driver différents. Les objets driver s'enregistrent eux-même auprès du driver manager lors de leur chargement, et on peut forcer le chargement avac la méthode Class.forName( ).

Pour ouvrir une base de données, il faut créer une « URL ]de base de données » qui spécifie :

  1. Qu'on utilise JDBC avec « jdbc ».]
  2. Le « sous-protocole » : le nom du driver ou le nom du mécanisme de connectivité à la base de données. Parce que JDBC a été inspiré par ODBC, le premier sous-protocole disponible est la « passerelle » jdbc-odbc », « spécifiée par « odbc ».]
  3. L'identifiant de la base de données. Il varie avec le driver de base de donnée utilisé, mais il existe généralement un nom logique qui est associé par le software d'administration de la base à un répertoire physique où se trouvent les tables de la base de données. Pour qu'un identifiant de base de données ait une signification, il faut l'enregistrer en utilisant le software d'administration de la base (cette opération varie d'une plate-forme à l'autre).

Toute cette information est condensée dans une chaîne de caractères, l'URL de la base de données.]Par exemple, pour se connecter au moyen du protocole ODBC à une base appelée » people, « l'URL de base de données doit être :

String dbUrl = "jdbc:odbc:people";

Si on se connecte à travers un réseau, l'URL de base de données doit contenir l'information de connexion identifiant la machine distante, et peut alors devenir intimidante. Voici en exemple la base de données CloudScape que l'on appelle depuis un client éloigné en utilisant RMI :

jdbc:rmi://192.168.170.27:1099/jdbc:cloudscape:db

En réalité cette URL de base de données comporte deux appels jdbc en un. La première partie jdbc:rmi://192.168.170.27:1099/ « utilise RMI pour effectuer une connexion sur le moteur distant de base de données à l'écoute sur le port 1099 de l'adresse IP 192.168.170.27. La deuxième partie de l'URL, [jdbc:cloudscape:db] représente une forme plus connue utilisant le sous-protocole et le nom de la base, mais elle n'entrera en jeu que lorsque la première section aura établi la connexion à la machine distante via RMI.

Lorsqu'on est prêt à se connecter à une base, on appelle la méthode statique DriverManager.getConnection( ) en lui fournissant l'URL de base de données, le nom d'utilisateur et le mot de passe pour accéder à la base. On reçoit en retour un objet Connection que l'on peut ensuite utiliser pour effectuer des demandes et manipuler la base de données.

L'exemple suivant ouvre une base d'« information de contact » et cherche le nom de famille d'une personne, donné sur la ligne de commande. Il sélectionne d'abord les noms des personnes qui ont une adresse email, puis imprime celles qui correspondent au nom donné :

//: c15:jdbc:Lookup.java
// Cherche les adresses email dans une  
// base de données locale en utilisant JDBC.
import java.sql.*;

public class Lookup {
  public static void main(String[ « args)
  throws SQLException, ClassNotFoundException {
    String dbUrl = "jdbc:odbc:people";
    String user = "";
    String password = "";
    // Charger le driver (qui s'enregistrera lui-même)
    Class.forName(
      "sun.jdbc.odbc.JdbcOdbcDriver");
    Connection c = DriverManager.getConnection(
      dbUrl, user, password);
    Statement s = c.createStatement();
    // code SQL:
    ResultSet r =
      s.executeQuery(
        "SELECT FIRST, LAST, EMAIL " +
        "FROM people.csv people " +
        "WHERE " +
        "(LAST='" + args[0 « + "') " +
        " AND (EMAIL Is Not Null) " +
        "ORDER BY FIRST");
    while(r.next()) {
      // minuscules et majuscules n'ont
      // aucune importance:
      System.out.println(
        r.getString("Last") + ", "
        + r.getString("fIRST")
        + ": " + r.getString("EMAIL") );
    }
    s.close(); // fermer également ResultSet
  }
} ///:~

Une URL de base de données est créée comme précédemment expliqué. Dans cet exemple, il n'y a pas de protection par mot de passe, c'est pourquoi le nom d'utilisateur et le mot de passe sont des chaînes vides.

Une fois la connexion établie avec DriverManager.getConnection( ) l'objet résultant Connection sert à créer un objet Statement au moyen de la méthode createStatement( ). Avec cet objet Statement, on peut appeler executeQuery( ) en lui passant une chaîne contenant une instruction standard SQL-92 (il n'est pas difficile de voir comment générer cette instruction automatiquement, on n'a donc pas à connaître grand'chose de SQL).

La méthode executeQuery( ) renvoie un objet ResultSet, qui est un itérateur : la méthode next( ) fait pointer l'objet itérateur sur l'enregistrement suivant dans l'instruction, ou bien renvoie la valeur false si la fin de l'ensemble résultat est atteinte. On obtient toujours un objet ResultSet en réponse à la méthode executeQuery( ) même lorsqu'une demande débouche sur un ensemble vide (autrement dit aucune exception n'est générée). Notons qu'il faut appeler la méthode next( ) au moins une fois avant de tenter de lire un enregistrement de données. Si l'ensemble résultant est vide, ce premier appel de next( ) renverra la valeur false. Pour chaque enregistrement dans l'ensemble résultant, on peut sélectionner les champs en utilisant (entre autres solutions) une chaîne représentant leur nom. Remarquons aussi que la casse du nom de champ est ignorée dans les transactions avec une base de donnée. Le type de données que l'on veut récupérer est déterminé par le nom de la méthode appelée : getInt( ), getString( ), getFloat( ), etc. Á ce moment, on dispose des données de la base en format Java natif et on peut les traiter comme bon nous semble au moyen du code Java habituel.

Faire fonctionner l'exemple

Avec JDBC, il est relativement simple de comprendre le code. Le côté difficile est de faire en sorte qu'il fonctionne sur votre système particulier. La raison de cette difficulté est que vous devez savoir comment charger proprement le driver JDBC, et comment initialiser une base de données en utilisant le software d'administration de la base.

Bien entendu, ce travail peut varier de façon radicale d'une machine à l'autre, mais la manière dont j'ai procédé pour le faire fonctionner sur un système Windows 32 bits vous donnera sans doute des indications qui vous aideront à attaquer votre propre situation.

Étape 1 : Trouver le Driver JDBC

Le programme ci-dessus contient l'instruction :

Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");

Ceci suggère une structure de répertoire, ce qui est trompeur. Avec cette installation particulière du JDK 1.1, il n'existe pas de fichier nommé JdbcOdbcDriver.class, et si vous avez cherché ce fichier après avoir regardé l'exemple vous avez dû être déçus. D'autre exemples publiés utilisent un pseudo nom, comme myDriver.ClassName, « ce qui nous aide encore moins. En fait, l'instruction de chargement du driver jdbc-odbc (le seul existant actuellement dans le JDK) apparaît en peu d'endroits dans la documentation en ligne (en particulier, une page appelée JDBC-ODBC Bridge Driver]). Si l'instruction de chargement ci-dessus ne fonctionne pas, il est possible que le nom ait été changé à l'occasion d'une évolution de version Java, et vous devez repartir en chasse dans la documention.

Ce livre a été écrit par Bruce Eckel ( télécharger la version anglaise : Thinking in java )
Ce chapitre a été traduit par Jean-Pierre Vidal, Alban Peignier ( groupe de traduction )
télécharger la version francaise (PDF) | Commandez le livre en version anglaise (amazon) | télécharger la version anglaise
pages : 1 2 3 4 5 6 7 8 9 10 11 12 
Penser en Java 2nde édition - Sommaire |  Préface |  Avant-propos | Chapitre : 1  2  3  4  5  6  7  8  9  10  11  12  13  14  15 |  Annexe : A B C D  | Tables des matières - Thinking in Java