Académique Documents
Professionnel Documents
Culture Documents
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
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
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);
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
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).
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);
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;
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()));
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.
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.
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;
Recepteur(Socket socket) {
this.socket = socket;
}
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();
}
}
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;
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);
}
}
}
}
try {
entreeClavier = new BufferedReader(new InputStreamReader(System.in));
while(true) {
String texte = entreeClavier.readLine();
emettre(texte);
}
}
catch (Exception exc) {
System.out.println(exc);
}
}
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);
}
}
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.
Côté serveur
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 {}
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.
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;
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.
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 :
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.
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
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.
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)