Prises de communication
Nous avons vus aux chapitre 3 et 4 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. Ils n'induisent, en général pas d'améliorations
de performances dans la mesure où les processus communicants
partagent les mêmes ressource ; en particulier, le même
processeur. La troisième possibilité, que nous présentons dans
ce paragraphe, utilise les prises de communication ou sockets1. Cette possibilité vient du monde Unix. Elle permet la
communication entre processus tournant soit sur une même machine,
soit sur différentes machines.
Description et création
Une prise de communication a pour rôle dans l'existence
d'établir une communication avec une autre prise dans le but
de transférer des informations. Nous listons 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 (connect); une fois la connexion établie la communication est dans les deux sens.
Domaine
Suivant qu'une prise est destinée à la communication interne ou
externe elles appartiennent à 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
Quelque soit leur domaine, les sockets définissent certaines
propriétés de communications (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 (de type int) 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), arguments 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 standards, 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
;;
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. L'intérêt est de pouvoir utiliser les fonctions sur les fichiers du module Unix sur les sockets. 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.
correspondant au type suivant :
# type
sockaddr
=
ADDR_UNIX
of
string
|
ADDR_INET
of
inet_addr
*
int
;;
L'entier qui fait partie de l'adresse des sockets du domaine Internet
correspond au numéro de port.
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 X 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 type int) de la fonction
listen indique le nombre maximal de connexions en attente
toléré. 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ée 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ées la file d'attente des demandes de connexion.
Demande de connexion
La fonction duale de accept est ;
# Unix.connect
;;
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.
Communication
À partir du moment où une connexion est établie entre
deux sockets, les processus propriétaires de celles-ci peuvent communiquer dans les deux sens. Les fonctions d'entrées-sorties sont celles du module Unix
décrites au chapitre 3.