TCP non bloquant (2/3)

Meilleur mini additionneur TCP non bloquant

Le but de cet exercice est d'écrire un serveur pour le protocole Sum de l'exercice 1 du TP précédent, mais cette fois-ci, nous cherchons à écrire un serveur qui puisse être à la fois en OP_READ et OP_WRITE, afin de pouvoir gérer efficacement le cas où un client envoie rapidement beaucoup d'entiers : ainsi, on pourra traiter plusieurs demandes d'un coup.

Protocole Sum
Le client envoie deux int (4 octets) en Big Endian. Le serveur répond un int en Big Endian, valant la somme des deux entiers reçus. Ces échanges continuent jusqu'à ce que le client ferme la connexion en écriture.

En partant de la trame ServerSumBetter.java, écrivez un serveur pour le protocole Sum.

Pour ce serveur, la classe Context contiendra deux ByteBuffer, l'un pour la lecture et l'autre pour l'écriture. La méthode process sert à transférer le ByteBuffer de lecture dans le ByteBuffer d'écriture.

static private class Context {
  private final SelectionKey key;
  private final SocketChannel sc;
  private final ByteBuffer bufferIn = ByteBuffer.allocate(BUFFER_SIZE);
  private final ByteBuffer bufferOut = ByteBuffer.allocate(BUFFER_SIZE);
  private boolean closed = false;

  private Context(SelectionKey key){
      this.key = key;
      this.sc = (SocketChannel) key.channel();
  }

  private void process() {
    // TODO
  }

  private void updateInterestOps() {
    // TODO
  }

  private void doRead() throws IOException {
    // TODO 
  }

  private void doWrite() throws IOException {
    // TODO
  }
}  

Le jar ClientSumChrono.jar chronomètre le temps nécessaire pour qu'un client réalise 100 000 sommes.

Chronométrez votre serveur ServerSumBetter et votre serveur ServerSum de l'exercice 1 et comparez les temps.

% java fr.upem.net.tcp.blocking.ServerSumBetter 7777
% java -jar ClientSumChrono.jar localhost 7777
% java fr.upem.net.tcp.blocking.ServerSum 7777
% java -jar ClientSumChrono.jar localhost 7777

Serveur de chat basique

Le but de cet exercice est d'écrire un serveur de chat rudimentaire. On commencera par un serveur de chat où l'on ne s'envoie que des int en Big Endian. Puis on passera à un protocole où l'on s'envoie des messages.

Protocole ChatInt

Les trames circulant entre les clients et le serveur sont des int en Big Endian.

Quand un message est reçu par le serveur, il le transmet à tous les clients.

En partant de la trame ServerChatInt.java, écrivez un serveur pour le protocole ChatInt en non-bloquant.

La différence principale avec les serveurs vus jusqu'ici est que lorsque qu'un message est reçu d'un client, il doit être transmis à tous les autres clients connectés. Dans la trame fournie, ce sera la rôle de la méthode serverChatInt.broadcast. Cette méthode va parcourir toutes les SelectionKey des clients et appeler la méthode context.queueMessage de leur objet Context.
La méthode context.queueMessage ne peut pas simplement écrire l'int dans le ByteBuffer de sortie car celui-ci pourrait être plein. On passe donc par une Queue<Integer> pour stocker tous les messages entrant. C'est la méthode context.processOut qui se chargera de remplir le ByteBuffer de sortie à partir de la file.

Pour tester votre serveur, vous pouvez utiliser le jar ClientChatInt.jar. Ce client envoie les int tapés au clavier et affiche les entiers reçus du serveur.

% java fr.upem.net.tcp.nonblocking.ServerChatInt 7777
% java -jar ClientChatInt.jar localhost 7777
% java -jar ClientChatInt.jar localhost 7777

Quand vous tapez un nombre dans l'un des deux clients, vous devez le voir s'afficher dans les deux clients.

Serveur de chat Chaton

Nous considérons maintenant un protocole plus complexe où les clients envoient des messages avec leur login.
Protocole Chaton

Les trames circulant entre les clients et le serveur représentent des messages. Un message contient un login et un texte au format suivant :

+------------------+------------+------------------+------------+
| Login size (INT) | Login UTF8 | Texte size (INT) | Texte UTF8 |
+------------------+------------+------------------+------------+

Les entiers sont en Big Endian. Les chaînes ne font pas plus de 1024 octets.

Quand un message est reçu par le serveur, il le transmet à tous les clients.

Par rapport au protocole précédent, la difficulté supplémentaire vient du fait qu'il faut savoir trouver les messages dans le ByteBuffer bufferIn.

Pour ce faire, nous vous proposons une architecture logicielle basée sur l'interface Reader.java. L'interface Reader est paramétrée par un type T qui le type de l'objet qu'elle va extraire du ByteBuffer

Les méthodes de l'interface doivent avoir le comportement suivant :

Comme exemple, nous vous présentons un IntReader.java implémentant l'interface Reader<Integer>. Cette classe vise à extraire des int en Big Endian.

Dans le ServerChatInt, on peut réécrire la méthode processIn comme suit :

  private void processIn() {
    for (;;) {
      Reader.ProcessStatus status = intReader.process(bufferIn);
      switch (status) {
      case DONE:
        var value = intReader.get();
        server.broadcast(value);
        intReader.reset();
        break;
      case REFILL:
        return;
      case ERROR:
        silentlyClose();
        return;
      }
    }
  }

Écrire une classe StringReader implémentant l'interface Reader<String> permettant de lire des String au format :

+------------+------------+
| Size (INT) | Texte UTF8 | 
+------------+------------+  
La taille maximum de size est de 1024.

Vérifiez que votre classe passe les tests unitaires dans StringReaderTest.java

Écrire une classe MessageReader implémentant l'interface Reader<Message> permettant de lire des messages au format du protocole Chaton.

Écrire un serveur non-bloquant pour le protocole Chaton.

Pour tester votre code, vous pouvez utilisez le client ClientChat.jar.

% java -jar ClientChat.jar Bob localhost 7777