CSC 4509 – Algorithmique et communications des applications réparties

Portail informatique
  • Communication entre processus en mode asynchrone avec JAVA NIO.

Pour ce TP nous utiliserons le projet csc4509-4-selector déjà importé sur votre IDE depuis le premier TP.

Ce projet contient les fichiers de la solution du TP3, avec juste le programme du client modifié. Lisez la classe MainMsgNioClient pour le comprendre.

Le client a été conçu pour pouvoir envoyer plusieurs messages vers le même serveur en les espaçant d'un timeout de plusieurs secondes. Pour cela il reçoit cinq arguments:

  • argv[0] contient le nom de la machine où tourne le serveur.
  • argv[1] contient le numéro du port du serveur.
  • argv[2] contient le nombre de secondes du timeout entre deux messages émis par le client.
  • argv[3] contient le nombre de messages que va émettre le client.
  • argv[4] contient la chaîne qui sera le message émis par le client.

Donc par exemple la commande "java MainMsgNioClient b02-01 20001 3 10 AAA" va demander au client de se connecter au serveur tournant sur la machine b02-01 sur le port 20001 et de lui envoyer 10 messages contenant la chaîne AAA en les espaçant chacun de 3 secondes.

Actuellement le serveur ne sait gérer qu'un seul client à travers le SocketChannel rwChan et grâce à une instance du FullDuplexMsgWorker appelée message. Il va falloir le modifier pour qu'il puisse gérer simultanément plusieurs clients.

Commencer par déclarer aussi une collection de FullDuplexMsgWorker qui permettra de référencer toutes les instances de cette classe pour gérer la communication vers tous les clients. Vous pouvez par exemple utiliser un Vector: Vector<FullDuplexMsgWorker> theMessages = new Vector<FullDuplexMsgWorker>();

Actuellement l'acceptation de la connexion des nouveaux clients est hors de la boucle. Donc le serveur ne peut accepter qu'un seul client. Il va falloir déplacer la boucle pour englober le listenChannel.accept().

L'algorithme du nouveau serveur devra donc ressembler à cela:

  • Tant que vrai // une boucle infinie
    • accepter un nouveau client
    • ajouter un FullDuplexMsgWorker pour gérer la communication vers ce nouveau client dans la collection theMessages
    • Pour tous les clients actuellement connectés, faire:
      • avancer la lecture sur le canal du client
      • si cette lecture indique que le client est déconnecté, fermer l'instance du FullDuplexMsgWorker associé à ce client, et retirer la de la collection theMessages
      • si cette lecture indique que le message est complet, afficher le message

Testez vos programmes après ces premieres modifications.

Vous devez constaster que le serveur n'avance les lectures que lorsqu'un nouveau client se connecte. En effet la méthode listenChannel.accept() est bloquante. Donc la boucle ne peut faire un nouveau tour que lorsqu'une connexion est faite. De même la lecture des messages arrivant des clients est bloquante. Donc si un client n'écrit rien, et qu'on tente de lire un message (faites un essai en donnant pour un client comme argument un timeout d'une heure) le serveur va être bloqué, et il ne pourra même plus accepter de nouveaux clients.

Pour éviter ces blocages il faut passer les canaux des sockets (celui qui sert à faire l'accept, et ceux qui servent à faire les lectures) en non-bloquants. Pour cela il faut utiliser le méthode configureBlocking des canaux. Par exemple si le canal du listen s'appelle listenChannel, il faut faire l'instruction listenChannel.configureBlocking(false); une fois celui-ci créé. Il faudra faire la même chose sur chacun des canaux reliés aux clients.

Attention, à partir du moment où ces canaux sont non-bloquants, si on tente une action dessus, et qu'elle n'est pas réalisable immédiatement, l'action ne bloque pas, mais échoue. Donc un retour possible et normal (et qu'il va donc falloir gérer) de la méthode listenChannel.accept() est la valeur null. Elle signifie juste qu'il n'y avait pas d'accept à faire à ce moment là.

Modifiez le code du serveur pour rendre le canal qui sert à faire l'accept et les canaux qui servent à la communication avec les clients, non-bloquants. Attention, pour éviter que votre serveur ne charge trop la CPU de votre ordinateur, ajoutez aussi un arrêt à chaque tour de boucle en insérant un Thread.sleep(1000); en début de boucle.

La solution précédente fonctionne, mais elle donne un serveur actif en permanence, même lorsqu'il n'y aucun client et aucun message à gérer. Pour éviter ce problème, il va falloir ajouter une méthode qui va bloquer le serveur, et qui ne débloquera que lorsqu'on sera sûr qu'il y a un événement à gérer (un client à accepter, un message à lire, un client qui s'est déconnecté). Pour cela on va utiliser la classe Selector

Les méthodes importantes pour l'usage de Selector sont:

  • la méthode de classe Selector.open() qui permet d'obtenir un objet de cette classe,
  • ma méthode SelectableChannel.register() qui permet d'enregistrer un canal à surveiller ainsi que le type de surveillance. La classe ServerSocketChannel (pour l'accept() ) et la classe SocketChannel (pour les read()) sont des SelectableChannel,
  • la méthode Selector.select() qui réalise la mise en attente de données disponibles,
  • la méthode Selector.selectedKeys()qui retourne l'ensemble des clés dont les canaux sont prêts.

Modifiez le serveur du mode polling en utilisant un objet de type Selector.

Pour réaliser cette étape, nous recommandons de remplacer le Vector de l'étape précédente, par un objet de type Map dont la clé est de type SelectionKey et l'objet associé de type du récepteur de l'étape précédente (FullDuplexMsgWorker):

Map<SelectionKey, FullDuplexMsgWorker> theReceivers = new HashMap<SelectionKey, FullDuplexMsgWorker>();

Vous pourrez ainsi faire le lien entre l'événement qui a réveillé le Selector, et l'instance du FullDuplexMsgWorker qu'il faudra utiliser.

 

 

 


$Date: 2017-10-07 15:00:48 +0200 (lun. 10 avril 2017) $