:: Enseignements :: Master :: M1 :: 2013-2014 :: Programmation d'applications réseaux ::
[LOGO]

Adresses IP et UDP


Exercice 1 - Adresses IP, client et serveur UDP

Copier-coller le code ci-dessous dans un nouveau projet Java eclipse.

  1. Quelles informations sont utilisées lors de l'exécution de ce programme sans arguments?
  2. Utiliser ce programme avec un argument sur la ligne de commande pour afficher les noms et adresses IP des machines suivantes :
    • la machine connue sous le nom www.u-pem.fr ;
    • la machine dont l'adresse IP est 193.55.63.80 ;
    • la machine connue sous le nom igm.univ-mlv.fr ;
    • la machine dont l'adresse IP est 300.55.63.80.
    Indiquer, pour chacune de ces machines, s'il y a résolution de nom.
  3. Modifier le code pour connaitre le nom (HostName) et le nom canonique (CanonicalHostName) de chaque machine ci-dessus. Tester l'affichage par défaut (toString()) avant et après la recherche du nom et du nom canonique: que peut on en déduire?
  4. Tester le programme ci-dessus plusieurs fois consécutives avec le nom www.google.com. Que peut on en déduire? Compléter le programme pour que toutes les adresses associées au nom passé en argument sur la ligne de commande soient affichées.

Exercice 2 - Client echo UDP

Écrire un client UDP permettant d'interroger le démon Echo (RFC 862) qui est accessible sur le port 7 d'une machine. Vous pouvez tester ce service depuis les machines des salles de TP (*) en lancant la commande
nc -u gaspard 7
Contrairement à nc, on attend de votre client qu'il soit verbeux et donne de nombreuses informations. Par exemple :
$ java fr.upem.net.udp.EchoUDPClient gaspard 7 "Mon beau message, roi des machines"
socket locale attachée à l'adresse 0.0.0.0/0.0.0.0:1044
34 octets émis vers gaspard/193.55.63.81:7
capacité de la zone de stockage : 44
34 octets recus
contenant : Mon beau message, roi des machines
provenant de : gaspard.univ-mlv.fr/193.55.63.20:7
Vous utiliserez pour cela les classes java.net.DatagramSocket et java.net.DatagramPacket. Vous utiliserez le même DatagramPacket pour la réception et pour l'envoi. Puisqu'on utilise le protocole Echo, on utilisera l'encodage par défaut de la machine cliente lors de l'envoi et de la réception des messages texte.

(*) si vous travaillez sur une autre machine qui ne peut pas accéder à gaspard, vous pouvez utiliser le serveur UDPUpperCaseServer.jar en le démarrant dans un autre terminal avec:
java -jar UDPUpperCaseServer.jar 7777
et en attaquant ce serveur sur le port 7777 de localhost. Par exemple:
nc -u localhost 7777

Exercice 3 - Serveur echo UDP

Écrire maintenant un serveur EchoUDPServer très simple qui va pouvoir jouer le rôle du service fourni par la machine gaspard. Ce serveur doit contenir une méthode main(), qui accepte sur la ligne de commande un entier représentant le port d'écoute, un constructeur, qui accepte ce numéro de port en paramètre et crée et attache une DatagramSocket sur ce port, et enfin une méthode launch() sans argument qui place le serveur en attente de réception de paquet UDP en provenance de clients; lorsqu'un paquet est reçu, le serveur se contente de renvoyer son contenu à l'expéditeur, et recommance ainsi de suite en boucle.

Faut-il fixer une taille maximale des données que le serveur peut reçevoir dans un paquet et pourquoi?

Testez votre serveur d'une part avec nc et d'autre part avec le client EchoUDPClient précédemment écrit; quel est l'encodage de caractère utilisé par les clients? Par le serveur?

Exercice 4 - Respecte mon encodage

On souhaite écrire un client UDP permettant d'interroger un serveur de mise en majuscule de chaînes de caractères. Pour illustrer l'importance de l'encodage des chaînes de caractères, nous allons utiliser un serveur qui est paramétré pour prendre en charge un encodage précis. Pour ce faire récupérez le serveur UDPUpperCaseServer.jar et lancez le sur votre machine avec un encodage différent de celui par défaut (e.g. Latin1 si vous êtes sous UTF-8 et inversement).

Vous pouvez tester avec netcat (nc) que si l'encodage de la machine cliente et celui précisé au lancement du serveur correspondent, la mise en majuscule s'effectue sans problème, mais que dans le cas où ils ne correspondent pas la mise en majuscule ne s'effectue pas (e.g. pour les lettres accentuées).

Par exemple, si on lance:
$ java -jar UDPupperCaseServer.jar 4545 Latin1
alors, dans un terminal configurés en UTF-8, on devrait obtenir ces problèmes:
$ nc -u localhost 4545
bla
BLA
éï€
éï‚�
Tandis que si le serveur est lancé par:
$ java -jar UDPupperCaseServer.jar 4545 UTF-8
alors la mise en majuscule fonctionne:
$ nc -u localhost 4545
bla
BLA
éï€
ÉÏ€

Écrire un client UDP permettant d'interroger le serveur de mise en majuscule lancé sur un port donné d'une machine host. On attend un fonctionnement du type :
        $ java fr.upem.net.udp.EncodedUDPClient 10.2.5.10 7777 "Autant arrêter le Java si c'est pour ça!" Latin1
        socket locale attachée :
        à l'adresse 0.0.0.0/0.0.0.0 au port 1044
        34 octets émis vers 10.2.5.10/10.2.5.10
        capacité de la zone de stockage : 44
        34 octets recus

        contenant : AUTANT ARRÊTER LE JAVA SI C'EST POUR ÇA!
        provenant : de 10.2.5.10/10.2.5.10:7777
   

Transformez votre code pour lire les chaînes de caractères au clavier et permettre plusieurs mises en majuscules.

Le jar accepte un 3ème argument (optionnel) sur la ligne de commande, qui correspond à la fiabilité (1, par défaut, si on le passe à 0.5, il jettera un paquet sur 2). Modifiez votre client de manière à afficher
Le serveur n'a pas répondu
si le serveur n'a pas répondu en moins d'une seconde.

Exercice 5 - Manipulation de bits et de bytes d'adresses

En général, l'ordre de transmission des octets sur le réseau, quelque fois appelé "network order", est l'ordre Big Endian: l'octet de poids fort de l'entier enregistré à l'adresse mémoire la plus petite. Voir Endianness sur Wikipedia pour plus d'infos.

Par ailleurs, en Java, tous les types primitifs numériques sont signés, ce qui signifie par exemple que l'octet (byte) dont tous les bits sont à 1 vaut -1 et non 255 (voir encore Wikipédia)!

Autre problème: lorsqu'on fait une opération sur entier d'un type primitif plus petit que int, Java réalise systématiquement une promotion entière, c'est à dire qu'il convertit cette valeur entière en un int (32 bits) avant de réaliser l'opération sur cette valeur. Ainsi par exemple, si b est de type byte et vaut -1 (il est donc représenté sur un octet constitué de 8 bits à 1), alors pour réaliser l'opération b + 1, la valeur de b va être promue en un entier de type int. Cet int sera donc codé sur 32 bits, et ils seront tous à 1 pour que la valeur de l'entier soit toujours -1 (c'est l'extension du bit de signe). Le résultat de l'addition sera donc -1 + 1 = 0 mais sera stockée dans un int sur 32 bits, et donc le code ci-dessous
byte b = -1;
b = b + 1;            // erreur de compilation: Type mismatch: cannot convert int to byte
ne compile pas. Il faudra donc caster le résultat en byte pour pouvoir réaliser l'affectation:
b = (byte) (b + 1);   // OK

Si l'on s'intéresse à la valeur "non signée" représentée dans un byte (pour obtenir par exemple 255 si tous les bits d'un octet sont à 1), il faut appliquer un masque qui permettra de "cacher" l'effet de l'extension du bit de signe. Ce masque fait un ET bit à bit avec une valeur dont la représentation binaire vaut 0 sur les bits qu'on veut cacher et 1 sur les bits qu'on veut garder. Exemple (0xFF en Hexa vaut 11111111 en binaire et 255 en décimal):
byte b = -1;
System.out.println(b);          // affiche: -1
System.out.println(b & 0xFF);   // affiche: 255

  1. Sachant que les long sont codés sur 8 octets, implémenter deux méthodes de conversion void longToByteArray(long value, byte[] array) et long byteArrayToLong(byte[] array) permettant les conversions entre long et tableau de byte en Big Endian. Tester ces méthodes en les appelant l'une après l'autre et en vérifant que vous récupérez bien l'entier orginal, par exemple en complétant cette classe:
    package fr.upem.net.udp;
    
    /**
     * This utility class offers conversion static methods between primitive types 
     * and byte arrays for network transport. 
     */
    public class Converter {
    
      /**
       * Returns the long primitive value being represented by the argument byte 
       * array in network order (big endian). The lowest index in the array 
       * contains the most significant byte of the long value.
       * @param array the byte array representing a long value in network order.
       * @return the long value represented by the byte array.
       * @throws IllegalArgumentException if the byte array size is incompatible 
       * with the long representation. 
       */
      public static long byteArrayToLong(byte[] array) throws IllegalArgumentException {
    	// TODO
      }
    
      /**
       * Assigns in the specified array the bytes of the specified long 
       * primitive value in network order representation (big endian). 
       * The most significant byte in the long value is stored at the lowest 
       * index in the array.
       * @param value the primitive long value.
       * @param array the byte array representing a long value in network order.
       * @throws IllegalArgumentException if the byte array size is incompatible with 
       * the long representation. 
       */
      public static void longToByteArray(long value, byte[] array) {
    	// TODO	
      }
      
      public static void main(String[] args) {
        long l = Long.parseLong(args[0]);
        byte[] array = new byte[8];
        longToByteArray(l, array);
        long l2 = byteArrayToLong(array);
        System.out.println((l==l2));
      }
    }