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.
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
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.
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.
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 :
ByteBuffer
. La convention sera toujours que le ByteBuffer buffer
est en mode écriture avant et après l'appel à la méthode.enum Reader.ProcessStatus
.
REFILL
indique qu'il n'y a pas encore assez de données dans le ByteBuffer
pour trouver toutes les données.
DONE
indique que les données ont été trouvées et enlevées du ByteBuffer
. On pourra récupérer la valeur avec la méthode get
.
ERROR
indique que les octets trouvés dans le ByteBuffer
ne sont pas compatibles avec le format attendu.
T
correspondant aux données (dans le cas où la méthode process
a renvoyé le code DONE
) et sinon lève une exception IllegalStateException
.
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