- Berkeley Sockets
-
Berkeley sockets
Les Berkeley sockets, que l'on pourrait traduire par « connecteurs réseau de Berkeley[1] », représentent une interface de programmation pour les communications entre processus — Interprocess communication — . Elles ont été introduites pour la première fois dans la version 4.3BSD (1986) de l'UNIX de l'université de Berkeley. Avant leur introduction, le seul mécanisme standard qui permettait à deux processus de communiquer se faisait par l'intermédiaire des pipes.
Sommaire
Généralités
La communication se fait à l'aide d'une socket. Une socket représente un point terminal d'un canal de communication entre deux ou plusieurs processus — un nom peut lui être attribué.
Chaque socket possède un type et un ou plusieurs processus qui lui sont associés. Elle est également caractérisée par le domaine de communication dans lequel elle se trouve. Ce dernier est une abstraction qui permet de regrouper les processus ayant des propriétés communes et communiquant par l'intermédiaire de sockets. Normalement, une socket ne peut échanger des données qu'avec une socket se trouvant dans le même domaine de communication.
La communication inter-processus de 4.3BSD supportait trois domaines de communication:
- le domaine Unix dans lequel deux processus se trouvant sur la même station Unix uniquement peuvent communiquer[2] ;
- le domaine Internet pour les processus utilisant le protocole TCP/IP pour communiquer entre eux ;
- le domaine NS pour les processus échangeant des données en utilisant le protocole standard de Xerox.
Types de sockets
Les différents types de sockets dépendent de quelques propriétés visibles par le programmeur. Rien n'empêche deux sockets de types différents de communiquer entre eux si le protocole utilisé le supporte — même si les processus sont supposés communiquer uniquement par des sockets de même type.
Il existe généralement quatre types de sockets.
Une socket stream permet une communication bidirectionnelle, sûre, séquencée et un flux de données sans duplication pouvant entraîner une fragmentation des paquets transmis. Dans le domaine Internet, il s'agit du protocole TCP.
Une socket datagram permet une communication bidirectionnelle qui n'est pas séquencée, pas sûre, et peut éventuellement entraîner une duplication des données. Un processus utilisant ce type de socket peut donc recevoir les données dans un ordre différent de l'ordre de départ. Dans le domaine Internet, il s'agit du protocole UDP.
Une socket raw permet aux utilisateurs d'accéder à des protocoles de communication différents en même temps. Les sockets raw ne sont pas destinées aux utilisateurs courants — seul l'utilisateur root peut y avoir accès sur la plupart des systèmes UNIX® — elles permettent d'avoir accès aux données "brutes" et sont utilisées par exemple pour analyser le trafic d'un réseau.
Une socket sequenced packet ressemble à une socket stream sauf qu'elle n'utilise pas de fragmentations de paquets.
Création d'une socket
La création d'une socket se fait à partir de l'appel système suivant:
#include <sys/types.h>
#include <sys/socket.h>
int s, domain, type, protocol; s = socket (domain, type, protocol);Cet appel système permet de créer une socket appartenant à domain et possédant la propriété type. Il renvoie un nombre entier — -1 en cas d'échec — qui peut être assimilé à un descripteur de fichier. Le champ protocol peut ne pas être spécifié — valeur égale à zéro. Dans ce cas, le système choisit un protocole approprié parmi les différents protocoles utilisés dans le domaine de communication considéré et supportant le type de socket requis.
Les champs domain et type sont choisis parmi les constantes définies dans le fichier <sys/socket.h>. Pour le domaine UNIX par exemple, la constante est AF_UNIX[3] — AF_INET pour le domaine internet et AF_NS pour le domaine NS. Les types de sockets sont les suivants: SOCK_STREAM, SOCK_DGRAM, SOCK_RAW, SOCK_NS. Par exemple pour créer une socket utilisant le protocole TCP, l'appel système suivant peut être effectué :
int s;
s = socket (AF_INET, SOCK_STREAM, 0);La plupart du temps, la valeur par défaut du champ protocol est suffisante — même si on peut lui attribuer une valeur particulière.
Attribution d'une adresse
Une socket est créée sans adresse et ne permet pas tout de suite d'échanger des données. Pour pouvoir y remédier, il faut lui associer une adresse. Pour les domaines Internet et NS, cette association est composée d'une adresse IP locale et d'un port local — tandis que pour le domaine UNIX, il s'agit d'un nom de fichier. Dans le domaine Internet, l'attribution d'une adresse à une socket peut être faite automatiquement par le système, et c'est généralement le cas pour un processus client, mais il est souvent nécessaire de l'expliciter dans le cas d'un processus serveur qui doit contrôler le port sur lequel il va écouter.
L'adresse générique d'une socket est définie par la structure suivante:
#define SOCK_MAXADDRLEN 255 /* adresse la plus longue possible */
struct sockaddr { unsigned char sa_len; /* longueur totale */ sa_family_t sa_family; /* famille d'adresse */ char sa_data[14]; /* valeur de l'adresse */ };Cette adresse n'est généralement pas directement utilisée en tant que telle — servant uniquement de référence générique pour les appels systèmes. À la place, une adresse plus spécifique est utilisée pour chaque domaine de communication. Pour le domaine internet par exemple, la structure suivante — définie dans le fichier netinet/in.h — remplace la version précédente:
struct sockaddr_in { uint8_t sin_len; /* longueur totale */ sa_family sin_family; /* famille d'adresse */ in_port_t sin_port; /* numero de port */ struct in_addr sin_addr; /* valeur sur 32 bits de l'adresse */ char sin_zero[8]; /* champ rempli de zeros */ };
De la même maniere, il existe également une adresse sockaddr_un et sockaddr_ns pour les domaines unix et NS.
L'association d'une socket avec une adresse se fait par l'intermediaire de l'appel système bind:
#include <sys/types.h>
#include <sys/socket.h>
int bind (int s, const struct sockaddr *addr, socklen_t addrlen);ou s est la socket associée avec l'adresse pointée par addr dont la longueur totale est addrlen. La fonction renvoie -1 en cas d'échec. Après avoir effectué cette operation, la socket est desormais referencée et peut donc recevoir et envoyer des données.
Exemple:
struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(1100); addr.sin_addr.s_addr = INADDR_ANY; if(-1 == bind(s,(const void *)&addr, sizeof(addr))) { perror("error bind failed"); close(s); exit(EXIT_FAILURE); }
Établissement d'une connexion
Pour des sockets de type SOCK_STREAM, l'établissement de la connexion nécessite un mécanisme différent pour le client et pour le serveur. Côté serveur, la socket écoute sur une adresse donnée (spécifiée par bind) par un appel à listen puis accepte chaque connexion entrante par un appel à accept:
int listen(int s, unsigned int maxconn); int accept(int s, struct sockaddr *addr, socklen_t *addrlen);
s désigne la socket serveur. maxconn spécifie le nombre maximum de connexions simultanées que pourra accepter la socket. Une valeur typique est 50. addr et addrlen reçoivent au retour de la fonction l'adresse de l'autre côté de la connexion. accept retourne une nouvelle socket qui sera utilisée pour la connexion — -1 en cas d'erreur. Ainsi, une même socket serveur peut avoir plusieurs connexions actives simultanément.
Exemple:
if(-1 == listen(s, 50)) { perror("error listen failed"); close(s); exit(EXIT_FAILURE); } for(;;) { int conn = accept(s, NULL, NULL); if(0 > conn ) { perror("error accept failed"); close(s); exit(EXIT_FAILURE); } /* Effectuer les opérations désirées sur la nouvelle socket conn... */ }
Côté client, la connexion est effectuée par l'appel à connect:
#include <sys/socket.h> int connect(ins s, struct sockaddr *addr, socklen_t addrlen);
où s est la socket à connecter, et addr l'adresse de destination à laquelle se connecter. Si aucun appel à bind n'a été effectué précédemment sur la socket, l'adresse locale (et en particulier le numéro de port) est choisie par le système.
Exemple:
struct sockaddr_in addr; addr.sin_family = PF_INET; addr.sin_port = htons(1100); inet_pton(PF_INET, "192.168.1.3", &addr.sin_addr); if(-1 == connect(s, (const void *)&addr, sizeof(addr))) { perror("connect failed"); close(s); exit(EXIT_FAILURE); }
Pour une socket de type SOCK_DGRAM (UDP), Il n'y a pas de connexion à proprement parler. L'appel à connect est néanmoins possible et spécifie l'adresse de destination par défaut de la socket.
Transfert de données
L'envoi de données est effectué par l'appel à send et la reception par l'appel à recv:
#include <sys/socket.h> int send (int s, void *buffer, size_t size, int flags); int recv (int s, void *buffer, size_t size, int flags);
Pour des sockets de type SOCK_DGRAM, non connectées, il peut être nécessaire de préciser l'adresse de destination lors de l'envoie et de récupérer l'adresse source lors de la réception. Pour cela, on utilise les fonctions sendto et recvfrom:
#include <sys/socket.h> int sendto (int s, void *buffer, size_t size, int flags, struct sockaddr *addr, socklen_t length); int recvfrom (int s, void *buffer, size_t size, int flags, struct sockaddr *addr, socklen_t *length);
Notes
- ↑ socket : traduction et genre sur la liste de diffusion traduc.org (29 Juillet 2004)
- ↑ Les processus communiquant via NFS ne font pas partie de ce domaine.
- ↑ AF pour address family
Liens externes
- Les sockets en Langage C
- Guide pour la programmation réseaux, Unix (Utilisation des sockets Internet)
- Portail de l’informatique
Catégorie : Programmation informatique
Wikimedia Foundation. 2010.