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