Fiabilisation d'une succession d'échanges UDP

Paquets avec identifiant, envoyés et acquittés un par un

Le but de ce TD est de refaire les deux clients du TD sur la fiabilisation des échanges UDP mais cette fois en en mode non-bloquant. Vous pouvez relire le TD 4 pour vous rafraîchir la mémoire sur le protocole IdUpperCase et sur le principe de fonctionnement des clients ClientIdUpperCaseUDPOneByOne et ClientIdUpperCaseUDPBurst.
Protocole IdUpperCase
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 Big Endian 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.

La classe ClientIdUpperCaseUDPOneByOne 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). Par exemple,

$ java fr.uge.net.udp.ClientIdUpperCaseUDPOneByOne in.txt out.txt 300 localhost 7777
Le client ClientIdUpperCaseUDPOneByOne envoie une par une les chaînes du fichier in-filename au serveur et écrit les réponses dans le fichier out-filename dans le protocole IdUpperCase.
Par exemple s'il y a deux chaînes à mettre en majuscules 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 qu'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é.

En partant de la trame ClientIdUpperCaseUDPOneByOne.java, réalisez le client en mode non-bloquant.

La méthode selector.select() est bloquante : un appel à selector.select() ne termine que quand une activité est détectée. Dans le cas de notre client, il faudra borner le temps passé dans selector.select() pour pouvoir renvoyer une ligne si la réponse correspondante n'a pas été reçue en moins de timeout millisecondes.
La méthode select(Consumer<SelectionKey> action, long timeout) termine en au plus timeout millisecondes même si aucune activité n'a été détectée.
Dans la trame proposée, nous utilisons un état State state pour garder la trace de l'état du protocole.
enum State {SENDING, RECEIVING, FINISHED};
Le client est dans l'état SENDING s'il doit envoyer une ligne (requête), il passe dans l'état RECEIVING lorsqu'il est en attente de sa réponse et il passe dans l'état FINISHED lorsqu'il a reçu la réponse à la dernière ligne.
La boucle de sélection a été légèrement modifiée :
    ...
    while (!isFinished()) {
        try {
            selector.select(this::treatKey, updateInterestOps());
        } catch (UncheckedIOException tunneled) {
            throw tunneled.getCause();
        }
    }
    dc.close();
    ...    

private void treatKey(SelectionKey key) {
    try {
        if (key.isValid() && key.isWritable()) {
            doWrite();
        }
        if (key.isValid() && key.isReadable()) {
            doRead();
        }
    } catch (IOException ioe) {
        throw new UncheckedIOException(ioe);
    }
}
Comme le sélecteur ne surveille ici qu'un seul DatagramChannel, il n'y a qu'une seule clé. Nous avons stocké cette uniqueKey dans un champ de l'objet client. La convention est que seule la méthode updateInterestOps() modifie l'interestOps de uniqueKey. De plus cette méthode renvoie le temps maximum à passer dans la méthode selector.select(action).
Attention, selector.select(action, 0) est équivalent à selector.select(action).

Tests pour votre client

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 

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

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

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 le temps écoulé entre chaque paquet reçu.

Vous devez tester avec les lignes suivantes:

$ java -jar ServerIdUpperCaseUDPTestTimeout.jar 4545
$ java fr.uge.net.udp.nonblocking.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
    ....

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

En partant de la même trame ClientIdUpperCaseUDPOneByOne.java, vous devez réaliser un client ClientIdUpperCaseUDPBurstqui résoud le problème en réalisant l'envoi en rafale des paquets.

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.uge.net.udp.nonblocking.ClientIdUpperCaseUDPBurst in.txt out.txt 300 localhost 7777