diff --git a/cesi/architecture_n_tiers/presentation/slides.md b/cesi/architecture_n_tiers/presentation/slides.md index e2d5873..2741327 100644 --- a/cesi/architecture_n_tiers/presentation/slides.md +++ b/cesi/architecture_n_tiers/presentation/slides.md @@ -130,19 +130,9 @@ La conception d'une application distribuée nécessite d'établir une répartiti --- -## Exercice (1) - -Implémenter en NodeJS une application distribuée permettant d'effectuer les opérations arithmétiques simples (addition, soustraction, multiplication et division). Cette application devra répondre aux contraintes suivantes: - -- Elle devra être suivre une architecture 2 tiers de classe 4. -- Le client devra utiliser le transport TCP/IP pour communiquer avec le serveur. -- Elle n'aura pas à supporter de multiples clients. -- Toutes les implémentations de la classe devront être compatibles, i.e. vous devez vous mettre d'accord sur un protocole de sérialisation/désérialisation des messages commun. - ---- - -## Exercice (2) +## Exercice +### Implémentation d'une calculatrice à état distribuée en NodeJS --- diff --git a/cesi/architecture_n_tiers/ressources/exercices/ex_2_tiers/README.md b/cesi/architecture_n_tiers/ressources/exercices/ex_2_tiers/README.md new file mode 100644 index 0000000..9230289 --- /dev/null +++ b/cesi/architecture_n_tiers/ressources/exercices/ex_2_tiers/README.md @@ -0,0 +1,30 @@ +# Exercice: implémentation d'une calculatrice à état distribuée + +## Consignes + +Implémenter en NodeJS une application distribuée permettant d'effectuer les opérations arithmétiques simples. Cette application devra répondre aux contraintes suivantes: + +- Elle devra être suivre une architecture 2 tiers de classe 4. +- Le client devra utiliser le transport TCP/IP pour communiquer avec le serveur. +- Elle n'aura pas à supporter de multiples clients. +- Toutes les implémentations de la classe devront être compatibles, i.e. vous devez vous mettre d'accord sur un protocole de sérialisation/désérialisation des messages commun. + +Le client/serveur devront gérer les instructions suivantes: + +- `add` Requête d'addition +- `sub` Requête de soustraction +- `div` Requête de division +- `mul` Requête de multiplication +- `status` Requête de récupération de la valeur de l'accumulateur sur le serveur +- `reset` Requête de réinitialisation de la valeur de l'accumulateur sur le serveur. + +Vous pouvez vous baser sur les fichiers `client.js` et `server.js` présent dans ce répertoire pour amorcer votre projet. + +## Exemple de séquence d'échange + +![center](./seq.png) + +## Ressources + +- [Télécharger/installer NodeJS](https://nodejs.org/en/download/) +- [Le module `net` de NodeJS](https://nodejs.org/api/net.html) diff --git a/cesi/architecture_n_tiers/ressources/exercices/ex_2_tiers/client.js b/cesi/architecture_n_tiers/ressources/exercices/ex_2_tiers/client.js new file mode 100644 index 0000000..4e2b66a --- /dev/null +++ b/cesi/architecture_n_tiers/ressources/exercices/ex_2_tiers/client.js @@ -0,0 +1,78 @@ +var net = require('net'); + +// Notre client se connecte sur le serveur local +var SERVER_HOST = '127.0.0.1'; +var SERVER_PORT = 3333; + +// Création du "socket" de connexion TCP/IP +var client = new net.Socket(); + +// Notre objet "client" est une instance d'EventEmitter +// (voir https://nodejs.org/api/events.html) +// On peut donc lui attacher des fonctions, +// appelées "callback" ou "handler", afin de réagir +// à certains évènements liés au cycle de vie de l'objet + +// On attache un callback à notre client pour +// l'évènement "connect". En passant une référence +// à la fonction "onConnect" on indique que l'on +// souhaite que celle ci soit automatiquement +// invoquée lorsque le client sera connecté. +client.on('connect', onConnect); + +// Les données transmises par le serveur au client sont +// automatiquement transformées sous forme d'évènement +// par NodeJS. On peut récupérer ces données en ajoutant +// un callback sur notre objet "client" pour l'évènement +// "data" +client.on('data', onData); + +// L'évènement "close" est émis lorsque la connexion +// avec le serveur est close. +client.on('close', onClose); + +// L'évènement "error" est émis lorsque une erreur +// est soulevée. (exemple: perte de connexion) +client.on('error', onError); + + +// On initialise la connexion au serveur +console.log('Tentative de connexion au serveur: %s:%s', SERVER_HOST, SERVER_PORT); +client.connect(SERVER_PORT, SERVER_HOST); + +function onConnect() { + + // Nous sommes connecté au serveur ! + console.log('Connecté sur ' + SERVER_HOST + ':' + SERVER_PORT); + + // TODO envoyer les commandes au serveur ici + + // On peut envoyer des données au serveur via la méthode + // write() du client + // Exemple + client.write('add 1'); + + // Si on le souhaite, on peut clore la connexion + // au serveur via la méthode destroy() + client.destroy(); + +} + +function onData(data) { + + // Les données sont reçues sous forme de bytes. On les transforme + // en chaine de caractères UTF-8 pour faciliter leur manipulation + var str = data.toString('utf8'); + + console.log('Données reçues du serveur: %s', str); + +} + +function onClose() { + console.log('La connexion au serveur a été close.') +} + +function onError(err) { + console.log('Une erreur a été soulevée: %s', err); + process.exit(1); +} diff --git a/cesi/architecture_n_tiers/ressources/exercices/ex_2_tiers/seq.msc b/cesi/architecture_n_tiers/ressources/exercices/ex_2_tiers/seq.msc new file mode 100644 index 0000000..1336608 --- /dev/null +++ b/cesi/architecture_n_tiers/ressources/exercices/ex_2_tiers/seq.msc @@ -0,0 +1,34 @@ +msc { + + wordwraparcs=true, hscale=2; + + Client,Server; + + ... [ label = "Lancement du serveur et du client" ]; + + Server box Server [ label="acc = 0" ]; + + Client->Server [ label="status" ]; + Server->Client [ label="0" ]; + + Client->Server [ label="add 10" ]; + Server->Server [ label="acc = acc + 10" ]; + Server->Client [ label="10" ]; + + Client->Server [ label="sub 2" ]; + Server->Server [ label="acc = acc - 2" ]; + Server->Client [ label="8" ]; + + Client->Server [ label="div 2" ]; + Server->Server [ label="acc = acc / 2" ]; + Server->Client [ label="4" ]; + + Client->Server [ label="mul 5" ]; + Server->Server [ label="acc = acc * 5" ]; + Server->Client [ label="20" ]; + + Client->Server [ label="reset" ]; + Server->Server [ label="acc = 0" ]; + Server->Client [ label="0" ]; + +} diff --git a/cesi/architecture_n_tiers/ressources/exercices/ex_2_tiers/seq.png b/cesi/architecture_n_tiers/ressources/exercices/ex_2_tiers/seq.png new file mode 100644 index 0000000..88b1e2a Binary files /dev/null and b/cesi/architecture_n_tiers/ressources/exercices/ex_2_tiers/seq.png differ diff --git a/cesi/architecture_n_tiers/ressources/exercices/ex_2_tiers/server.js b/cesi/architecture_n_tiers/ressources/exercices/ex_2_tiers/server.js new file mode 100644 index 0000000..160a9fc --- /dev/null +++ b/cesi/architecture_n_tiers/ressources/exercices/ex_2_tiers/server.js @@ -0,0 +1,48 @@ +var net = require('net'); + +var HOST = '127.0.0.1'; // On écoute sur toutes les interfaces +var PORT = 3333; + +var server = net.createServer(); + +server.on('connection', onClientConnection); +server.on('listening', onListening); +server.on('error', onError); + +server.listen(PORT, HOST); + +function onClientConnection(client) { + + console.log('Nouveau client depuis %s:%s', client.remoteAddress, client.remotePort); + + // On souhaite commencer à récupérer les données + // provenant de ce nouveau client + client.on('data', onClientData); + client.on('close', onClientClose); + + function onClientData(data) { + + // Les données sont reçues sous forme de bytes. On les transforme + // en chaine de caractères UTF-8 pour faciliter leur manipulation + var str = data.toString('utf8'); + + console.log('Données reçues de %s:%s: "%s"', client.remoteAddress, client.remotePort, str); + + // TODO désérialiser les données reçues et traiter les opérations. + + } + + function onClientClose() { + console.log('Le client %s:%s s\'est déconnecté.', client.remoteAddress, client.remotePort); + } + +} + +function onListening() { + console.log("Serveur en écoute sur %s:%s", HOST, PORT) +} + +function onError(err) { + console.log('Une erreur a été soulevée: %s', err); + process.exit(1); +} diff --git a/cesi/architecture_n_tiers/ressources/exercices/ex_2_tiers_solution/client.js b/cesi/architecture_n_tiers/ressources/exercices/ex_2_tiers_solution/client.js new file mode 100644 index 0000000..3f4fc74 --- /dev/null +++ b/cesi/architecture_n_tiers/ressources/exercices/ex_2_tiers_solution/client.js @@ -0,0 +1,80 @@ +var net = require('net'); + +// Notre client se connecte sur le serveur local +var SERVER_HOST = '127.0.0.1'; +var SERVER_PORT = 3333; + +// Création du "socket" de connexion TCP/IP +var client = new net.Socket(); + +// Notre objet "client" est une instance d'EventEmitter +// (voir https://nodejs.org/api/events.html) +// On peut donc lui attacher des fonctions, +// appelées "callback" ou "handler", afin de réagir +// à certains évènements liés au cycle de vie de l'objet + +// On attache un callback à notre client pour +// l'évènement "connect". En passant une référence +// à la fonction "onConnect" on indique que l'on +// souhaite que celle ci soit automatiquement +// invoquée lorsque le client sera connecté. +client.on('connect', onConnect); + +// Les données transmises par le serveur au client sont +// automatiquement transformées sous forme d'évènement +// par NodeJS. On peut récupérer ces données en ajoutant +// un callback sur notre objet "client" pour l'évènement +// "data" +client.on('data', onData); + +// L'évènement "close" est émis lorsque la connexion +// avec le serveur est close. +client.on('close', onClose); + +// L'évènement "error" est émis lorsque une erreur +// est soulevée. (exemple: perte de connexion) +client.on('error', onError); + + +// On initialise la connexion au serveur +console.log('Tentative de connexion au serveur: %s:%s', SERVER_HOST, SERVER_PORT); +client.connect(SERVER_PORT, SERVER_HOST); + +function onConnect() { + + // Nous sommes connecté au serveur ! + console.log('Connecté sur ' + SERVER_HOST + ':' + SERVER_PORT); + + // TODO envoyer les commandes au serveur ici + + // On peut envoyer des données au serveur via la méthode + // write() du client + // Exemple + client.write('add 100\n'); + client.write('sub 10\n'); + client.write('div 9\n'); + client.write('mul 5\n'); + + client.write('status\n'); + + client.write('reset\n'); + client.write('status\n'); + + // Si on le souhaite, on peut clore la connexion + // au serveur via la méthode destroy() + // client.destroy(); + +} + +function onData(data) { + console.log('Données récupérée du serveur: %s', data); +} + +function onClose() { + console.log('La connexion au serveur a été close.') +} + +function onError(err) { + console.log('Une erreur a été soulevée: %s', err); + process.exit(1); +} diff --git a/cesi/architecture_n_tiers/ressources/exercices/ex_2_tiers_solution/modd.conf b/cesi/architecture_n_tiers/ressources/exercices/ex_2_tiers_solution/modd.conf new file mode 100644 index 0000000..c7a242b --- /dev/null +++ b/cesi/architecture_n_tiers/ressources/exercices/ex_2_tiers_solution/modd.conf @@ -0,0 +1,6 @@ +server.js { + daemon: node server.js +} +client.js server.js { + prep: node client.js +} diff --git a/cesi/architecture_n_tiers/ressources/exercices/ex_2_tiers_solution/server.js b/cesi/architecture_n_tiers/ressources/exercices/ex_2_tiers_solution/server.js new file mode 100644 index 0000000..ef3ef35 --- /dev/null +++ b/cesi/architecture_n_tiers/ressources/exercices/ex_2_tiers_solution/server.js @@ -0,0 +1,102 @@ +var net = require('net'); + +var HOST = '127.0.0.1'; // On écoute sur toutes les interfaces +var PORT = 3333; + +var server = net.createServer(); + +server.on('connection', onClientConnection); +server.on('listening', onListening); +server.on('error', onError); + +server.listen(PORT, HOST); + +function onClientConnection(client) { + + var accumulator = 0; + + console.log('Nouveau client depuis %s:%s', client.remoteAddress, client.remotePort); + + // On souhaite commencer à récupérer les données + // provenant de ce nouveau client + client.on('data', onClientData); + client.on('close', onClientClose); + + function onClientData(data) { + + console.log('Données reçues de %s:%s: "%s"', client.remoteAddress, client.remotePort, data); + + var messages = data.toString('utf8').split('\n'); + + for(var i = 0; i < messages.length; i++) { + + var line = messages[i]; + var tokens = line.split(' '); + + if (tokens.length !== 1 && tokens.length !== 2) { + client.write('invalid message'); + continue + } + + var command = tokens[0]; + + var arg; + if (tokens.length === 2) { + try { + arg = parseFloat(tokens[1]); + } catch(err) { + client.write('invalid message\n'); + continue + } + } + + console.log("Tokens", tokens); + + switch(command) { + case "add": + accumulator = accumulator + arg; + client.write(accumulator+'\n'); + break; + case "sub": + accumulator = accumulator - arg; + client.write(accumulator+'\n'); + break; + case "div": + accumulator = accumulator / arg; + client.write(accumulator+'\n'); + break; + case "mul": + accumulator = accumulator * arg; + client.write(accumulator+'\n'); + break; + case "status": + client.write(accumulator+'\n'); + break; + case "reset": + accumulator = 0; + client.write(accumulator+'\n'); + break; + } + + + + } + + + + } + + function onClientClose() { + console.log('Le client %s:%s s\'est déconnecté.', client.remoteAddress, client.remotePort); + } + +} + +function onListening() { + console.log("Serveur en écoute sur %s:%s", HOST, PORT) +} + +function onError(err) { + console.log('Une erreur a été soulevée: %s', err); + process.exit(1); +}