VIII - La programmation réseau

 

Comme pour les threads, Java possède en standard une API permettant de programmer des applications réseau, en utilisant des sockets. Dans ce chapitre, nous traiterons des sockets TCP ainsi que des URL car Java possède plusieurs classes spécialisées dans ce domaine.

 

1. Présentation

Une socket est une notion très répandue en programmation réseau, en particulier sous UNIX. Il s'agit en fait d'un point de communication entre un processus et un réseau. Un processus client et un processus serveur, lorsqu'ils communiquent, ouvrent donc chacun une socket. C'est le système d'exploitation qui alloue ces sockets sur demande d'une application. A chaque socket est associé un port de connexion. Ces numéros de port sont uniques sur un système donné, une application pouvant en utiliser plusieurs (un serveur par exemple exploite une socket par client connecté). Certains numéros sont réservés pour des applications (ou protocoles) précises, par exemple le port 80 est réservé aux serveurs web (HTTP). Ces ports réservés ont un numéro inférieur à 1024.

Une connexion est identifiée de façon unique par la donnée de deux couples, une adresse IP et un numéro de port, un pour le client et un autre pour le serveur. Il est important de noter qu'un dialogue client/serveur n'a pas forcément lieu via un réseau. Il est en effet possible de faire communiquer un client et un serveur sur une même machine, via ce qu'on appelle l'interface de loopback, représentée par convention par l'adresse IP 127.0.0.1.

Dans ce chapitre, nous allons voir comment manipuler des sockets TCP et des URL. Nous n'aborderons pas la programmation d'applications en mode non connecté, via UDP.

 

2. Les sockets en Java

Java distingue les sockets clientes des sockets serveurs en deux classes, respectivement Socket et ServerSocket, contenues dans le package java.net. Nous allons introduire les principales notions liées à la programmation d'un client et d'un serveur TCP en Java, en prenant l'exemple d'un serveur echo c'est à dire qui renvoie le texte envoyé par un client. Nous présenterons ensuite un client utilisant ce serveur.

Voici le source du serveur echo :

// ServeurEcho.java

//

import java.io.*;

import java.net.*;

class ServeurEcho {

public static void main(String [] argv) {

try {

// On crée une socket serveur (port passé en

// argument)

ServerSocket s_ecoute = new ServerSocket(Integer.valueOf (argv[0]).intValue());

System.out.println("Serveur démarré sur la socket d'écoute " + s_ecoute);

// Attente d'une connexion

Socket s_service = s_ecoute.accept();

// Une connexion a été ouverte

System.out.println("Ouverture de la connexion sur la socket de service " + s_service);

// On crée un flot d'entrée et un de sortie

// (compatible JDK 1.1 uniquement)

BufferedReader entree = new BufferedReader (new InputStreamReader ( s_service.getInputStream()));

PrintWriter sortie = new PrintWriter ( new OutputStreamWriter ( s_service.getOutputStream()));

// On boucle sur l'echo

while (true) {

// On lit une ligne en entrée

String buff = entree.readLine();

// On quitte si c'est égal à "FIN"

if (buff.equals("FIN")) break;

// On renvoie l'echo au client

sortie.println(buff);

sortie.flush();

}

// Le client s'est déconnecté

System.out.println("Fermeture de la connexion...");

// On ferme la socket de service

s_service.close();

}

catch (Exception e) { return;}

}

}

Analysons cet exemple. Tout d'abord, on importe les classes du package dans lequel sont définies les sockets, java.net, ainsi que celui correspondant aux classes gérant les entrées/sorties, java.io.

On crée ensuite une socket serveur, dite socket d'écoute, sur le port indiqué sur la ligne de commande. Ensuite, on attend la réception d'une demande de connexion sur cette socket, via la méthode bloquante accept(). Cette méthode renvoie une nouvelle socket, dite socket de service, via laquelle le serveur et le client vont pouvoir dialoguer.

Nous définissons ensuite deux flots, l'un en entrée et l'autre en sortie, avant d'entrer dans une boucle infinie qui constitue le cœur du serveur echo : elle copie le texte reçu du client dans une chaîne et le renvoie tel quel au client. La communication se termine quand le client envoie la chaîne "FIN" au serveur. Précisons que la méthode flush() de la classe PrintWriter a pour effet d'envoyer immédiatement le texte voulu sur la socket.

Voyons maintenant le source du client correspondant :

// ClientEcho.java

//

import java.io.*;

import java.net.*;

class ClientEcho {

public static void main (String argv[]) {

try {

// On crée un objet InetAdress sur

// l'interface de loopback

InetAddress adr = InetAddress.getByName("127.0.0.1");

// On crée une socket

Socket s = new Socket (adr, Integer.valueOf (argv[0]).intValue());

System.out.println("Socket crée");

// On crée 2 flots d'entrée et un de sortie

DataInputStream saisie =

new DataInputStream

(new BufferedInputStream (System.in));

DataInputStream entree = new DataInputStream (new BufferedInputStream (s.getInputStream()));

PrintStream sortie = new PrintStream ( new BufferedOutputStream(s.getOutputStream()));

// On envoie du texte au serveur et on

// affiche l'echo reçu

while (true) {

// Saisie du texte à envoyer au serveur

System.out.println("Texte ? ");

String buff = saisie.readLine();

// Si on entre "FIN", on quitte

if (buff.equals("FIN")) break;

// On envoie le texte saisi au serveur

sortie.println(buff);

sortie.flush();

// On affiche l'écho du serveur

String buff2 = entree.readLine();

System.out.println(buff2);

}

// On ferme la socket

s.close();

}

catch (Exception e) { return; };

}

}

Etudions ce source. Dans la fonction main(), on crée un objet de type InetAddress qui va contenir l'adresse IP de la machine sur laquelle tourne le serveur. Ici, on suppose que le client et le serveur sont sur la même machine, on indique donc l'adresse de loopback, via la méthode getByName(). On crée ensuite une socket de communication vers le serveur, en spécifiant comme numéro de port celui indiqué sur la ligne de commande. Bien sûr, cela doit être le même numéro que pour le serveur.

Ensuite, en crée trois flots, deux en entrée (un pour la saisie au clavier, l'autre pour lire la réponse du serveur) et un en sortie (pour envoyer le texte au serveur).

Nous entrons alors dans une boucle, telle que celle que nous avions créée pour le serveur, dans laquelle s'effectuent trois opération successives :

  1. lecture de la chaîne à envoyer, à partir du clavier (String buff = saisie.readLine();),
  2. envoi de cette chaîne au serveur, sauf si elle est égale à "FIN", signifiant que l'on veut fermer la connexion avec le serveur (sortie.println(buff);)
  3. lecture de l'écho envoyé par le serveur (String buff2 = entree.readLine();) et affichage de celui-ci à l'écran.

Enfin, on ferme la socket quand on sort de la boucle.

Nous n'en dirons pas plus sur les sockets, il faut cependant noter qu'un serveur réel aurait utilisé des threads afin de pouvoir gérer simultanément plusieurs connexions (ce qui n'est pas le cas pour l'exemple que nous avons pris). Voyons maintenant comment Java gère les URL.

 

3. Les URL

Java permet d'utiliser des URL, lors de la désignation d'une ressource, ce qui simplifie grandement la programmation de certaines applications. Regardons comment ces URL sont utilisées en Java, en prenant l'exemple d'un programme qui récupère un fichier sur un serveur web et qui en affiche le contenu à l'écran :

// GetURL.java

//

import java.io.*;

import java.net.*;

class GetURL {

public static void main (String [] argv) {

try {

URL url = new URL (argv[0]);

URLConnection uc = url.openConnection();

// On crée un flot d'entrée

DataInputStream entree = new DataInputStream (new BufferedInputStream (uc.getInputStream()));

// On récupère le document

System.out.println("Contenu du document " + argv[0] + ":");

String buff = " ";

while (buff != null) {

buff = entree.readLine();

System.out.println(buff);

}

} catch (Exception e) { return ; }

}

}

Cette fois-ci, on ne définit plus d'objet socket mais un objet URL, associé à une URL (passée en argument). Ensuite, on tente de se connecter au serveur spécifié dans cette URL (URLConnection uc = url.openConnection()). On ouvre après un flot de lecture sur l'URL, puis on entre dans une boucle qui copie les données envoyées par le serveur à l'écran.

Cet exemple très simple montre comme il est facile d'utiliser les URL pour réaliser des applications de base. C'est une intéressante alternative aux sockets que Sun nous propose.

Avant de décrire AWT et de présenter les applets Java, étudions ce qu'on appelle les exceptions.