Systèmes distribués
Ingéniérie mathématique et informatique - S3 Je matin


[Introduction à Java]


Séance 1 : Protocoles IP

Internet, adresses et noms

L'Internet étant une interconnexion de réseaux, toute machine raccordée à l'Internet est d'abord sur un réseau. A ce titre, elle est désignée par une adresse IP, qui spécifie le réseau, et à l'intérieur de ce réseau, la machine. Ces adresses sont des objets de la classe InetAddress du paquet java.net. Les méthodes de cette classe peuvent déclencher l'exception UnknownHostException. L'adresse IP de la machine locale s'obtient par la fonction getLocalHost() de la classe InetAddress.

import java.net.*;

class AdresseLocale {
  public static void main(String[] args)
    throws UnknownHostException {
    InetAddress adresse = InetAddress.getLocalHost();
    System.out.println("Adresse IP locale : " + adresse);
  }
}

Une adresse IP se compose actuellement de 4 octets ; elle se décompose en numéro de réseau et numéro de machine. L'adresse du serveur Web de l'École est 195.221.195.17, c'est la machine 17 dans le réseau 195.221.195. On peut obtenir ces quatre octets sous la forme d'un tableau d'octets retourné par la méthode getAddress. Comme Java ne connait que des entiers signés (pour les octets, compris entre -127 et 127), il faut faire une translation si on veut les afficher comme des entiers positifs, ou utiliser la méthode getHostAddress() :

    System.out.println(adresse.getHostAddress());
    

Plutôt que d'utiliser directement ces adresses IP, il est préférable de désigner les machines par des noms, plus faciles à retenir, et d'organiser ces noms de façon plus signifiante pour l'utilisateur. Par l'exemple, le nom www.enpc.fr indique immédiatement que www est dans le domaine enpc de l'Ecole, qui est dans le domaine français fr. L'association d'une adresse IP à un nom de machine est obtenue grâce au service des noms ou DNS. La machine locale est désignée par le nom localhost. On obtient l'adresse à partir d'un nom de machine ou d'un numéro IP par la méthode de classe getByName(String).

InetAddress adresse1 = InetAddress.getByName("www.enpc.fr");
InetAddress adresse2 = InetAddress.getByName("195.221.193.72");
    

Protocoles et organisation en couches

Un service distribué est mis en oeuvre par un ensemble de programmes partageant un même protocole. Un protocole est un ensemble de règles (format de données, de dialogues, etc) permettant de réaliser un service. Un protocole n'est pas un programme, mais il est implémenté par une bibliothèque de fonctions.

Les protocoles et les services qu'ils offrent sont organisés en couches. L'idée est qu'une couche utilise des services fournis par la couche inférieure et offre des services à la couche supérieure. On distingue, par niveaux décroissants, les services d'application (les seuls qui aient un sens pour l'utilisateur), les services de transport, les services d'interconnexion des réseaux, et les services de liaison de données.

coucheprotocoles
ApplicationTelnet, FTP, NNTP, SMTP, HTTP
TransportTCP, UDP
RéseauIP (ICMP)
LiaisonEthernet

Services d'application

Les services d'application fonctionnent généralement sur le mode client-serveur. Clients et serveurs sont des processus (c'est-à-dire des exécutions de programmes). Un service d'application se compose ainsi de deux processus, chacun sur une machine et communicant par l'intermédiaire d'un protocole.

Le processus client prend l'initiative d'une communication destinée au processus serveur et lui transmet une requête. Ce processus serveur écoute en permanence les requêtes : il peut leur répondre lui-même, ou créer un autre processus serveur qu'il charge de cette réponse. Les serveurs sont souvent des démons (processus qui s'exécutent en tâche de fond) : on trouve par exemple les programmes ftpd, telnetd et httpd, réalisant les services FTP, Telnet et HTTP sous Unix. Pour ces mêmes applications, on peut trouver côté client les programmes ftp, telnet, netscape.

Dans le cas du Web, le protocole est HTTP, «HyperText Transfer Protocol» [RFC 2068], les programmes clients sont des navigateurs, comme Netscape, Explorer ou Lynx ; le programme serveur est généralement httpd (sous Unix).

Ports et adresses de transport

Sur une machine, il peut y avoir plusieurs serveurs coexistants (par exemple un serveur FTP et un serveur HTTP). Il faut donc dire quel serveur doit traiter la requête émise par le client. On utilise pour cela un numéro de port (entier positif 16 bits) attribué par les instances de l'Internet, connu de tous : 23 pour Telnet, 21 pour FTP, 80 pour HTTP, etc. Les ports compris entre 1 et 1023 sont réservés, sous Unix, au super-utilisateur. La donnée d'une machine (adresse IP ou nom) et d'un numéro de port détermine une adresse de transport.

Il peut aussi y avoir plusieurs clients sur une machine, et même plusieurs clients requérant le même service d'application. Il faut donc que le serveur puisse distinguer entre ces différents clients pour leur répondre correctement. On utilise aussi un numéro de port côté client, mais cette fois-ci le numéro de port est généré de manière à assurer l'unicité.

Protocoles et services de transport

Un protocole de transport permet de réaliser un service d'application reliant deux processus, l'un serveur et l'autre client. Un service de transport supporte une communication bidirectionnelle de données entre les processus client et serveur, et est réalisé à l'initiative du client.

Une communication se fait en mode connecté s'il y a accord préalable entre l'émetteur et le récepteur sur l'établissement de la communication, avant que l'émission de données ne commence. Elle est en mode non-connecté sinon. La téléphonie fonctionne en mode connecté, la messagerie électronique en mode non-connecté. Le mode connecté assure une meilleure sécurité à l'acheminement, et permet de négocier la qualité du service (par exemple, pour déterminer un taux de compression). Mais la mise en place d'une connexion demande toujours un temps supplémentaire, ce qui peut être pénalisant si le message transmis est court. Le mode non connecté est plus rapide et moins sûr.

Il y a deux protocoles de transport dans l'Internet :

Une requête de transport est spécifiée par le protocole (TCP ou UDP) et les deux adresses de transport, du client et du serveur. Ce sera un quintuplet, par exemple (TCP, 192.54.211.35, 6492; 192.54.211.74, 21) dans le cas d'une connexion Telnet.

Datagrammes UDP

Le protocole UDP est le plus simple, ne permettant pas de reconstituer l'ordre d'envoi des messages, donc plus efficace, mais moins fiable, puisqu'il n'est pas doté d'accusé de réception. Les données sont placées dans un datagramme UDP, muni d'un en-tête comportant les numéros de port d'origine et de destination, la taille du datagramme et une somme de contrôle ; ce datagramme est lui-même placé dans un datagramme IP (ou paquet IP), muni d'un en-tête comportant entre autre les adresses IP d'émission et de réception. À cause de ces deux en-têtes, la taille des données est limitée à 65 507 octets.

Les datagrammes UDP sont implémentés en Java par des objets de la classe DatagramPacket ; les données sont contenues dans un tableau d'octets. Voici d'abord la construction d'un datagramme destiné à une machine distante, à partir des données et de l'adresse de transport distante :

String s = "Des données, ...";
byte[] donnees = s.getBytes();
InetAddress adresse = InetAddress.getByName("www.enpc.fr");
final int PORT = 1314;
DatagramPacket paquet =
  new DatagramPacket(donnees, donnees.length, adresse, PORT);
En réception (côté serveur), il faut construire un datagramme à l'aide d'un tableau d'octets qui recevra les données :
byte[] donnees = new byte[8096];;
DatagramPacket d =
  new DatagramPacket(donnees, donnees.length);

Les méthodes getData, getLength, getAddress, getPort permettent d'obtenir le tableau d'octets des données, la taille des données (en octets) et l'adresse de transport distante. Dans le cas d'un datagramme de réception, ces méthodes ne devront être invoquées qu'après réception.

Sockets UDP

Les datagrammes sont émis et reçus par l'intermédiaire d'un objet de la classe DatagramSocket. Ses constructeurs peuvent déclencher l'exception SocketException. En émission (côté client), on utilise un port anonyme pour construire le DatagramSocket, puis la méthode send :

String s = "Des données, ...";
byte[] donnees = s.getBytes();
InetAddress adresse = InetAddress.getByName("www.enpc.fr");
final int PORT = 1314;
DatagramPacket paquet =
  new DatagramPacket(donnees, donnees.length, adresse, PORT);
DatagramSocket client = new DatagramSocket();
client.send(paquet);

En réception, le port doit être passé en argument au constructeur DatagramSocket(int), et la méthode receive remplit le tableau d'octets du paquet :

final int PORT = 1314;
DatagramSocket serveur = new DatagramSocket(PORT);
byte[] donnees = new byte[8096];;
DatagramPacket paquet =
  new DatagramPacket(donnees, donnees.length);
serveur.receive(paquet);

Client UDP

Le programme suivant communique avec le port 1315 d'une machine éventuellement spécifiée sur la ligne de commande, à l'aide d'un DatagramSocket. À chaque ligne entrée par l'utilisateur sur le clavier, constituant la requête, il prépare un paquet contenant la requête, l'envoie sur ce DatagramSocket, puis reçoit un paquet contenant la réponse sur ce même DatagramSocket et affiche cette réponse à l'écran.

import java.io.*;
import java.net.*;

class ClientEchoUDP {

  public static void main(String[] args) 
    throws UnknownHostException, IOException {
    
    String nomHote = args.length>0 ? args[0] : "localhost";
    InetAddress adresse = InetAddress.getByName(nomHote);
    final int PORT = 1315;
    String requete, reponse;
    DatagramPacket paquetRequete, paquetReponse;
    DatagramSocket client = new DatagramSocket();
    BufferedReader entree = 
      new BufferedReader(
        new InputStreamReader(System.in));
    byte[] donneesRequete, donneesReponse = new byte[8096];
      
    while (!(requete = entree.readLine()).equals(".")) {
      donneesRequete = requete.getBytes();
      paquetRequete =
	new DatagramPacket(donneesRequete, donneesRequete.length, 
			   adresse, PORT);
      paquetReponse =
	new DatagramPacket(donneesReponse, donneesReponse.length);
      client.send(paquetRequete);
      client.receive(paquetReponse);
      reponse = new String(paquetReponse.getData());
      System.out.println(paquetReponse.getAddress() + " : " + reponse);
    }
    client.close();
  }
}

Serveur « echo » UDP

Serveurs et clients se programment de façon assez symétrique. Le programme suivant crée un DatagramSocket en réception sur le port 1315. Dans une boucle infinie, il crée un paquet requête, le remplit, crée un paquet contenant la réponse et l'émet. Le paquet réponse contient les mêmes données que le paquet requête, ce qui est souhaité pour un serveur d'écho, et a pour adresse de destination l'adresse d'émission du paquet requête. Dans cet exemple, le même DatagramSocket sert aux communications avec tous les clients sans qu'il soit nécessaire.

import java.io.*;
import java.net.*;

class ServeurEchoUDP {

  public static void main(String[] args) 
    throws UnknownHostException, IOException {
    
    final int PORT = 1315;
    DatagramPacket paquetRequete, paquetReponse;
    DatagramSocket serveur = new DatagramSocket(PORT);
    byte[] donneesRequete = new byte[8096], donneesReponse;
      
    while (true) {
      paquetRequete =
	new DatagramPacket(donneesRequete, donneesRequete.length);
      serveur.receive(paquetRequete);
      paquetReponse =
	new DatagramPacket(paquetRequete.getData(), 
			   paquetRequete.getLength(), 
			   paquetRequete.getAddress(), 
			   paquetRequete.getPort());
      serveur.send(paquetReponse);
    }
  }
}

[Systèmes distribués] [TP]