Protocoles de communication
Les différentes communications des clients-serveurs décrits à la section précédente consistent à envoyer une chaîne de caractères terminée par un retour-chariot et à en recevoir une autre. Bien que simples ce sont déjà des protocoles. En effet un récepteur s'attend à recevoir une chaîne terminée par un caractère spécial. Dès que l'on veut communiquer des valeurs plus complexes : un flottant une matrice de flottants, un arbre d'expressions arithmétiques, une fermeture ou
un objet, se pose le problème du codage de ces valeurs. Plusieurs solutions existent selon la nature des programmes communicants que l'on caractérise par : le langage d'implantation, l'architecture machine
et dans certains cas le système d'exploitation. En effet selon
l'architecture machine les entiers seront représentés de différentes manières (bits de poids fort à gauche, à droite, bits de tag, taille du mot machine). Comment alors communiquer une valeur entière cohérente pour d'autres programmes sur d'autres architectures. Il devient alors nécessaire d'avoir une représentation ``externe''
des valeurs2. De plus pour les valeurs plus structurées comme un enregistrement, là aussi la représentation interne doit pouvoir <<s'externaliser>> mais de plus certains langages autorisent des constructions, comme les champs de bits, qui n'existent pas dans les autres langages. Le passage de valeurs fonctionnelles ou d'objets, qui contiennent une partie
code, pose une nouvelle difficulté sur le chargement de ce code. Celui-ci est-il compatible (code-octet) entre l'envoyeur et le récepteur, et existe-t-il un mécanisme de chargement dynamique de code? En règle générale on simplifiera ce problème en supposant que le code existe des deux cotés et celui-ci ne sera pas transmis. Ce qui signifie que pour les objets la
même classe existe chez le receveur. Pour les fermetures, que le code correspondant au function existe et est à la même place, ce qui implique que ce
sont deux mêmes programmes qui communiquent.
Les protocoles texte, c'est à dire la communication en format ASCII,
sont les plus courants car les plus simples à mettre en oeuvre et
les plus portables. Par contre quand le protocole se complexifie ils
peuvent être assez lourds à implanter. Dans ce cadre là on définit une
grammaire pour décrire le format de communication. Celui peut être
riche, mais ce sera aux programmes communicants de faire le travail de
codage et d'interprétation des chaînes reçues.
En règle générale, l'utilisation d'une application réseau ne permet
pas voir les différentes couches de protocoles utilisées. C'est
typiquement le cas du protocole HTTP permettant à un
navigateur de communiquer avec un site Web.
Protocole HTTP
On rencontre fréquemment le terme <<HTTP>> dans les publicités. Il correspond au protocole de communication
des applications du Web.
Celui-ci est complètement décrit sur la page du consortium W3 :
Lien
http://www.w3.org
Il est utilisé pour transporter les requêtes émises par les navigateurs (Explorer, Communicator, Opera, etc) et pour retourner le contenu de la page demandée.
Une requête effectuée par un navigateur contient le nom du protocole (HTTP), le nom de la machine (www.ufr-info-p6.jussieu.fr), et l'arboressence de la page demandée
(/Public/Localisation/index.html). Cet ensemble de trois composants permet de définir une URL (Uniform Ressource Locators) :
http://www.ufr-info-p6.jussieu.fr/Public/Localisation/index.html
permettant d'avoir accès aux ressources du Web.
Quand une telle URL est demandée à partir d'un navigateur, une connexion via une socket
est établie entre le navigateur et le serveur s''exécutant sur la machine indiquée
par défaut sur le port 80.
Puis le navigateur envoie une requête au format HTTP, proche de la suivante :
GET /index.html HTTP/1.0
Alors le serveur lui répond dans le protocole HTTP, l'entête :
HTTP/1.1 200 OK
Date: Wed, 14 Jul 1999 22:07:48 GMT
Server: Apache/1.3.4 (Unix) PHP/3.0.6 AuthMySQL/2.20
Last-Modified: Thu, 10 Jun 1999 12:53:46 GMT
ETag: "4c801-e4f-375fb55a"
Accept-Ranges: bytes
Content-Length: 3663
Connection: close
Content-Type: text/html
Cet entête indique que la requête est acceptée (code 200 OK), la nature du serveur, la date
de modification de la page, la longueur de la page envoyée et son type du contenu qui va suivre.
Sans la précision de la version de protocole (HTTP/1.0) de la commande GET, seule la page HTML est transférée. La connexion suivante avec telnet permet de voir ce qui est effectivement transmis :
$ telnet www.ufr-info-p6.jussieu.fr 80
Trying 132.227.68.44...
Connected to triton.ufr-info-p6.jussieu.fr.
Escape character is '^]'.
GET
<!-- index.html -->
<HTML>
<HEAD>
<TITLE>Serveur de l'UFR d'Informatique de Pierre et Marie Curie</TITLE>
</HEAD>
<BODY>
<IMG SRC="/Icons/upmc.gif" ALT="logo-P6" ALIGN=LEFT HSPACE=30>
Unité de Formation et de Recherche 922 - Informatique<BR>
Université Pierre et Marie Curie<BR>
4, place Jussieu<BR>
75252 PARIS Cedex 05, France<BR><P>
....
</BODY>
</HTML>
<!-- index.html -->
Connection closed by foreign host.
La connexion se referme dès que la page est copiée.
On s'aperçoit qu'un navigateur reçoit une telle information et arrive
à l'afficher de manière formatée. Le protocole de base est en mode
texte, et le langage à interpréter aussi. On note que les images ne
sont pas transmises avec la page. C'est au navigateur, lors de
l'analyse syntaxique de la page HTML, de remarquer les
ancres et les images (voir la balise IMG de la page transmise).
À ce moment là, le navigateur envoie une nouvelle requête par image
rencontrée dans le source HTML. Quand
ces images sont transmises, elles sont affichées. Il y a donc une nouvelle
connexion par image. C'est pour cela que souvent les images
s'affichent en parallèle.
Le protocole HTTP est ``assez'' simple, mais il véhicule une
information dans le langage HTML qui est plus complexe et
peut être source de nombreuses nouvelles connexions.
La plupart des navigateurs lancent d'ailleurs les différentes connexions de manière concurrente.
Protocoles avec acquittement et limite de temps
Quand le protocole est complexe, il est utile que le récepteur d'un
message indique à l'envoyeur qu'il a bien reçu le message et que
celui-ci est grammaticalement correct. En effet le client peut être en
attente bloquante de réponse avant de poursuivre ses taches. Si la
partie du serveur traitant cette requête a une difficulté
d'interprétation du message, le serveur doit l'indiquer au client
plutôt que d'oublier cette requête. L'exemple du protocole
HTTP a une gestion de code d'erreurs. Une requête correcte
renvoie le code 200. Une requête mal formée ou la demande d'accès à
une page non autorisée renvoie un code d'erreur 4xx ou 5xx selon la
nature de l'erreur. Ces codes d'erreur permettent au client de savoir
ce qu'il a à faire et au serveur de tenir des fichiers de compte-rendu
précis sur la nature des requêtes.
Lorsqu'un serveur est dans un état incohérent, il peut toujours
accepter une connexion d'un client, mais risque de ne jamais lui
envoyer de réponse sur la socket. Pour éviter ces attentes
bloquantes, il est utile de fixer une limite de temps pour la
transmission de la réponse. Passé ce délai, le client suppose que le
serveur ne répondra plus. Alors le client peut fermer cette connexion
pour passer à la suite de son travail. C'est ainsi que les
navigateurs WWW font. Quand une requête n'a pas de réponse au
bout d'un certain temps, le navigateur décide de l'indiquer à
l'utilisateur. Objective CAML possède des entrées-sorties avec limite de
temps. Dans le module Thread, les fonctions
wait_time_read et
wait_time_write qui suspendent l'exécution de la tache
jusqu'au moment où un caractère peut être lu ou écrit, cela dans un
certain délai. Elles prennent en entrée un descripteur de fichier et
un délai exprimé en secondes :
Unix.file_descr -> float -> bool.
Quand le retour vaut true alors l'entrée-sortie peut
s'effectuer, sinon le délai a été dépassé.
Transmission de valeurs en représentation interne
Les difficultés inhérentes à l'envoi-réception de valeurs en
représentation interne sont les mêmes que celles rencontrées pour les
valeurs persistantes (voir le module Marshal page
). En effet stocker une valeur dans un fichier
puis la relire plus tard du même ou d'un autre programme revient au
même que d'envoyer une valeur sur une socket et de la lire à
l'autre bout de la prise. Il faut donc alors que les deux programmes
connaissent le nom du la valeur, pour que le receveur puisse forcer
la valeur transmise, et que ce nom corresponde bien à la même
définition. L'intérêt est de simplifier le protocole. Il n'y a plus
besoin de coder des données dans un format texte. Par contre le typage
est moins sûr car rien n'empêche d'écrire un client se connectant à un
tel serveur et lui envoyant une donnée d'un autre type. De plus cela
limite les communications à des programmes écrits en Objective CAML. On
montre sur deux exemples comment effectuer cette communication.
Exemple : service calcul matriciel
On définit un petit service de calcul matriciel. Le client envoie au
serveur le nom de l'opération désirée (par exemple ``PLUS'' ou
``MULT'') ainsi que les matrices en représentation interne. Le serveur
renvoie le résultat de ce calcul. On reprend le squelette du serveur
``majuscules'' en changeant la fonction de traitement.
Valeurs fonctionnelles
Dans le cas de transmission d'une fermeture entre deux programmes
Objective CAML, le code de la fermeture n'est pas envoyé, seul son
environnement et son pointeur de code (voir )
le sont. Pour que cela fonctionne il est nécessaire que le serveur
possède le même code au même emplacement. Ce qui implique que le même
programme tourne en tant que serveur et en tant que client. Rien
n'empêche cependant que ces deux programmes soient en train d'exécuter
des parties de code différentes. On adapte alors le service ``calcul
matriciel'' en envoyant la fermeture contenant dans son environnement
les données à calculer. À la réception le serveur applique cette
fermeture à () et le calcul se déclenche.
Pour améliorer ce service, on peut définir un type pour la gestion des
exceptions du coté du serveur :
# type
'a
valeur
=
V
of
'a
|
E
of
exn;;
type 'a valeur = | V of 'a | E of exn
Le serveur transmet alors ue valeur de ce type. Le client la filtre et
selon le cas utilise la valeur retournée ou déclenche l'exception
transmise.
Inter-opérer avec d'autres langages
L'intérêt des protocoles texte est qu'ils sont indépendant des
langages d'implantation des clients et des serveurs. En effet le code
ASCII est toujours connu des langages de
programmation. Ensuite c'est au client et au serveur de savoir
analyser syntaxiquement les chaînes de caractères transmises. On
discute sur l'exemple d'un tel protocole ouvert est la simulation de
joueurs de football, appelée RoboCup.
Robots footballeurs
Une équipe de foot joue contre une autre équipe. Chaque membre d'une équipe est un client du serveur ``arbitre''. Les joueurs d'une même équipe ne peuvent pas communiquer directement entre eux. Ils doivent passer par le serveur,
qui retransmet le dialogue. Le serveur indique selon la position du joueur
une partie du terrain. Toutes ces communications s'effectuent selon un protocole texte. La page de présentation du protocole, du serveur et de certains clients :
Lien
http://www.robocup.org/
Le serveur est écrit en C. Les clients sont écrits dans différents langages :
C, C++, Smalltalk, Objective CAML, ...Rien n'empêche d'ailleurs à une équipe de posséder des joueurs issus de différents langages.
Ce protocole répond bien aux besoins d'inter-opérabilité entre programmes de
différents langages d'implantation. Il est relativement simple, mais il nécessite un analyseur syntaxique
particulier pour chaque famille de langages.