TP Java Avancé 31/03/2004 -- Ing2 SIGL1 -- Réseau (1/2) ======================================================= I. Modalités de rendu --------------------- - Date/heure : mardi 06/04/2004 à 14:00 - Répertoire : ~/rendu/java/sigl1/sphinx - Fichiers : SphinxMind.java, SphinxServer.java, SphinxClientHandler.java, SphinxClient.java - Droits : 700 répertoires, 600 fichiers (*) Gardez la clause package (org.insia.network.quizz) mais ne me rendez que le fichier, sans le placer dans un sous-répertoire correspondant. Juste à même le répertoire de rendu. Evidemment, ne TRAVAILLEZ PAS dans votre répertoire de RENDU. ------------------------------------------------------------------------------ Principe ------------------------------------------------------------------------------ Vous allez devoir créer un système de quizz simpliste (voilà qui va faire plaisir aux drogués de #quizz...). Nous l'avons baptisé Le Sphinx. ------------------------------------------------------------------------------ Paquet ------------------------------------------------------------------------------ Le paquet de vos classes est : org.insia.network.quizz. ------------------------------------------------------------------------------ SphinxMind ------------------------------------------------------------------------------ La classe SphinxMind représente l'Esprit du Sphinx(tm)... Elle constitue une sorte de "factory de questions/réponses", qui charge à l'initialisation un fichier de Q/R et permet d'obtenir la Q/R courante et de passer à la suivante. Elle fournit donc quatre méthodes publiques statiques (et le reste vous regarde) : loadQuestions(final String fileName) Charge le fichier de questions indiqué par fileName. Le fichier est au format texte, une paire Q/R par ligne (séparés par "##"). Toute ligne sans "##" est ignorée. Si une série de questions a déjà été chargée *pour aboutir à une série non vide*, appeler cette méthode lance une IllegalStateException avec le message "Questions already loaded!". Une fois le chargement effectué, le questionnaire est positionné sur la première question. String getCurrentQuestion() Renvoie le texte de la question courante, ou null si on a atteint la fin du questionnaire. String getCurrentAnswer() Renvoie le texte de la réponse courante, ou null si on a atteint la fin du questionnaire. String getNextQuestion() Renvoie null si on a atteint la fin du questionnaire, ou déplace le positionnement interne de ce dernier et renvoie le texte de la nouvelle question courante. ------------------------------------------------------------------------------ SphinxServer ------------------------------------------------------------------------------ La classe SphinxServer contient le serveur de quizz. Elle maintient une liste des clients connectés, auxquels elle doit pouvoir relayer les tentatives de chacun. Lorsque le questionnaire est fini, elle déclare le vainqueur et termine le serveur. Chaque client connecté fait l'objet d'un gestionnaire de client (voir SphinxClientHandler plus bas), lancé au sein d'un thread standard. Elle a deux méthodes publiques statiques (et le reste vous regarde) : void notifyClients(final String msg, final SphinxClientHandler sender) Elle relaie le message msg à tous les clients *sauf* le sender. Elle utilise pour cela la méthode SphinxClientHandler.say(String). void main(final String[] args) La méthode principale. Elle lance l'écoute sur le port 1042, récupère le chemin du fichier de Q/R sur la ligne de commande et demande à SphinxMind de le charger. Ensuite, elle attend en permanence de nouveaux clients pour les incorporer dans le quizz. Le lancement du serveur affiche sur la sortie standard (sans l'indentation) : --- Waiting for connection on port: 1042 --- L'arrivée d'un nouveau client envoie à tous les clients sauf lui (idem) : --- New client --- Le client qui vient de se connecter se prend toutefois un message indiquant la question courante (toujours sans indentation) : CURRENT QUESTION: xxx Où "xxx" est le texte de la question courante. ------------------------------------------------------------------------------ SphinxClientHandler ------------------------------------------------------------------------------ La classe SphinxClientHandler permet de gérer, au sein d'un thread standard, une connexion avec un client particulier. Elle lit en boucle ce que le client a à lui dire, et interprète ses saisies comme des réponses potentielles. Elle interagit avec SphinxMind pour détecter les bonnes réponses et passer à la question suivante. Les méthodes/constructeurs publics suivants sont requis : SphinxClientHandler(final Socket socket) throws IOException Associe la socket au gestionnaire. Vous y créerez probablement de quoi écrire du texte dans la socket. Chaque nouveau client se voit attribuer un numéro, qui démarre à un, et est incrémenté de façon synchronisée. Affiche sur la sortie standard du serveur : [server]> NEW CLIENT #xxx Où xxx est le numéro du client. void run() Le corps de votre classe. Lit en boucle sur le client, et interprète les réponses en ignorant les MAJ/min. Le texte spécial "quit" permet au client de quitter le quizz. Les clients restants comme la sortie standard du serveur voient alors apparaître le message (sans l'indentation) : [server]> CLIENT #xxx HAS LEFT THE GAME. Où xxx est le numéro de la connexion se terminant. on ferme alors la connexion au client et le gestionnaire se termine en quittant sa boucle. Pour tout autre texte, on le considère comme une tentative de réponse. La sortie standard du serveur affiche alors : [xxx]> yyy Où xxx est le numéro de la connexion et yyy le texte envoyé. On traite alors la réponse en interrogeant SphinxMind, et trois cas se présentent : 1) Le client a trouvé. Tous les clients à part lui reçoivent : [server]> [xxx] FOUND THE ANSWER: yyy. Now has ppp points. Où ppp est le nombre de points obtenus par le joueur. Ce nombre démarre à zéro et augmente de 1 à chaque bonne réponse. Le client lui-même reçoit : [server]> CONGRATULATIONS! YOUR NOW HAVE ppp POINTS! On passe à la question suivante. Si le questionnaire est en fait terminé, tous les clients reçoivent l'un des deux message suivants : [server]> GAME OVER. NO WINNER. Si aucun joueur n'a un score supérieur à tous les autres, ou alors : [server]> GAME OVER. THE WINNER IS CLIENT #xxx WITH ppp POINTS. Si un des joueurs a un score supérieur à tous les autres. Et le serveur se termine (code de retour 0). Sinon (il reste des questions) les clients reçoivent tous : [server]> NEXT QUESTION: yyy Où yyy est le texte de la nouvelle question courante. 2) Le client n'a pas trouvé. Tous les clients reçoivent alors une notification de son essai : [xxx]> yyy Où xxx est le numéro de la connexion et yyy le texte envoyé. void say(final String msg) Envoie le texte fourni au client. Cet envoi est suivi d'un saut de ligne. Si vous utilisez des mécanismes bufferisés (PrintWriter, BufferedWriter...) assurez-vous de flusher, manuellement ou automatiquement. ------------------------------------------------------------------------------ SphinxClient ------------------------------------------------------------------------------ La classe SphinxClient permet de se connecter au serveur et de participer au quizz. Elle dispose d'une classe interne exécutable depuis un Thread standard et qui lit en boucle sur une socket, affichant chaque ligne lue à la volée. Ceci permettra au client de voir s'afficher les envois du serveur à la volée. Cette classe interne s'appelle SocketReader, elle est privée statique. Un constructeur et une méthode requis : SocketReader(final Socket s) throws IOException Associe la socket au lecteur. Vous y créerez probablement de quoi lire dans la socket. void run() Le corps de la classe. Lit en boucle des lignes de texte sur la socket et les affiche au fur et à mesure sur la sortie standard, telles quelles. Evidemment, surveille le interrupted() du thread courant dans sa boucle. Toute exception I/O est capturée/baillonnée/tuée, mais stoppe la boucle et la méthode se termine. Par ailleurs, toute ligne contenant le texte "GAME OVER." entraîne, après affichage, une terminaison un peu brutale (System.exit(0)). La classe SphinxClient elle-même n'a qu'une méthode, main, qui en fait un programme. La ligne de commande peut prendre l'hôte du serveur en argument optionnel (défaut : "localhost"). Les exceptions sont propagées. Une socket client vers le serveur est ouverte directement, et un Thread standard est lancé autour d'un SocketReader basé sur cette socket client. Rien n'est affiché par le client, c'est le serveur qui fait tout le reste. Le main lit l'entrée standard en boucle et envoie chaque ligne au serveur (pensez au flush, auto ou manuel !). ------------------------------------------------------------------------------ Ressources ------------------------------------------------------------------------------ http://profs.insia.org/~tdd/sigl1/java http://java.sun.com/docs/books/tutorial/networking/index.html http://java.sun.com/features/2002/08/j2se-network.html http://java.sun.com/developer/technicalArticles/Networking/timeouts/ http://java.sun.com/j2se/1.4.2/docs/guide/net/index.html -- FIN DE SUPPORT TP --