Author: Christian.Queinnec@lip6.fr

## 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, 12345)) ;;
    - : 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.