Fiabilisation d'une succession d'échanges UDP

Paquets identifiés envoyés et acquittés un par un

Comme dans le dernier exercice du TD précédent, on souhaite disposer d'un programme qui utilise les services d'un serveur de mise en majuscule pour réaliser les opérations suivantes:
  • lire un fichier texte ligne par ligne et stocker toutes ces lignes dans une liste,
  • envoyer ces lignes au serveur et stocker toutes les réponses dans une autre liste,
  • écrire ces lignes mises en majuscule par le serveur dans un fichier.
Cependant, on a constaté pour cela qu'il faut que le client ait un moyen d'identifier, lorsqu'il reçoit une réponse, à quelle requête elle correspond.

Pour cela, nous améliorons notre protocole UpperCase en IdUpperCase. Nous allons préfixer les données envoyées au serveur par une nouvelle information: un entier de type long, transmis en Big Endian, qui identifie de manière unique la requête et sa réponse: si une requête doit être ré-envoyée parce qu'elle n'a pas reçu de réponse, le même identifiant est utilisé. Chaque requête (émanant d'une nouvelle chaîne à mettre en majuscule) doit avoir un identifiant différent.

Protocole IdUpperCase
Ainsi, pour chaque chaîne de caractères à envoyer au serveur pour mise en majuscule, la représentation utilisée sera la suivante:
  • Un long en BigEndian identifiant la requête (chaque requête a un identifiant unique, qui est également utilisé dans la représentation de la réponse à cette requête)
  • Les octets de la chaîne encodée en UTF-8.
Les paquets échangés dans ce protocole ont une taille maximale de 1024 octets.

Nous vous fournissons un nouveau serveur ServerIdUpperCase.jar qui implémente ce protocole en utilisant l'identifiant, et vous pouvez réutiliser le proxy UDP UDPProxy.jar qui introduit des délais ou des pertes dans les échanges de paquets UDP.

$ java -jar ServerIdUpperCaseUDP.jar 4545 UTF-8
$ java -jar UDPProxy.jar 7777 localhost 4545 -no-swap 

En partant de la trame ClientIdUpperCaseUDPOneByOne.java, vous devez réaliser un client qui résoud le problème en réalisant l'envoi une par une des chaînes (dans notre trame, l'encodage utilisé est UTF-8).
Supposons par exemple qu'il y a deux chaînes à mettre en majuscule dans in-filename. Le client enverra la requête correspondant à la première ligne avec l'identifiant 0. Si aucune réponse avec l'identifiant 0 n'est reçue en timeout millisecondes, le client renvoie la requête. Le procédé est répété jusqu'à ce que une réponse avec l'identifiant 0 soit reçue. Le client passe ensuite à la seconde ligne qu'il enverra avec l'identifiant 1 selon le même procédé.
Comme dans le dernier exercice du TD précédent, la classe ClientIdUpperCaseUDPOneByOne.java prend en paramètre le fichier d'entrée (in-filename), fichier de sortie (out-filename), le délai de renvoi en millisecondes (timeout) et l'adresse d'un serveur IdUpperCase (hostname port).

Vous pouvez testez votre classe avec le fichier in.txt avec la ligne suivante:

$ java fr.upem.net.udp.ClientIdUpperCaseUDPOneByOne in.txt out.txt 300 localhost 7777

Paquets identifiés envoyés et acquittés un par un : l'effet boule de neige

Dans cet exercice, on revient sur un problème classique d'implémentation classique dans l'exercice 1.

Il est probable que la solution que vous avez proposé à l'exercice précédent un problème. Le problème potentiel vient du comportement en cas de réception d'une réponse avec un identifiant qui ne correspond pas à la question courante. Imaginons que l'on vienne de poser une question avec l'id 5 et qu'au bout de 50ms, votre code recoive un réponse avec l'id 4. Le problème survient si votre code renvoie tout de suite la question avec l'id 5 au lieu de continuer à attendre 250ms (timeout - 50ms déjà attendu) avec de renvoyer la question avec l'id 5.
Pour comprende le problème, il faut voir que chaque mauvaise réponse va engendrer une nouvelle question qui à son tour va engendrer une mauvaise réponse et ainsi de suite. On parle de l'effet boule de neige qu'on appelle le syndrome de l'apprenti sorcier.

Pensez bien à lancer au préalable le serveur et le proxy UDP comme montré ci-dessus.

Vérifiez bien que votre client ne renvoie pas la requête trop tôt, c'est à dire avant d'avoir attendu timeout millisecondes. Cela peut se produire dans votre code si une réponse (à une autre requête) avec un identifiant qui n'est pas celui attendu est reçue et que votre code renvoie immédiatement sa requête sans vérifier le temps écoulé depuis le dernier envoi.

Pour vous aidez à diagnostiquer le problème, nous vous fournissons un faux serveur ServerIdUpperCaseTestTimeout.jar. Au lieu de répondre normalement aux requêtes en mettant des chaînes en majuscule, il renvoie arbitrairement au premier client qui le contacte, toutes les 100 millisecondes, une réponse avec l'identifiant -1 ; il affiche par ailleurs le temps écoulé entre chaque paquet reçu.

Vous devez tester avec les lignes suivantes:

$ java -jar ServerIdUpperCaseUDPTestTimeout.jar 4545
$ java fr.upem.net.udp.ClientIdUpperCaseUDPOneByOne in.txt out.txt 300 localhost 4545
Si votre client respecte bien le timeout, vous devriez voir des temps très proches de timeout.
    Time since last receive: 301 ms
    Time since last receive: 301 ms
    Time since last receive: 301 ms
    Time since last receive: 301 ms
    Time since last receive: 300 ms
    Time since last receive: 301 ms
    ....

Si votre code ne passe pas le test, modifiez-le.

Paquets identifiés envoyés en rafale et acquités de manière asynchrone

Le temps d'attente systématique (synchrone) de la réponse après l'envoi d'un paquet n'est pas optimal. Il serait intéressant de pouvoir envoyer tous les paquets, sans se soucier de leur réponse ou de leur perte, et de recevoir les réponses de manière asynchrone, l'idée étant que l'on aura à ré-envoyer toutes les requêtes pour lesquelles aucune réponse n'a été retenue.

Cette solution semble bien plus performante, mais elle nécessite de mettre en place un mécanisme permettant de savoir, pour l'ensemble des paquets envoyés, lesquels ont reçu (ou pas) une réponse, et d'assurer que les réponses seront bien remises dans l'ordre où elles doivent être: dans notre exemple de client ci-dessus qui met en majuscule toutes les lignes d'un fichier, il faut bien entendu respecter l'ordre des lignes du fichier...

En partant de la trame ClientIdUpperCaseUDPBurst.java, vous devez réaliser un client qui résoud le problème en réalisant l'envoi en rafale des chaînes.
Cette classe prend les mêmes paramètres que la classe ClientIdUpperCaseUDPOneByOne.

> L'idée générale est la suivante:

Il faut donc faire communiquer ses deux threads au moyen d'une classe thread-safe que vous allez devoir créer. Cette classe devra permettre de:

Vous pouvez testez votre classe avec le fichier in.txt avec la ligne suivante:

$ java -jar ServerIdUpperCaseUDP.jar 4545 UTF-8
$ java -jar UDPProxy.jar 7777 localhost 4545 -no-swap 
$ java fr.upem.net.udp.ClientIdUpperCaseUDPBurst in.txt out.txt 300 localhost 7777
    

Pensez bien à lancer le serveur et le proxy UDP comme montré ci-dessus.