Le but de cet exercice est de réaliser un serveur pour le protocole IdUpperCase
.
IdUpperCase
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) ;
UTF-8
.
Contrairement aux premiers clients, que l'on implémentait jusqu'ici dans le main()
d'une classe, on va pour le serveur considérer que son port d'attachement local, via le DatagramChannel
,
est un champ qui identifie un objet "serveur" et on fournira pour la classe ServerIdUpperCaseUDP
:
serve()
qui place le serveur en attente de réception de requêtes de clients ;
main()
qui utilise les arguments sur la ligne de commande pour créer une instance de serveur et le démarre en appelant serve()
.
Écrivez la classe ServerIdUpperCaseUDP.java
qui prend simplement en paramètre le port d'attachement du serveur.
Testez votre serveur avec un de vos clients développés et testés lors des séances précédentes.
$ java fr.uge.net.udp.ServerIdUpperCaseUDP 4545Vous pouvez tester d'abord sans proxy, puis à travers le proxy.
Si vous avez encore des problèmes avec votre client, vous pouvez utiliser le ClientIdUpperCaseUDP.jar en guise de client (implémentation burst).
int
L'objectif est dans cet exercice de réaliser un service de somme
d'entiers ints. Le client dispose d'une liste
d'opérandes (de type int
) qu'il doit envoyer, dans des
paquets différents, au serveur qui doit en faire la somme (de type long
) et la renvoyer au client.
Bien sûr, il s'agit d'un protocole statefull et le serveur devra mémoriser de quel client (adresse IP + port) il reçoit les informations, afin de ne pas "mélanger" les opérandes de plusieurs clients.
De plus, plusieurs points de vigilance nous conduisent à mettre en place un protocole particulier :
Voici donc les différents paquets de notre protocole : chaque paquet débute par un code d'opération codé sur un octet qui vaut :
OP = 1
si le paquet contient une opérande envoyée par le client au serveur.byte long int int int +---+-----------+-----------+-----------+-----------+ | 1 | sessionID | idPosOper | totalOper | opValue | // paquet OP +---+-----------+-----------+-----------+-----------+où
sessionID
est un identifiant de session unique choisi par le client et
commun à tous les échanges liés à une somme donnée,idPosOper
est la position de cette opérande dans la somme,
qui l'identifie (valeur comprise dans l'intervalle [0 .. totalOper-1[
)totalOper
est le nombre total d'opérandes de cette somme,opValue
est la valeur de l'opérande en question.ACK = 2
si le paquet contient l'accusé de réception envoyé
par le serveur en réponse à un paquet OP
.byte long int +---+-----------+-----------+ | 2 | sessionID | idPosOper | // paquet ACK +---+-----------+-----------+où
sessionID
est l'identifiant de sessionidPosOper
est la position de l'opérande acquittéRES = 3
si le paquet contient la somme de tous les opérandes.
Le serveur envoie un paquet RES
en réponse à n'importe quel
paquet OP
d'un client dès lors qu'il a reçu tous les opérandes de
cette somme (session) -- cet envoi a lieu en plus du paquet ACK
acquittant le paquet OP
reçu.RES
a le format suivant :
byte long long +---+-----------+-----------+ | 3 | sessionID | sum | // paquet RES +---+-----------+-----------+où
sessionID
est l'identifiant de sessionsum
est la somme de tous les opérandes
de cette sessionint
et long
sont transmis en Big Endian. La somme est un long
, attention aux possibles overflows.
Prenons l'exemple d'un client Charlie qui veut envoyer les entiers ints (opérandes) 10000, 2000 et 300000 au serveur Serge. Supposons que l'identifiant de cette somme soit 55555.
Charlie envoie donc ses 3 opérandes, dans 3 paquets distincts :
+-----+-----------+-----------+-----------+-----------+ | 1 | 55555 | 0 | 3 | 10000 | // C -> S OP 0 +-----+-----------+-----------+-----------+-----------+ OP sessionID idPosOper totalOper opValue +-----+-----------+-----------+-----------+-----------+ | 1 | 55555 | 1 | 3 | 2000 | // C -> S OP 1 +-----+-----------+-----------+-----------+-----------+ OP sessionID idPosOper totalOper opValue +-----+-----------+-----------+-----------+-----------+ | 1 | 55555 | 2 | 3 | 300000 | // C -> S OP 2 +-----+-----------+-----------+-----------+-----------+ OP sessionID idPosOper totalOper opValueSupposons que Serge ne reçoit correctement que le paquet 1 et le paquet 2 (le paquet 0 est perdu !) ; il acquitte les opérandes reçues :
+-----+-----------+-----------+ | 2 | 55555 | 1 | // S -> C ACK 1 +-----+-----------+-----------+ ACK sessionID idPosOper +-----+-----------+-----------+ | 2 | 55555 | 2 | // S -> C ACK 2 +-----+-----------+-----------+ ACK sessionID idPosOperMais l'acquittement 2 est perdu et Charlie ne reçoit que l'acquittement du paquet 1. Au bout d'un temps d'attente fixé, Charlie décide de ré-envoyer les opérandes qui n'ont pas été acquittées, soit 0 et 2 :
+-----+-----------+-----------+-----------+-----------+ | 1 | 55555 | 0 | 3 | 10000 | // C -> S OP 0 +-----+-----------+-----------+-----------+-----------+ OP sessionID idPosOper totalOper opValue +-----+-----------+-----------+-----------+-----------+ | 1 | 55555 | 2 | 3 | 300000 | // C -> S OP 2 +-----+-----------+-----------+-----------+-----------+ OP sessionID idPosOper totalOper opValueSi Serge reçoit cette fois les deux paquets, il ne "comptabilise" dans sa somme que le premier, puisque le second a le même
idPosOper
qu'un paquet déjà reçu (il a été reçu en double), mais il acquitte tout de même chaque paquet reçu.
+-----+-----------+-----------+ | 2 | 55555 | 0 | // S -> C ACK 0 +-----+-----------+-----------+ ACK sessionID idPosOper +-----+-----------+-----------+ | 2 | 55555 | 2 | // S -> C ACK 2 +-----+-----------+-----------+ ACK sessionID idPosOperet de plus, comme il a obtenu tous (3) les opérandes nécessaires (d'après
totalOper
), il peut renvoyer la somme à Charlie :
+----+-----------+-----------+ | 3 | sessionID | 312400 | // S -> C RES 312400 +----+-----------+-----------+ RES sessionID sumSi Charlie reçoit les deux acquittements, il a la certitude que toutes ses opérandes sont à la disposition de Serge, mais même s'il ne recevait pas l'acquittement 0, qui pourrait à nouveau se perdre, il peut arrêter immédiatement toute activité dès qu'il reçoit le paquet
RES
, puisque celui-ci contient sa réponse ultime.RES
qui s'est perdu : il devra donc au bout d'un certain temps ré-envoyer une opérande (n'importe laquelle), pour provoquer de Serge le ré-envoi du résultat...
On souhaite écrire un serveur ServerIntSumUDP
pour ce service avec
un constructeur, une méthode serve()
et un main()
.
Pour vérifier que votre serveur se comporte bien comme attendu, récupérer le client suivant ValidatorIntSumUDP.jar. Ce client teste le serveur en simulant plusieurs clients.
Dans trois consoles (shell) différentes, simultanément visibles à l'écran, exécutez les trois applications suivantes :
$ java fr.uge.net.udp.ServerIntSumUDP 7777 $ java -jar UDPProxy.jar 6666 localhost 7777 $ java -jar ValidatorIntSumUDP.jar localhost 6666
Si tout se passe correctement, vous devez avoir l'affichage suivant :
Trying 5 queries one after the other. Query 1 succeeded! Query 2 succeeded! Query 3 succeeded! Query 4 succeeded! Query 5 succeeded! Trying 5 queries from the same client at the same time. Test passed Trying 5 clients at the same time. Test passed
Dans l'implémentation actuelle de vos serveurs, les données relatives à toutes les requêtes sont conservées indéfiniment. Cela pose un problème car tout au long de l'activité du serveur la mémoire qu'il utilise ne cesse de croître.
Question : Pourquoi ne peut-on pas libérer les ressources liées à une session avec un client après avoir envoyé le paquet contenant le résultat ?
Une première solution pour aider à libérer les ressources consiste à modifier le protocole pour permettre au client de signaler au serveur qu'il peut effacer les données relatives à une session. Pour cela on introduit deux nouveaux types de paquets.
CLEAN = 4
sont envoyés par le client au serveur pour lui signifier que la
session ne sera plus utilisée par la suite.
CLEAN
a le format suivant :
byte long +---+-----------+ | 4 | sessionID | // paquet CLEAN +---+-----------+où
sessionID
est l'identifiant de sessionACKCLEAN = 5
sont envoyés par le serveur en réponse au paquet CLEAN
.
ACKCLEAN
a le format suivant :
byte long +---+-----------+ | 5 | sessionID | // paquet ACKCLEAN +---+-----------+où
sessionID
est l'identifiant de sessionModifiez votre serveur pour qu'il puisse gérer ce protocole étendu. Pour tester, récupérer le client suivant ClientFreeIntSumUDP.jar. Ce client teste le serveur en simulant plusieurs clients.
Dans trois consoles (shell) différentes, simultanément visibles à l'écran, exécutez les trois applications suivantes :
$ java fr.uge.net.udp.ServerFreeIntSumUDP 7777 $ java -jar UDPProxy.jar 6666 localhost 7777 $ java -jar ClientFreeIntSumUDP.jar localhost 6666
Cette mesure n'est à elle seule pas suffisante. On ne doit jamais supposer que les clients respectent le protocole. Il faut donc en plus donner une durée de vie limitée à chaque session. Mettez en place cette solution sur votre serveur.