Client de « chat » ´el´ementaire
Transcription
Client de « chat » ´el´ementaire
Analyse I Client de « chat » élémentaire L’architecture et l’élégance du code sont de première importance. Veillez à expliquer vos algorithmes et à justifier vos décisions. Vous ferez cela dans un fichier appelé rapport (éventuellement avec une extension) que vous joindrez au code. Vous pouvez mettre en page ce rapport avec un traitement de texte (tels Word ou TEX) ou simplement l’écrire dans un fichier texte rapport.txt (sans utiliser de symboles spéciaux). Faites parvenir vos programmes à [email protected]. Vous pouvez grouper de multiples fichiers sous la forme d’un tarball (.tar.gz ou .zip). N’envoyez pas d’exécutable. Question 1. Programmez les deux fonctions suivantes : split1 : char -> string -> string * string split_all : char -> string -> string list définies par : split1 c s retourne un couple de chaı̂nes de caractères (s1, s2) où s1 est composé des caractères s.[0],...,s.[i − 1] et s2 de s.[i + 1],...,s.[` − 1] avec ` = String.length s ( min{i : s.[i] = c} i= ` s’il existe un i tel que s.[i] = c, sinon. Exemple : split1 ’X’ "abXcdXXef" = ("ab", "cdXXef"). splitall c s découpe s à chaque caractère c et retourne la liste des chaı̂nes de caractères ainsi obtenue. Exemple : split_all ’X’ "abXcdXXef" = ["ab"; "cd"; ""; "ef"]. Indications : Les fonctions String.length et String.sub de la librairie standard vous seront utiles. Pensez aussi, avant d’écrire votre algorithme, aux valeurs des expressions split1 ’X’ s et split_all ’X’ s avec s = "abc" et s = "abcX". Question 2. ➢ Il y a plusieurs manières pour des programmes de communiquer entre eux. Nous nous limiterons ici uniquement aux échanges de données au travers du réseau internet grâce au protocole TCP/IP. Nous appellerons serveur un programme qui attent que des actions lui soient demandées par d’autres programmes. Ces derniers seront dits des clients. Pour pouvoir contacter un serveur, il faut le localiser, ce qui se fait au travers de deux informations : un hostname qui est le nom de la machine sur laquelle le serveur réside ; un numéro de port que le serveur « écoute ». On peut comparer le « hostname » à l’adresse de l’appartement qu’habite le serveur et le numéro de port au numéro d’appartement. Par exemple, si on veut contacter le serveur « apache » qui gère les pages web de l’université, on le touvera sur la machine www.umh.ac.be écoutant au port 80. En Caml, la localisation d’un serveur est une valeur du type Unix.sockaddr créée (en particulier) par 1/4 Client de « chat » élémentaire Analyse I Unix.ADDR_INET(adresse,port) Si le port est le numéro de port dont on vient de parler, adresse ne peut pas être remplacé par une chaı̂ne de caractères du type "www.umh.ac.be". En effet, ces noms ne servent qu’à faciliter leur mémorisation par les humains, les ordinateurs employant eux des nombres. La tâche de conversion des noms en adresses numériques est dévolue à des « serveurs de noms » (DNS servers). Le commande Caml qui y fait appel est Unix.gethostbyname : string -> Unix.host_entry L’appel Unix.gethostbyname nom renvoie une structure contenant diverses informations sur la machine nom. Le champ qui nous intéresse est Unix.h_addr_list. Il s’agit d’un vecteur qui contient les adresses1 de la machine. Nous choisirons la première d’entre elles. Le code pour obtenir l’adresse du serveur sur la machine nom et le port port est donc let addr = (Unix.gethostbyname nom).Unix.h_addr_list.(0) in Unix.ADDR_INET(addr,port) On peut alors utiliser cette adresse pour se connecter au serveur grâce à la commande Unix.open_connection : Unix.sockaddr -> in_channel * out_channel Le résultat de Unix.open_connection adresse est un couple (inchan, outchan) où inchan est un canal de lecture qui permet de recevoir des données du serveur et outchan est un canal d’écriture qui sert à envoyer des données au serveur. Malheureusement, cette commande ne fonctionne pas sous les versions 95 et 98 de Windows.2 Pour vous faciliter la vie, nous avons donc développé une bibliothèque Socket (jointe à ce document) qui contient un analogue des fonctions d’entrée/sortie de la bibliothèque standard. Elle possède la fonction Socket.open_connection : Unix.sockaddr -> Socket.t qui renvoie un « canal » de communication sur lequel on peut lire et écrire grâce à Socket.input_line : Socket.t -> string Socket.output_string : Socket.t -> string -> unit etc. On se déconnecte avec Socket.close : Socket.t -> unit. ➢ L’échange d’informations avec le serveur suit une forme structurée, souvent sous le forme d’un texte avec des mots clefs et des conventions de forme. Cette structure s’appelle un protocole. Par exemple, le protocole pour surfer sur le Web est HTTP. Pour demander une page web, on envoie une requète du type GET nom/de/la/page HTTP/1.1 1 Bien qu’en général une machine ne possède qu’une seule adresse, il peut arriver qu’il y en ait plusieurs — par exemple si cette machine possède plusieurs cartes Ethernet. 2 Ceci est dû à des limitations internes à Windows qu’il est coûteux de contourner d’autant plus que, selon l’opinion — que je partage — du principal développeur d’OCaml : « Windows 9x [...] is an horrible piece of software that should die as quickly as possible ». 2/4 Client de « chat » élémentaire Analyse I suivit d’une ligne blanche et le serveur renvoie la page HTML précédée d’un entête donnant diverses informations. Cet entête est séparé de la page HTML par une ligne blanche. Il faut faire attention que tous les retours de ligne doivent être indiqués par "\r\n" et non simplement par "\n". Voici le code qui se connecte à la machine machine sur le port port, demande la page file, lit les données renvoyées par le serveur ligne par ligne (grâce à input_line) jusqu’à la première ligne vide (s = "") et les affiche (par la fonction print_endline) au fur et à mesure à l’écran. Comme la fonction input_line ne supprime que le "\n" à la fin de la ligne, on utilise String.sub pour enlever le "\r". let header machine port file = let addr = (Unix.gethostbyname machine).Unix.h_addr_list.(0) in let sk = Socket.open_connection (Unix.ADDR_INET(addr, port)) in Socket.output_string sk ("GET /" ˆ file ˆ " HTTP/1.0\r\n\r\n"); Socket.flush sk; let rec read_more () = let s = Socket.input_line sk in let s = String.sub s 0 (String.length s - 1) in if s = "" then () else ( print_endline (machine ˆ "> " ˆ s); read_more() ) in read_more(); Socket.close sk On pourra par exemple utiliser cette fonction comme suit : let () = header "www.umh.ac.be" 80 "index.html" Le code se trouve dans le fichier client-demo.ml ci-joint. ➢ Pour le client de chat que nous allons concevoir, le principe est le même. La différence réside dans le protocole utilisé. Nous allons le décrire ci-dessous et dans la question suivante. Un serveur implantant ce protocole est à votre disposition sur la machine raid.swapping.umh.ac.be sur le port 9999. Comme cette machine n’est pas accessible de l’extérieur de l’université, le fichier objet server.cmo est joint. Pour le transformer en un exécutable, taper dans un shell ouvert dans le répertoire où il se trouve make exe (si vous possédez make) ou makeexe (fichier .bat contenu dans l’archive ; fonctionne uniquement sous windows). ➢ Revenons au protocole de chat. Toutes les commandes tiennent sur une ligne qui doit se terminer par "\n". La ligne commence par le nom de la commande suivie de ses arguments. Les arguments sont séparés du nom par un unique caractère blanc. Une ligne a donc la forme : NOM DE LA COMMANDE arguments \n La première chose à faire, une fois la connection établie, est d’envoyer son pseudonyme. Ceci se fait en envoyant la commande : NOM pseudonyme 3/4 Client de « chat » élémentaire Analyse I Le serveur répond alors par OK message ou KO message. La réponse OK message signifie que le peudonyme a été accepté et message est un message de bienvenue. Au contraire, KO message signifie que le pseudonyme a été rejeté et message explique alors pourquoi. La seconde commande que nous utiliserons ici permet de savoir qui est connecté. Il s’agit simplement de QUI Le serveur répond par QUI pseudo1 pseudo2 ... pseudon où les différents pseudonymes pseudoi sont séparés par des espaces. On vous demande : de faire une fonction qui se connecte au serveur raid.swapping.umh.ac.be, vous identifie, et retourne la liste des personnes connectées3 (sous forme d’une string list) ; d’utiliser la fonction ci-dessus pour faire un programme (exécutable) qui se connecte, vous identifie et affiche à l’écran la liste des personnes connectées ; d’utiliser la fonction ci-dessus pour faire une fonction est connecté : string -> bool qui dit si un copain (dont le nom est passé en argument) est oui ou non connecté. Question 3. Nous allons rajouter une troisième commande qui va vraiment permettre le dialogue. Pour envoyer un message à une autre personne, on enverra la commande MSG pseudonyme message où pseudonyme est le nom du récipiendaire du message et message est le texte à lui envoyer (qui ne peut contenir de \n mais ce n’est pas un problème vu qu’il s’agit de « chat », donc de messages courts). Le serveur répondra par OK message ou KO message selon que tout s’est bien passé ou non. À chacune des commandes que nous envoyons au serveur, après y avoir répondu, ce dernier nous transmettra des lignes du type MSG pseudonyme message où pseudonyme est l’expéditeur et message est le texte que celui-ci veut nous faire parvenir. Cette liste de messages sera terminée par la commande « FIN ». Grâce à ces nouvelles commandes, faire un programme (exécutable) qui lorsqu’il est lancé (a) demande à l’utilisateur de rentrer son peusonyme (et lui demande d’en changer si celui-ce n’est pas accepté) ; (b) permet d’envoyer et de recevoir des messages. On pourra aussi implanter une fonction broadcast : string -> unit, à l’intention de l’administrateur, qui envoie un message à toutes les personnes connectées. 3 Les fonctions de la question 1 peuvent vous être utiles... 4/4