Vous êtes sur la page 1sur 21

Communiquer à travers un réseau avec Java

https://perso.telecom-paristech.fr/hudry/coursJava/reseau/index.html
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.
On peut simplement télécharger une ressource Internet référencée par une URL (Uniform Resource Locator. On
peut employer pour cela la classe java.net.URL. Si on veut disposer d'un certain contrôle sur le
téléchargement des données, on peut obtenir des fonctionnalités supplémentaires grâce à la classe
URLConnection du paquetage java.net ; nous n'illustrons pas cette classe.
Nous verrons comment communiquer par l'envoi et la réception de datagrammes (communication utilisant le
protocole UDP pour User Datagram Protocol). Dans ce mode de communication, il n'y a pas de liaison établie
entre les deux machines qui doivent communiquer ; les messages sont envoyés au coup par coup, munis de
l'adresse du destinataire ; ce mode de communication est rapide et efficace mais, par sa nature, il n'offre pas de
garantie sur le fait qu'un message atteigne bien sa destination ou n'ait pas été altéré ou encore sur l'ordre d'arrivée
de différents messages. Les classes utilisées pour ce type de communication sont essentiellement
DatagramPacket, DatagramSocket et MulticastSocket du paquetage java.net. Nous verrons un
exemple d'envoi et de réception de messages par datagrammes. Si on souhaite communiquer à l'intérieur d'un
groupe, on peut aussi communiquer en envoyant des datagrammes à l'ensemble des membres du groupe en
utilisant le multicast ; nous verrons aussi deux exemples de ce type de communication.
On peut préférer une communication en mode connecté (communication utilisant le protocole TCP pour
Transmission Control Protocol). On établit entre deux machines une connexion par laquelle on ouvre des flux de
données, connexion qu'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 utilisées pour établir les flux de données. Nous illustrons ce mode de communication
par un exemple. Ce mode de communication est plus fiable
• Communication en utilisant des datagrammes
• Communication en mode connecté modèle client-serveur
• Communication en mode connecté modèle poste à poste
• Communication en multicast
• Communication avec RMI
• Communication avec RMI, une autre programmation

Communication en utilisant des datagrammes


https://perso.telecom-paristech.fr/hudry/coursJava/reseau/data.html
L'envoi et la réception de messages par datagrammes nécessitent d'effectuer les opérations décrites ci-dessous.
• on crée un socket sur la machine locale pour envoyer ou recevoir des datagrammes en instanciant la
classe java.net.DatagramSocket ;
• pour l'envoi d'un message : on construit un « paquet », qui est en fait une instance de la classe
java.net.DatagramPacket, contenant des données ; ce paquet comporte aussi notamment l'adresse IP de
destination du message. Autrement dit, on commence par écrire un message et on le met dans une
enveloppe comportant l'adresse écrite à l'aide d'une instance de la classe java.net.InetAdress. On
envoie le message à l'aide de l'instance de DatagramSocket qu'on a construite ;
• pour la réception d'un message : on crée un « paquet », instance de la classe
java.net.DatagramPacket, avec un tampon vide susceptible de recevoir le message ; on attend
l'arrivée du message en utilisant l'instance de DatagramSocket qu'on a construite ; on déchiffre le
message.
Nous proposons un exemple composé de deux classes comportant chacune une méthode main.
Le programme EnvoiMessage envoie des messages par des datagrammes sur une machine dont le nom est
indiqué sur la ligne de commande ; le numéro de port de la machine destinatrice sur lequel les messages seront
attendus est aussi indiqué sur la ligne de commande ; ce numéro doit être compris entre 1024 et 65535. Chaque
message est donné par l'entrée standard sur une seule ligne. Après l'envoi de chaque message, le programme
attend un accusé de réception.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;

class EnvoiMessage {
public static void main(String[] arg) {
int portDestinataire;
InetAddress adresseIP;// Adresse IP du destinataire
DatagramSocket socketUDP;//Permet d’envoyer et recevoir un message
DatagramPacket message;//Permet d’instancier un message
BufferedReader entree;//Permet de lire des lignes
String ligne;//Objet pour stocker une ligne
int longueur;
byte[] tampon;//On envoi les bits sur le réseau et pas les caractères

try {
new DatagramSocket();//Instanciation d’une socket
socketUDP =
System.out.println("Port local : " + socketUDP.getLocalPort());
//Recuperation de l’adresse IP du destinataire en convertissant
// le premier argument (le nom du destinataire) en adresse IP
adresseIP = InetAddress.getByName(arg[0]);
//Recuperation du port du destinataire en convertissant
// le deuxième argument en un entier
portDestinataire = Integer.parseInt(arg[1]);
// Instanciation d’u BufferedReader pour lire des lignes
entree = new BufferedReader(new InputStreamReader(System.in));

while(true) {
ligne = entree.readLine();//Lecture d’une ligne au clavier

// on construit le paquet a envoyer


tampon = ligne.getBytes();//Conversion de la ligne lue en bits
longueur = tampon.length;//Taille en nombre de bits
//Construction d’un DatagramPacket pour l’envoi
message = new DatagramPacket(tampon, longueur, adresseIP,
portDestinataire);
socketUDP.send(message);//Envoi du message

// on attend un accusé de réception


tampon = new byte[256];
//Construction d’un DatagramPacket pour la reception
message = new DatagramPacket(tampon, tampon.length);
socketUDP.receive(message);//Reception du message
ligne = new String(tampon);//Conversion des bits en caractères
ligne = ligne.substring(0, message.getLength()); //Extraction

System.out.println("Du port " + message.getPort() +


" de la machine " + message.getAddress().getHostName() +
" : " + ligne);
} // END While
} // END try
catch(ArrayIndexOutOfBoundsException exc) {
System.out.println("Avez-vous donne le nom de la machine
destinatrice et le numero de port du client ?");
}
catch(UnknownHostException exc) {
System.out.println("Destinataire inconnu");
}
catch(SocketException exc) {
System.out.println("Probleme d'ouverture de la socket");
}
catch(IOException exc) {
System.out.println("Probleme sur la reception ou l'envoi du
message");
}
catch(NumberFormatException exc) {
System.out.println("Le second argument doit etre un entier");
}
} //END main
}//END class

Le programme ReceptionMessage sert à recevoir les messages. Il doit être lancé sur la machine destinatrice
en donnant en argument le numéro de port où sont attendus les messages. Lorsqu'un message arrive, il est
affichéàà l'écran précédé de l'adresse de la machine et du numéro de port dont est issu le message ; après chaque
réception de message, le programme envoie un accusé de réception.

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;

class ReceptionMessage {
public static void main(String[] arg) {
DatagramSocket socketUDP;//Permet d’envoyer et recevoir un message
DatagramPacket message;//Permet d’instancier un message
byte[] tampon;//On envoi les bits sur le réseau et pas les caractères
int portLocal;
//Conversion du String "accuse de reception" en bits
byte[] tamponAccuse = "accuse de reception".getBytes();
int longueurAccuse = tamponAccuse.length;
String texte ;

try {
//Recuperation du port du recepteur (ReceptionMessage) en
// convertissant le premier argument en un entier
portLocal = Integer.parseInt(arg[0]);
socketUDP = new DatagramSocket(portLocal);
while(true) {
// on se prepare à recevoir un datagramme
tampon = new byte[256];
//Instanciation d’un DatagramPacket pour la reception
message = new DatagramPacket(tampon, tampon.length);
socketUDP.receive(message); //Reception s’un message
//Obtention de l’adresse IP de l’expéditeur du message reçu
InetAddress adresseIP = message.getAddress();
//Obtention du numéro de port de l’expéditeur du message reçu
int portDistant = message.getPort();
texte = new String(tampon) ;//Conversion des bits en caractères
texte = texte.substring(0, message.getLength());
System.out.println("Reception du port " + portDistant +
" de la machine " + adresseIP.getHostName() + " : " +
texte);

// On envoie un accuse de reception


//Instanciation d’un DatagramPacket pour l’envoi
message = new DatagramPacket(tamponAccuse, longueurAccuse,
adresseIP, portDistant);
socketUDP.send(message);//Envoi du message
}
} //END Try

catch(ArrayIndexOutOfBoundsException exc) {
System.out.println("Avez-vous donne le numero de port sur
lequel vous attendez le message ?");
}
catch(SocketException exc) {
System.out.println("Probleme d'ouverture du socket");
}
catch(IOException exc) {
System.out.println("Probleme sur la reception ou
l'envoi du message");
}
} //END main
} // END class

Si on lance sur la machine gargantua.enst.fr :


java ReceptionMessage 10302
puis si, sur la machine mars.eons.fr (on peut le remplacer par localhost), on lance la commande :
java EnvoiMessage gargantua.enst.fr 10302
puis qu'on indique par le clavier de cette machine :
bonjour
alors, sur gargantua, on peut obtenir :
Reception du port 56327 de la machine mars.eons.fr : bonjour
et sur mars :
Port local : 56327
Du port 10302 de la machine gargantua.enst.fr : accuse de reception
InetAddress : cette classe de java.net modélise des adresses Internet.

DatagramSocket : cette classe de java.net modélise un socket qui peut recevoir ou envoyer des
datagrammes.

socketUDP = new DatagramSocket(); : crée un socket pour datagrammes et la rattache à un port libre
de la machine locale ; elle peut envoyer une exception de type SocketException si le socket ne peut pas être
ouvert et une exception du type SecurityException si un gestionnaire de sécurité existe et n'autorise pas la
création de ce socket (il n'est pas nécessaire d'attraper une exception de type SecurityException ou d'en
signaler, par une clause throws, le possible lancement, car la classe SecurityException hérite de la
classe RuntimeException).

getByName(arg[0]) : cette méthode statique de la classe InetAddress renvoie l'instance de


InetAddress correspondant à une machine spécifiée par son nom. Elle peut envoyer une exception de type
UnknownHostException.

new DatagramPacket(tampon, longueur, adresseIP, portDestinataire) : on fabrique le


paquet à envoyer en indiquant en arguments le message sous forme d'un tableau d'octets, la longueur du
message, l'adresse IP et le port de destination.

new DatagramPacket(tampon, tampon.length); : l'objet DatagramPacket construit est destiné


à recevoir le message d'accusé de réception.

ligne.substring(0, message.getLength()) : cet appel de méthode permet d'extraire de ligne les


caractères effectivement envoyés ; cela fonctionne parce qu'ici chaque caractère a été codé sur un seul octet et
donc la longueur du message est égale au nombre de caractères envoyés

getAddress() : la méthode getAddress de la classe DatagramPacket renvoie, sous la forme d'une


instance de la classe InetAddress, l'adresse IP de la machine d'où vient un datagramme, ou vers laquelle un
datagramme va être envoyé.

getHostName() : la méthode getHostName de la classe InetAddress renvoie le nom de la machine


correspondant à l'adresse contenue par l'instance de InetAddress concernée ; elle peut envoyer une exception
de type SecurityException si un gestionnaire de sécurité existe et n'autorise pas la connexion avec cette
machine.

Communication en mode connecté modèle client-serveur


L'exemple est ici de type client-serveur. Un serveur attend des clients pour leur rendre le service suivant : le
client envoie un texte issu d'un fichier de type texte, ligne par ligne, et le serveur compte le nombre total de mots
du texte.
Le client indique sur la ligne de commande le nom d'un fichier dont il veut compter les mots et le nom de la
machine serveur. Le port du serveur sur lequel se fait l'attente de demande de connexion a été fixé dans le
programme et vaut 10302. Lorsque le texte a été totalement envoyé, le client envoie la chaîne de caractères
"*+*+*+*+" pour indiquer au serveur la fin de l'envoi.
Voici le programme client.

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.net.UnknownHostException;

class Client {
public static void main(String[] arg) {
// Numéro du port du serveur
int portEcouteServeur = 10302;
//permet de lire des lignes d’un fichier
BufferedReader lecteurFichier;
//permet de lire des lignes d’une socket comme dans un fichier
BufferedReader entree;
//permet d’écrire des lignes dans une socket comme dans un fichier
PrintStream sortie;
String ligne;// Permet de stocker une ligne de caractères
//Permet de communiquer comme si on lisait et écrivait dans un fichier
Socket socket;
try {
//Instanciation d’une socket TCP avec le nom (deuxième argument)
//et le numéro de port du serveur
socket = new Socket(arg[1], portEcouteServeur);
// Instanciation d’un BufferedReader avec le nom du fichier
// (premier argument) à envoyer au serveur
lecteurFichier = new BufferedReader(new FileReader(arg[0]));
// Instanciantion d’un BufferedReader pour lire une socket
// comme un fichier
entree = new BufferedReader(new
InputStreamReader(socket.getInputStream()));
// Instanciantion d’un PrintStream pour écrire dans une socket
// comme dans un fichier
sortie = new PrintStream(socket.getOutputStream());

//Tant que la ligne suivante lue dans le fichier n’est pas vide
// on lit une ligne dans le fichier et on l’écrit dans la socket
while ((ligne = lecteurFichier.readLine()) != null)
sortie.println(ligne);

//Ecriture de la ligne de fin dans la socket


sortie.println("*+*+*+*+");
System.out.println(entree.readLine());
sortie.close();//Fermeture du flux de sortie de la socket
entree.close();//Fermeture du flux d’entrée de la socket
socket.close();//Fermeture de la socket TCP.
}
catch(FileNotFoundException exc) {
System.out.println("Fichier introuvable");
}
catch(UnknownHostException exc) {
System.out.println("Destinataire inconnu");
}
catch(IOException exc) {
System.out.println("Probleme d'entree-sortie");
}
}//END main
}// END Client

Le programme serveur est un peu plus long. La méthode principale crée un socket d'écoute puis, dans une boucle
sans fin, utilise le socket d'écoute pour attendre des clients. Pour chaque client, le programme ouvre un socket
de communication avec ce client puis instancie pour ce client la classe Service, que nous avons définie,
destinée à rendre le service (recevoir un texte ligne par ligne et en compter les mots).
Pour permettre au serveur de gérer plusieurs clients à la fois, la classe Service étend la classe Thread et
effectue le travail de réception des lignes de texte et de dénombrement des mots dans sa méthode run. Plus
précisément, le constructeur de la classe Service ouvre un flux d'entrée et un flux de sortie sur le socket de
communication puis démarre le thread ; durant la méthode run, le thread reçoit le texte ligne par ligne et utilise la
classe StringTokenizer pour compter les mots ; lorsque la ligne reçue est *+*+*+*+, le nombre de mots est
envoyé par le socket au client.
Les techniques de base relatives à la communication réseau sont en grande partie les mêmes que celles de la
classe Client visible ci-dessus.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.StringTokenizer;

class Service extends Thread {


// Un Thread est un objet de la classe Thread ou d’une autre classe qui
// implémente l’interface Runnable. Une interface est un ensemble de
// méthodes représentant des comportements. Implémenter une interface
// revient à donner corps à toutes ses méthodes. Une classe qui annonce
// une interface sans dpnner corps à toutes les méthodes de cette
// interface est une classe abstraite. Une classe est dite abstraite si
// elle a au moins une méthode qui n’a pas de corps. L’interface
// runnable a une seule méthode : run()
Socket socket;
BufferedReader entree; //Permet de lire des lignes de caractère
PrintStream sortie;

Service(Socket socket) {
// Le constructeur prend en entrée une socket TCP.
this.socket = socket;
try {
// Instanciantion d’un BufferedReader pour lire une socket
// comme un fichier
entree = new BufferedReader(new
InputStreamReader(socket.getInputStream()));

// Instanciantion d’un PrintStream pour écrire dans une socket


// comme dans un fichier
sortie = new PrintStream(socket.getOutputStream());

// Lancement de l’exécution du Thread. L’objert en cours de


// construction (this) est un Thread. Ceci lance l’exécution de la
// méthode run()
this.start();
}
catch(IOException exc) {
try {
socket.close();
}
catch(IOException e){}
}
}//End constructeur de Service

public void run() {


String texte;
int compteur = 0;
try {
//Tant que la ligne lue est différente de la ligne de fin
while(!(texte = entree.readLine()).equals("*+*+*+*+"))
//On donne à StringTokenizer les délimiteurs et il extrait les
//tokens.La méthode countTokens() retourne le nombre de tokens
compteur += (new StringTokenizer(texte, " ,.;:_-+*/\\.;\"'{}
()=><\t!\n")).countTokens();
sortie.println("votre texte possede " + compteur + " mots");
sortie.close();//Fermeture du flux de sortie
entree.close();//Fermeture du flux d’entrée
socket.close(); //Fermeture de la socket
} //END try
catch(IOException e) {}
}//END run
}//END du Thread Service

class Serveur {
public static void main(String[] arg) {
int portEcoute = 10302;
ServerSocket standardiste;
Socket socket;

try {
// Instanciation d’un ServerSocket qui délègue :
// Recoit une connexion et crée un Thread Service qui traite
// la requette de cete connexion
standardiste = new ServerSocket(portEcoute);
while(true) {
socket = standardiste.accept(); //accepter la connexion
new Service(socket); //Instantiation d’un Thread Service
}
}
catch(IOException exc) {
System.out.println("probleme de connexion");
}
}// END main
} // END class Serveur

Si on lance la commande :
java Serveur
sur la machine gargantua.enst.fr (on peut le remplacer par localhost)
puis si, sur la machine cliente, on lance, pour compter le nombre de mots du fichier Serveur.java, la commande :
java Client Serveur.java gargantua.enst.fr alors, sur la machine cliente, on obtient :
votre texte possede 125 mots
Vous pouvez accéder à :
• La classe Client.java
• La classe Serveur.java

portEcouteServeur = 10302 : numéro de port sur lequel le serveur attend les clients.

PrintStream : on ne peut pas utiliser ici un PrintWriter.

new Socket(arg[1], portEcouteServeur) : l'instance de la classe java.net.Socket ainsi construite permet


de créer un socket pour une communication en mode connecté avec la machine dont le nom est indiqué en
premier argument. Le second argument indique le port de la machine serveur sur lequel la demande de
connexion de clients est attendue. De nombreux autres constructeurs existent pour cette classe. Ce constructeur
lance une exception de type UnknownHostException si le nom de la machine correspond à une machine
inconnue et une exception de type IOException si une erreur se produit au moment de la création du socket.

socket.getInputStream() : on obtient ainsi une instance de la classe InputStream qui servira à extraire du
socket un flux d'octets vers le programme.

socket.getOutputStream() : on obtient ainsi une instance de la classe OutputStream qui servira à extraire du
programme un flux d'octets vers le socket.

sortie.println("*+*+*+*+"); : on envoie au serveur le mot *+*+*+*+ pour indiquer la fin du texte.

System.out.println(entree.readLine()); : cette instruction permet de recevoir et d'écrire à l'écran le nombre de


mots du texte.

socket.close(); : ne pas oublier cette instruction.


new StringTokenizer(texte, " ,.;:_-+*/\\.;\"'{}()=><\t!\n") : on instancie la classe StringTokenizer en
indiquant dans la chaîne du second argument les caractères devant servir de séparateurs.

standardiste = new ServerSocket(portEcoute); : la classe ServerSocket modélise un socket d'écoute qui


sert à recueillir les requêtes de connexion de clients.

socket = standardiste.accept(); : la méthode accept de la classe ServerSocket fait attendre le thread


courant à l'écoute d'une requête de connexion et, quand une telle demande se présente, renvoie un socket,
instance de la classe Socket, liée à un nouveau port de la machine, qui servira pour la communication avec le
nouveau client.
Communication en mode connecté modèle poste à poste

Ce modèle est illustré avec un exemple plus simple. Il s'agit de faire un programme pour établir une
communication en mode connecté entre deux machines ; deux personnes peuvent communiquer d'une machine à
une autre ; chacune des personnes peut exécuter le programme qui nous sert d'exemple en indiquant sur la ligne
de commande le nom de la machine avec laquelle il souhaite communiquer. Le programme fonctionne quelle que
soit celle des deux personnes qui lance en premier le programme mais nécessite que chaque personne sache que
l'autre souhaite communiquer avec elle à partir d'une certaine machine dont elle doit indiquer le nom. Lorsque la
connexion est établie, les personnes envoient et reçoivent les messages à l'aide de la fenêtre d'exécution. Les
méthodes Java utilisées sont les mêmes que dans le paragraphe précédent. Dans un « vrai » programme, il
faudrait traiter plus en détail les exceptions

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;

class Recepteur extends Thread {


Socket socket;

Recepteur(Socket socket) {
this.socket = socket;
}

public void run(){


try {
BufferedReader entree = new BufferedReader(new
InputStreamReader(socket.getInputStream()));
while(true) {
String texte = entree.readLine();
if (texte == null) break;
if (texte == null) System.exit(0);
System.out.println(texte);
}
entree.close();
}
catch(IOException exc) {
exc.printStackTrace();
}
}
}

class Emetteur extends Thread{


Socket socket;

Emetteur(Socket socket) throws IOException {


this.socket = socket;
}

public void run(){


try {
BufferedReader entree = new BufferedReader(new
InputStreamReader(System.in));
PrintStream sortie = new PrintStream(socket.getOutputStream());
while(true) {
String texte = entree.readLine();
if (texte == null) break;
sortie.println(texte);
}
sortie.close();
}
catch(IOException exc) {
exc.printStackTrace();
}
}
}

class Connecte {
public static void main(String[] arg) throws IOException {
int portEcouteServeur = 40302;
ServerSocket standardiste;
Socket socket;

try {
socket = new Socket(arg[0],portEcouteServeur);
}
catch(IOException exc) {
standardiste = new ServerSocket(portEcouteServeur);
socket = standardiste.accept();
}
(new Recepteur(socket)).start();
(new Emetteur(socket)).start();
}
}

Vous pouvez accéder à :


• La classe Connecte.java

texte == null : cela se produit si la communication a été coupée.

socket = new Socket(arg[0],portEcouteServeur); : cette demande de connexion échoue si


l'utilisateur de la machine indiquée par arg[0] n'a pas encore lancé le programme ; le flux d'exécution se
retrouve dans le bloc catch qui crée une instance de ServerSocket pour attendre les demandes de
connexion ; l'autre utilisateur devrait faire ensuite sa demande de connexion, cette seconde demande de
connexion aboutira alors.
Communication en multicast

Le concept de multicast est apparu il y a une quinzaine d'années. Nous n'expliquerons pas son fonctionnement
technique, mais uniquement son utilisation. Il permet à un groupe de machines de communiquer de telle sorte
que chaque message envoyé soit reçu par l'ensemble des membres du groupe. Le groupe utilise pour
communiquer une adresse IP virtuelle ; cette adresse doit nécessairement être comprise entre 224.0.1.0 et
239.255.255.255 et n'est pas l'adresse d'une machine réelle ; on choisit également un numéro de port compris
entre 1024 et 65535 qui sera le numéro de port sur lequel les messages seront attendus.
Pour recevoir les messages du groupe, il faut, dans le programme Java, disposer d'un socket relié au port retenu ;
on utilise pour cela la classe MulticastSocket qui hérite de la classe DatagramSocket ; il faut alors que
ce socket indique qu'il joint le groupe, avec la méthode adéquate, en indiquant l'adresse IP virtuelle de ce groupe.
Après cela, il suffit d'attendre des datagrammes par le socket, de la même façon que pour toute réception de
datagramme.
Pour envoyer des messages au groupe, il faut construire un datagramme en indiquant l'adresse IP virtuelle et en
spécifiant le port de réception. Par ailleurs, il faut préciser au socket qui servira à l'émission (qui peut être le
même que celui utilisé pour la réception) le TTL (Time To Live) ; celui-ci indique la portée de l'émission et
correspond au nombre de routeurs multicast que le datagramme pourra traverser ; un TTL de 1 ne permet que
d'atteindre les machines du réseau local, avec un TTL de 15, on atteint l'ensemble du site, pour un TTL de 31, la
région, pour un TTL de 47 le pays, pour un TTL de 63 le continent et pour un TTL de 127 le monde entier ; par
défaut, le TTL vaut 1.
Nous donnons ci-dessous un exemple de programme qui permet de communiquer entre un groupe de personnes
disposant de différentes machines. Les messages sont envoyés et reçus dans la fenêtre d'exécution. Le
programme est lancé en indiquant sur la ligne de commande un identificateur qui permet aux membres du groupe
de savoir qui est l'auteur de l'envoi.

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;

class Recepteur extends Thread {


InetAddress groupeIP;
int port;
String nom;
MulticastSocket socketReception;

Recepteur(InetAddress groupeIP, int port, String nom) throws Exception {


this.groupeIP = groupeIP;
this.port = port;
this.nom = nom;
socketReception = new MulticastSocket(port);
socketReception.joinGroup(groupeIP);
start();
}
public void run() {
DatagramPacket message;
byte[] contenuMessage;
String texte;
ByteArrayInputStream lecteur;

while(true) {
contenuMessage = new byte[1024];
message = new DatagramPacket(contenuMessage, contenuMessage.length);
try {
socketReception.receive(message);
texte = (new DataInputStream(new
ByteArrayInputStream(contenuMessage))).readUTF();
if (!texte.startsWith(nom)) continue;
System.out.println(texte);
}
catch(Exception exc) {
System.out.println(exc);
}
}
}
}

class Emetteur extends Thread{


InetAddress groupeIP;
int port;
MulticastSocket socketEmission;
String nom;

Emetteur(InetAddress groupeIP, int port, String nom) throws Exception {


this.groupeIP = groupeIP;
this.port = port;
this.nom = nom;
socketEmission = new MulticastSocket();
socketEmission.setTimeToLive(15); // pour un site
start();
}

public void run() {


BufferedReader entreeClavier;

try {
entreeClavier = new BufferedReader(new InputStreamReader(System.in));
while(true) {
String texte = entreeClavier.readLine();
emettre(texte);
}
}
catch (Exception exc) {
System.out.println(exc);
}
}

void emettre(String texte) throws Exception {


byte[] contenuMessage;
DatagramPacket message;

ByteArrayOutputStream sortie = new ByteArrayOutputStream();


texte = nom + " : " + texte ;
(new DataOutputStream(sortie)).writeUTF(texte);
contenuMessage = sortie.toByteArray();
message = new DatagramPacket(contenuMessage, contenuMessage.length,
groupeIP, port);
socketEmission.send(message);
}
}

class Multicast {
public static void main(String[] arg) throws Exception{
String nom = arg[0];
InetAddress groupeIP = InetAddress.getByName("239.255.80.84");
int port = 8084;
new Emetteur(groupeIP, port, nom);
new Recepteur(groupeIP, port, nom);
}
}

Vous pouvez accéder à :


• La classe Multicast.java

socketReception = new MulticastSocket(port); : on demande de construire un socket lié au


port indiqué par l'argument.

socketReception.joinGroup(groupeIP); : par cette instruction, on demande d'être ajouté au groupe


des personnes recevant les messages du groupe dont l'adresse virtuelle correspond à groupeIP. L'instruction
leaveGroup permettrait de quitter le groupe.

if (!texte.startsWith(nom)) continue; : sans cette instruction, une personne envoyant un


message recevrait son propre message, ce qu'on a souhaité éviter ici.

socketEmission.setTimeToLive(15); : en l'absence de cette instruction, le groupe serait restreint au


réseau local.
Communication avec RMI

Le système RMI permet qu'un objet d'un programme s'exécutant dans une certaine machine virtuelle puisse
invoquer une méthode d'un objet d'un programme tournant dans une autre machine virtuelle distante ; autrement
dit, il permet de faire communiquer des objets distants.
Nous allons expliciter un exemple, fondé sur un modèle client-serveur, dont l'objectif est qu'un client puisse
demander au serveur de trier un tableau d'Object par un appel à une méthode du serveur. On définit plusieurs
classes et une interface.

Une interface de communication


L'interface Trieur donne le prototype d'une méthode servant à trier un tableau de Comparable<Object> (c'est-
à-dire d'instances d'une classe implémentant l'interface java.lang.Comparable<Object>) ; elle étend l'interface
Remote du paquetage java.rmi. Une interface telle que notre interface Trieur existe toujours lorsqu'on fait
communiquer deux programmes par RMI : elle contient les prototypes de toutes les méthodes qui pourront être
invoquées à distance. Nous l'appellerons interface de communication.
public interface Trieur extends java.rmi.Remote {
public Comparable[] trier(Comparable[] tableau) throws java.rmi.RemoteException;
}

Côté serveur

La classe ServeurTri implémente l'interface Trieur (et donc aussi l'interface


Remote) ; elle est destinée à trier à distance des tableaux de Comparable ; elle étend la
classe java.rmi.server.UnicastRemoteObject. Cette dernière classe apporte les
fonctionnalités nécessaires pour effectuer une communication d'un site à un autre fondée
sur le système de communication par sockets de RMI. La méthode main de la classe
ServeurTri reçoit éventuellement, en argument, un numéro de port. Le constructeur de la
classe ServeurTri fait appel au constructeur de sa superclasse ; celui-ci exporte
l'instance créée, ce qui signifie qu'il la met à l'écoute d'appels distants d'un éventuel
client.

import java.rmi.RMISecurityManager;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;

@SuppressWarnings("serial")
public class ServeurTri extends UnicastRemoteObject implements Trieur {
public ServeurTri() throws RemoteException {}

public Comparable[] trier(Comparable[] tableau) {


System.out.println("je trie");
java.util.Arrays.sort(tableau);
return tableau;
}

public static void main(String[] arg) {


String port;

if (arg.length == 0) port = "";


else port = arg[0];
if (System.getSecurityManager() == null)
System.setSecurityManager(new RMISecurityManager());
try {
String nom = "trieur";
Trieur serveurTri = new ServeurTri();
Registry registry = LocateRegistry.getRegistry();
registry.rebind(nom, serveurTri);
System.out.println("Enregistrement du trieur ");
}
catch (RemoteException e) {e.printStackTrace();
}
}
}

La classe Eleve modélise un élève, ne possède qu'un attribut qui est une note
(dans la pratique, un élève aurait sans doute aussi un nom) et implémente l'interface
Comparablee<Object> ; elle implémente aussi l'interface Serializable.

public class Eleve implements Comparable, java.io.Serializable {


private int note;

public Eleve(int note) {


this.note = note;
}

public int getNote() {


return note;
}

public void setNote(int note) {


this.note = note;
}

public int compareTo(Object objet) {


Eleve autreEleve = (Eleve)objet;
if (note < autreEleve.note) return -1;
else if (note == autreEleve.note) return 0;
else return 1;
}
}

La classe ClientTri permet de trier des élèves selon leur note ; on imagine que la
machine sur laquelle s'exécute la méthode main de cette classe dispose de peu de puissance
de calcul et qu'en conséquence, on préfegrave;re demander à un serveur de type ServeurTri
d'effectuer le tri ; il faudra transmettre à la méthode de tri invoquée à distance un
tableau contenant les élèves ; c'est pour cette raison que la classe Eleve doit
implémenter l'interface Serializable. La méthode main de cette classe reçoit en argument
le nom de la machine sur lequel le programme serveur s'exécute suivi éventuellement du
symbole "'" et du même numéro de port que celui indiqué par le programme serveur.

import java.rmi.NotBoundException;
import java.rmi.NotBoundException;
import java.rmi.RMISecurityManager;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.Random;

public class ClientTri {


public static void main(String[] arg) {
if (System.getSecurityManager() == null)
System.setSecurityManager(new RMISecurityManager());
try {
String nom = "trieur";
Registry registry = LocateRegistry.getRegistry(arg[0]);
Trieur serveurTri = (Trieur) registry.lookup(nom);

Random alea = new Random();


int nbEleves = 10;
Eleve[] eleves = new Eleve[nbEleves];
int i;

for (i = 0; i < nbEleves; i++) {


eleves[i] = new Eleve(Math.abs(alea.nextInt())%21);
System.out.print(eleves[i].getNote ()+ " ");
}
System.out.println();
eleves = (Eleve[]) serveurTri .trier(eleves);
for (Eleve eleve : eleves) System.out.print(eleve.getNote() + "
");
System.out.println();
}
catch (NotBoundException e) {
System.out.println("le nom du programme serveur " + "n'est pas
enregistre");
}
catch (RemoteException e) {
System.out.println("Probleme de connexion a distance : " +
e.getMessage());
}
}
}

Lorsqu'on lance le serveur, on voit s'afficher :


Enregistrement du trieur
Lorsqu'on lance un client, le serveur ajoute :
je trie
pendant que le client affiche par exemple :
8 18 14 18 16 17 10 5 19 11
5 8 10 11 14 16 17 18 18 19

Dans notre exemple, le client fait appel aux méthodes du serveur, et l'inverse ne s'y fait
pas ; si on souhaite avoir des appels distants bidirectionnels, il faut que le client
(resp. un objet du client) implémente aussi une interface qui étend l'interface Remote et
qui déclare les méthodes qui pourront être appelées par le serveur et par ailleurs que le
client (resp. l'objet du client) soit exporté grâce à la méthode statique exportObject de
la classe UnicastRemoteObject. Afin de pouvoir être (resp. que l'objet puisse être)
invoqué par le serveur, le client peut alors, à l'aide d'une méthode distante adéquate,
indiquer au serveur sa référence (resp. la référence de l'objet).

Nous allons voir comment mettre en œuvre l'application décrite dans les paragraphes
précédents.

On compile d'abord les différentes classes.

Pour exécuter les deux programmes, serveur et client, il est nécessaire de spécifier un
fichier de permissions par une option au moment de l'exécution. Nous avons choisi ici de
donner toute permission en utilisant le fichier de permissions ci-dessous :

grant codebase "file:/infres/mic2/charon/public_html/coursJava/reseau/RMITri/" {


permission java.security.AllPermission;
};

Nous supposerons que les répertoires d'où on lance d'une part le serveur et d'autre part
le client possèdent chacun un exemplaire du fichier java.permissions.

Avant de lancer le serveur, il faut lancer le programme d'enregistrement sur la machine où


tournera le programme serveur RMI, par la commande :
• rmiregistry sous Solaris ou Unix,
• start rmiregistry sous Microsoft Windows

Grâce à l'option -D qui permet de spécifier une propriété système, on indiquera, le


fichier de permissions par la propriété java.security.policy ;

On lance alors le serveur, par exemple sur la machine zadig.enst.fr où tourne déjà
rmiregistry, par la commande :
java -Djava.security.policy=java.permissions Serveurtri

On lance le programme client d'une machine quelconquemais d'un répertoire où se trouvent


tous le bytecode nécessaire ainsi aus le fin=chier de permissions, par la commande :
java -Djava.security.policy=java.permissions ClientTri zadig.enst.fr

Par cette façon de faire, il faut que le serveur dispose aussi de la classe Eleve,
puisqu'il en a besoin pour trier le tableau des élèves.

D'autres propriétés, java.rmi.server.codebase et java.rmi.server.hostname peuvent être


spécifiées.

Les différents fichiers


• Le fichier Trieur.java
• Le fichier ServeurTri.java
• Le fichier Eleve.java
• Le fichier ClientTri.java
• Le fichier java.permissions

On peut aussi consulter : ce tutorial pour plus de détails.

ServeurTri() : ce constructeur fait appel au constructeur de la classe UnicastRemoteObject


(qui est la superclasse de la classe ServeurTri) qui met l'objet serveur « à l'écoute »
d'éventuels appels distants sur un port anonyme de la machine.

Comparable[] trier(Comparable[] tableau) : il s'agit de la méthode qui peut être invoquée


à distance ; lors d'un appel distant, le tableau ne va pas être passé par référence mais
par copie du contenu ; il est donc obligatoire de renvoyer le tableau résultant du tri.

System.setSecurityManager(new RMISecurityManager()) : il est nécessaire d'utiliser un


gestionnaire de sécurité.

Registry registry = LocateRegistry.getRegistry();


registry.rebind(nom, serveurTri); :
ces deux lignes servent à enregistrer l'objet serveur sur le registre RMI, objet Java qui
permet d'associer un objet (ou plus précisément la référence d'un objet) à une chaîne de
caractères ; ce registre est géré par un « programme d'enregistrement RMI » qui doit
tourner sur la machine serveur ; l'objet serveur sera ici associé à la chaîne de
caractères indiquée par le premier argument de la méthode rebind ; cet enregistrement
permet à un programme client d'obtenir (nous verrons comment) un accàs à l'objet trieur en
indiquant la chaîne de caractères qui lui est associée.

Trieur serveurTri = (Trieur) registry.lookup(nom) : le nom indiqué en argument doit ê


celui sous lequel on a enregistré (par la méthode rebind) le programme serveur (dans le
registre RMI de la machine distante),
eleves = (Eleve[])serveur.trier(eleves); : il s'agit de l'appel à la méthode distante de
tri. Il est nécessaire de récupérer le tableau renvoyé par la méthode car le tableau
initial n'est pas trié puisqu'il a été transmis par recopie de son contenu.
Java RMI : Suite

Nous présentons ici un deuxième programme utilisant RMI qui fait la même chose que le précédent, mais le
serveur peut faire différentes tâches. La tâche que l'on souhaite exécuter sur la machine serveur est en fait définie
chez le client qui demande à exécuter cette tâche sur la machine serveur.
Il y a différents interfaces ou classes :
• L'interface InterfaceServeur qui définit une méthode executerTache qu'implémente le
serveur.
• L'interface Tache, qui définit une méthode nommée ici executer et qu'implémentera une classe où sera
définie la tâche à exécuter par le serveur.
• La classe Serveur implémente l'interface InterfaceServeur et définit le programme du serveur.
• La classe Eleve est identique à celle de l'exemple précédent.
• La classe TriEleves implémente l'interface Tache et définit la méthode exécuter qui demande à trier
les élèves ; c'est cette méthode qui sera exécutée sur la machine serveur.
• La classe ClientTri sert à construire une liste d'élèves, construit un objet de type Tache qui pourra
trier les élèves, et fait appel à la machine distante pour effectuer la tâche.
• Le fichier java.permissions.
On compile et on exécute comme pour le programme précédent (on remplace juste ServeurTri par
Serveur)

Vous aimerez peut-être aussi