Prises de communication
Nous avons vus aux chapitres 18 et 19 deux moyens
de communication entre processus, respectivement : les tubes et les
canaux. Ces deux premières méthodes utilisent un modèle logique de
concurrence. Elles n'induisent en général pas d'amélioration de
performances dans la mesure où les processus communicants partagent
les mêmes ressources ; en particulier, le même processeur. La troisième
possibilité, que nous présentons dans ce paragraphe, utilise les
prises de communication. Cette possibilité vient du
monde Unix. Elle permet la communication entre processus s'exécutant soit
sur une même machine, soit sur des machines différentes.
Description et création
Une prise de communication a pour rôle
d'établir une communication avec une autre prise dans le but
de transférer des informations. Nous énumérons
les différentes situations que l'on peut
rencontrer ainsi que les commandes et types qui seront utilisés
par les sockets TCP/IP.
La métaphore classique est de comparer les sockets
aux postes téléphoniques.
-
Pour fonctionner ils doivent être raccordés au réseau (socket).
- Pour recevoir un appel ils doivent posséder un numéro de type
sock_addr (bind).
- Pendant un appel, il est possible de recevoir un autre appel si la configuration
le permet (listen).
- Il n'est pas nécessaire d'avoir soi-même un numéro pour appeler
un autre poste, une fois la connexion établie la communication est
dans les deux sens (connect).
Domaine
Suivant qu'une prise est destinée à la communication interne ou
externe elle appartient à un domaine différent. La
bibliothèque Unix définit deux domaines possibles
correspondant aux constructeurs du type :
# type
socket_domain
=
PF_UNIX
|
PF_INET;;
Le premier domaine correspond à la communication locale et le
second, à la communication transitant sur le réseau Internet. Ce
sont là les principaux domaines d'application des sockets.
Nous n'utiliserons dans la suite que les sockets
du domaine Internet.
Type et protocole
Quel que soit leur domaine, les sockets définissent certaines
propriétés de communication (fiabilité, ordonnancement, etc.)
représentées par les constructeurs du type :
# type
socket_type
=
SOCK_STREAM
|
SOCK_DGRAM
|
SOCK_SEQPACKET
|
SOCK_RAW
;;
Suivant le type de socket utilisé, le protocole sous-jacent à la
communication obéira aux caractéristiques définies. À chaque
type de communication est associé un protocole par défaut.
En fait, nous n'utiliserons ici que le premier type de communication :
SOCK_STREAM avec le protocole par défaut TCP. Il garantit
fiabilité, ordonnancement et non duplication des messages
échangés et fonctionne en mode connecté.
Pour le reste, nous
renvoyons le lecteur à la littérature sur Unix, par exemple
[Rif90].
Création
La fonction de création d'une socket est :
# Unix.socket
;;
- : Unix.socket_domain -> Unix.socket_type -> int -> Unix.file_descr = <fun>
Le troisième argument permet de préciser le
protocole associé à la communication. La valeur 0 est
interprétée comme << le protocole par défaut >> associé au couple
(domaine, type), argument de la création de la socket.
La valeur de retour de cette fonction est un
descripteur de fichier. Ainsi les échanges pourront se faire en
utilisant les fonctions standard du module Unix
d'entrées-sorties.
Créons une socket TCP/IP :
# let
s_descr
=
Unix.socket
Unix.
PF_INET
Unix.
SOCK_STREAM
0
;;
val s_descr : Unix.file_descr = <abstr>
Warning
Bien que la fonction socket retourne une valeur de type
file_descr, le système fait la différence entre un
descripteur de fichier classique et celui attribué à une
socket.
On peut utiliser les fonctions sur les fichiers du module
Unix avec les sockets ; mais une exception est
déclenchée dans le cas où un descripteur classique est passé à une
fonction attendant une socket.
Fermeture
Comme tout descripteur de fichier, une
socket se ferme par la fonction :
# Unix.close
;;
- : Unix.file_descr -> unit = <fun>
La sortie par exit d'un processus ferme les descripteurs de fichier encore ouverts.
Adresses et connexions
Une socket que l'on vient de créer ne possède pas d'adresse.
Pour qu'une connexion puisse s'établir entre deux sockets, l'appelant doit connaître
l'adresse du récepteur.
L'adresse d'une socket (TCP/IP) est constituée d'une adresse IP et d'un numéro de port. Une socket du domaine Unix
est simplement constituée d'un nom de fichier.
# type
sockaddr
=
ADDR_UNIX
of
string
|
ADDR_INET
of
inet_addr
*
int
;;
Attribution d'une adresse
La première chose à faire pour pouvoir recevoir des appels après
la création d'une socket est
de lui attribuer (de la lier à) une adresse. C'est le rôle
de la fonction :
# Unix.bind
;;
- : Unix.file_descr -> Unix.sockaddr -> unit = <fun>
En effet, nous avons déjà un descripteur de socket, mais l'adresse
qui lui est attachée à la création ne peut guère nous être
utile comme le montre l'exemple suivant :
# let
(addr_in,
p_num)
=
match
Unix.getsockname
s_descr
with
Unix.
ADDR_INET
(a,
n)
->
(a,
n)
|
_
->
failwith
"pas INET"
;;
val addr_in : Unix.inet_addr = <abstr>
val p_num : int = 0
# Unix.string_of_inet_addr
addr_in;;
- : string = "0.0.0.0"
Reste donc à fabriquer une adresse utile et à l'associer à notre
socket. Nous reprenons notre adresse locale my_addr obtenue
page ?? et choisissons le port 12345 qui, en général, n'est pas
attribué.
# Unix.bind
s_descr
(Unix.
ADDR_INET(my_addr,
1
2
3
4
5
))
;;
- : unit = ()
Capacité d'écoute et réception
Il faut encore procéder à deux opérations avant que notre socket
soit complètement opérationnelle pour recevoir des appels : définir sa capacité
d'écoute et se mettre effectivement à l'écoute. C'est le rôle
respectif des deux fonctions :
# Unix.listen
;;
- : Unix.file_descr -> int -> unit = <fun>
# Unix.accept
;;
- : Unix.file_descr -> Unix.file_descr * Unix.sockaddr = <fun>
Le second argument de la fonction
listen indique le nombre maximal de connexions toléré en attente.
L'appel de la fonction accept attend une demande de
connexion. Quand celle-ci survient la fonction accept se
termine et retourne l'adresse de la socket ayant appelé ainsi
qu'un nouveau descripteur de socket, dite socket
de service. Cette socket de service est automatiquement liée
à une adresse. La fonction accept ne s'applique qu'aux
sockets ayant exécuté un listen, c'est-à-dire aux
sockets ayant paramétré la file d'attente des demandes de
connexion.
Demande de connexion
La fonction duale de accept est ;
# Unix.connect
;;
- : Unix.file_descr -> Unix.sockaddr -> unit = <fun>
Un appel à Unix.connect
s_descr
s_addr établit un connexion
entre la socket locale s_descr (qui est automatiquement
liée) et la socket d'adresse s_addr (qui doit exister).
Communication
À partir du moment où une connexion est
établie entre deux sockets, les processus propriétaires de
ces dernières peuvent communiquer dans les deux sens. Les fonctions
d'entrées-sorties sont celles du module Unix décrites au
chapitre 18.