Vous êtes sur la page 1sur 13

Les Sockets TCP/IP

Numéro de session

Intitulé de la session

1

Généralités

2

Initialisation

3

Le mode connecté

-

Présentation

-

Primitives

4

Le mode datagramme

-

Présentation

-

Primitives

5

Architecture des serveurs

6

Compléments

1 Session 1 : Généralités

1.1 Définition :

Les sockets sont apparues sous UNIX en 1981, elles forment un composant de base de la communication entre processus (processus situés sur une même machine ou sur deux machines distinctes). Elles fournissent un accès aux protocoles de la couche transport. Une socket est une extrémité de connexion (assimilable à une prise électrique femelle) à laquelle on peut associer un nom. Elle peut être de différents types et être associée à plusieurs processus.

Elles peuvent être assimilées à :

Une interface :

Une socket peut être représentée comme une interface entre les programmes d'application et les protocoles de communication.

Une abstraction :

Une socket peut s’apparenter à un canal de communication logique, accessible par un processus.

Des primitives :

- pour attribuer aux processus un rôle de client ou de serveur,

- pour échanger des messages protocolaires en rendant transparents les services de

communication réseau,

- pour manipuler des informations de communication,

- pour contrôler et paramétrer les sockets.

Caractéristiques générales des sockets 1.2 L'abstraction pour le programmeur Vision réseau : C’est une

Caractéristiques générales des sockets

1.2 L'abstraction pour le programmeur

Vision réseau :

C’est une association d'application représentée par le quintuplet suivant :

- adresse IP locale,

- numéro de port local,

- adresse IP distante,

- numéro de port distant

- mode de communication : protocole TCP ou UDP.

Vision système :

Une socket est gérée en mode FIFO, on y associe également un descripteur.

Socket = extrémité d'association + identificateur logique

2

Session 2 : Initialisation

2.1 Création d’une socket

La primitive socket() demande au système la création d'une socket devant fonctionner selon un mode de communication (datagramme ou connecté) dans un domaine spécifié (AF_INET ou AF_UNIX). Le système retourne un descripteur de fichier qui permet d'identifier logiquement ce canal de communication.

Syntaxe :

int socket(domaine,type,protocole) :

int domaine, /*AF_INET ou AF_UNIX. */ int type, /*SOCK_STREAM ou SOCK_DGRAM. */ int protocole ; /*code du protocole à utiliser , 0 pour détermination automatique.*/

Valeur de retour : entier qui identifiera la socket dans tout le reste du programme (descripteur de socket).

Exemple :

int sid ; sid = socket(AF_INET, SOCK_STREAM, 0) ;

2.2 Association d'adresse

La primitive bind() permet d’associer une adresse locale au descripteur de socket créé par socket(). Cette opération est facultative pour les clients car l’adresse est attribué au moment de la connexion avec le serveur si le bind() n’a pas été effectué. Pour les sockets TCP/IP, il est possible de donner la valeur 0 pour le numéro de port. Le système attribue alors un numéro que l’on peut retrouver par la primitive getsockname().

Syntaxe :

int bind(sock, localaddr, addrlen),

:

int sock, /*descripteur de socket*/ struct sockaddr_in *localaddr, /*adresse de socket locale*/ int addrlen ; /*longueur de l’adresse*/

Valeur de retour : nombre négatif si l’opération s’est mal déroulée.

Remarque : Avant d’appeler cette primitive, il est nécessaire d’initialiser les champs de la structure de type sockaddr_in à zéro avec la primitive bzero(), puis de donner les valeurs correctes des champs suivants :

- le domaine de communication

- le numéro de port (soit fixé , soit 0)

- l’adresse internet de la machine

Syntaxe de la primitive bzero() qui remet à zéro les nboctets à partir de l’adresse addr_struct : bzero(addr_struct, nboctets), char *addr_struct, /*adresse de la socket locale*/ int nboctets; /*taille de la structure*/

Exemple :

struct sockaddr_in s_addr ; /* initialisation des champs de s_addr à zero */ bzero((char*)&s_addr, sizeof(s_addr)); /* initialisation des champs de s_addr aux valeurs correctes */ bind(sid, &s_addr, sizeof(s_addr));

valeurs correctes */ bind(sid, &s_addr, sizeof(s_addr)); Initialisation 1 Initialisation 2 3 Session 3 : Le mode

Initialisation 1

*/ bind(sid, &s_addr, sizeof(s_addr)); Initialisation 1 Initialisation 2 3 Session 3 : Le mode connecté 3.1

Initialisation 2

3 Session 3 : Le mode connecté

3.1 Présentation

L’échange d’informations en mode connecté s’assimile à un flot bidirectionnel d'octets, transférés de façon fiable et ordonnée. Il n’y a ni perte, ni duplication de données. Pour une communication en mode connecté on utilisera le service TCP 3 phases sont alors nécessaires :

- l’établissement de connexion

- le transfert de données

- la libération de connexion

Cependant, il faut d’abord avoir créé la socket et lui avoir associé une adresse.

Au préalable, création et caractérisation des sockets (voir initialisation) = = >MODE d’INTERACTION ASYNCHRONE permis

3.2 Les primitives

Etablissement de la connexion

Le client doit initier l'interaction, pour se faire il demande un établissement de connexion grâce à la primitive connect(). Cette primitive permet de spécifier les caractéristiques de l'extrémité de la connexion distante ; c’est à dire l’adresse IP et le numéro de port associé à la socket du serveur.

Syntaxe :

int connect (sock, serv_addr, addrlen), int sock, /*descripteur de socket*/ struct sockaddr_in *servaddr, /*adresse de la socket du serveur*/ int addrlen ; /*longueur de l’adresse*/

Valeur de retour : un entier négatif si la connexion n’a pu être réalisé.

Remarque :

Avant d’appeler cette primitive, il est nécessaire d’initialiser les champs de la structure servaddr à zéro, puis de fixer les valeurs correctes pour les champs suivants :

- le domaine de communication

- le numéro de port (soit fixé par programme, soit 0)

- l’adresse Internet de la machine

Exemple :

struct sockaddr_in s_addr ; /* initialisation des champs de s_addr à zero */ /* initialisation des champs de s_addr aux valeurs correctes */ connect (sid, &s_addr, sizeof(s_addr)) ;

Le serveur doit être capable de considérer des demandes de connexion, on dit qu’il écoute sur une socket « générique ».

La primitive listen() permet au processus serveur de se connecter sur la socket et de se bloquer jusqu'à l'arrivée d'une demande de connexion entrante. On parle d’ouverture passive.

Syntaxe :

int sock, /*descripteur de socket*/ int qlen ; /*nombre maximum de demande de connexion qui peuvent être mises en attente (entre 1 et 20)*/

int listen(sock, qlen),

Cette fonction indique que le serveur est prêt à recevoir au maximum qlen demandes de connexion

Valeur de retour : Néant.

Exemple :

listen(sid, 1) ;

Le processus serveur doit ensuite accepter une demande de connexion, et différencier la connexion ainsi établie.

La primitive accept() permet au serveur d'extraire une requête de connexion de la file d'attente, et crée une nouvelle socket pour chaque connexion. Chaque nouvelle socket est créée avec un descripteur logique spécifique affecté par le système : ce descripteur est utilisé pour la suite des opérations sur cette connexion.

Syntaxe :

int accept(sock, addr_dist, addrlen) int sock: /*descripteur de socket*/ struct sockaddr_in *addr_dist, /*adresse du socket distant*/ int addrlen ; /*longueur de l’adresse*/

Valeur de retour : cette fonction retourne un entier négatif si la connexion n’a pu être réalisé.

Exemple :

int newsock ; int sid ; struc sockaddr_in c_addr ; newsock = accept(sid, &c_adr, (sizeof(c_adr));

Transfert de données

Il permet de supporter les différents échanges entre protocoles applicatifs. Pour réaliser les échanges protocolaires le client utilise sa socket tandis que le serveur utilise la socket spécifique à cette connexion.

Emission La primitive send() permet l'envoi de données sans préciser l’adresse car elle a déjà été précisée lors de la connexion.

Syntaxe :

int send(sock, mess, longmess, 0), int sock, /*descripteur de socket*/ char *mess, /*message à expédier*/ int longmess ; /*nombre de caractères*/

Cette primitive permet d’envoyer sur la socket sock un message mess de longmess caractères.

Remarque :

La fonction d’écriture revient dès que le buffer à écrire a été transmis à UDP.

La primitive write() permet également l'envoi de données à travers le réseau. Elle est identique à la fonction liée aux entrées-sorties «classiques» et comporte les mêmes arguments (descripteur de socket, adresse du tampon où sont stockées les données et taille de ce tampon).

Syntaxe :

int write(sock, mess, longmess), int sock, /*descripteur de socket*/ char *mess, /*message à expédier*/ int longmess ; /*nombre de caractères*/

Valeur de retour : nombre de caractères envoyés.

Réception

La primitive recv() permet la réception de données.

Syntaxe :

int recv(sock, mess, longmess, 0), int sock, /*descripteur de socket*/ char *mess, /*stockage du message reçu*/ int longmess ; /*nombre d’octets à prélever*/

Cette primitive permet de retirer sur la socket sock un message mess de longmess caractères

Valeur de retour :

- = -1 : si la connexion est rompue ou n’existe pas

- = >0 : un certain nombre d’octets a été reçu

- = 0 : l’application distante a fait un close() ou s’est arrêté

La primitive read() permet également la réception de données. Elle se comporte de la même façon que la fonction liée aux entrées-sorties «classiques» et prend les mêmes arguments (descripteur de socket, adresse du tampon où stocker les caractères reçus, et taille de ce tampon). Elle renvoie le nombre de caractères lus (toujours inférieur ou égal à la taille donnée pour le buffer).

Syntaxe:

int read(sock, mess, longmess), int sock, /*descripteur de socket*/ char *mess, /*stockage du message reçu*/ int longmess ; /*nombre d’octets à prélever*/

Valeur de retour : nombre de caractères reçus.

Ces primitives sont bloquantes tant que rien n'est à prélever (le buffer d’entrée est vide).

Libération de connexion

Client et serveur doivent « libérer » chacun leur extrémité de connexion. La primitive close() permet de fermer un descripteur de socket. La communication ne sera fermée réellement que s'il n'y a plus de processus utilisant cette connexion. Le noyau essaie d’envoyer les données non émises avant de sortir du close().

Syntaxe :

int close(sock) ;

Exemple :

Int sid ; Close (sid) ;

Récapitulatif des primitives en mode connecté

Récapitulatif des primitives en mode connecté

4

Session 4 : Le mode datagramme

4.1

Présentation

Contrairement au mode connecté, le mode datagramme ne comporte qu’une seule phase : le transfert de données. Le protocole utilisé est UDP, le transfert est donc non fiable. Le client initie l'interaction, envoi de message protocolaire.

Transfert de données

Emission

La primitive sendto() permet l'envoi de données en précisant l'adresse du destinataire : on spécifie les caractéristiques de l'extrémité de connexion distante, à savoir l’adresse IP et le numéro de port de la socket du serveur.

Syntaxe :

int sendto (sid, msg, lg, 0, dest, lgdest), int sid, /*descripteur de socket*/ char *mess, /*message à envoyer*/ int lg, /*longueur du message*/ struct sockaddr_in *dest, /*descripteur du socket destinataire*/ int lgdest ; /*longueur de l’adresse destinataire*/

Remarque :

Il faut auparavant initialiser l’adresse de la socket destinataire. La fonction d’écriture revient dès que le buffer à écrire a été transmis à UDP.

Réception

Le serveur attend des messages sur sa socket, réception de messages protocolaires. La primitive recvfrom() permet la réception de données en récupérant l'adresse de l'envoyeur : récupération des caractéristiques de l'extrémité de connexion distante ,à savoir l’adresse IP et la numéro de port de la socket du client.

Syntaxe :

int recvfrom (sid, msg, lg, 0, emet, lgemet) ; int sid, /*descripteur de socket*/ char *mess, /*message à envoyer*/ int lg, /*longueur du message*/ struct sockaddr_in *emet, /*descripteur du socket émetteur*/ int lgdemet ; /*longueur de l’adresse émetteur*/

Valeur de retour :

- = -1 : si la connexion est rompue ou n’existe pas,

- = >0 : un certain nombre d’octets a été reçu,

- = 0 : l’application distante a fait un close() ou s’est arrêtée.

L’approche est symétrique pour le transfert de la réponse.

On parle de mode d’interaction SYNCHRONE.

4.2

Principe

On parle de mode d’interaction SYNCHRONE. 4.2 Principe Récapitulatif des primitives en mode datagramme

Récapitulatif des primitives en mode datagramme

5

Session 5 : Architecture des serveurs

5.1 Serveur Itératif : un client à la fois

Un processus serveur offre via une socket une connectivité sur le réseau et entre indéfiniment dans un processus d'attente de requêtes clientes. Lorsqu’une requête arrive sur la socket, le processus déclenche le traitement de la requête puis émet le résultat vers le client. Le serveur traite les requêtes clientes séquentiellement.

CLIENTS TRAITÉS EN SÉQUENCE, PROCESSUS UNIQUELe serveur traite les requêtes clientes séquentiellement. 5.2 Serveur Concurrent : plusieurs clients à la fois

5.2 Serveur Concurrent : plusieurs clients à la fois

Un processus serveur offre, via une socket, une connectivité sur le réseau. Répétitivement il réceptionne une initiative d'interaction d'un client, offre une nouvelle socket et crée un processus secondaire chargé de traiter l'interaction courante.

CLIENTS TRAITÉS EN PARALLELE,secondaire chargé de traiter l'interaction courante. 5.3 Serveur Multi Protocoles Le même service est offert

5.3 Serveur Multi Protocoles

Le même service est offert simultanément sur plusieurs protocoles de communication. Exemple: DAYTIME port 13 sur UDP et TCP.

Ce type de serveur permet de satisfaire les clients qui ont des exigences différentes de transfert (fiabilité). On distingue 2 approches d'implémentation :

- un serveur par protocole, l'un servant les requêtes TCP, l'autre les requêtes UDP. Cette approche est plus aisément contrôlable (accès UDP interdits sur système) - un seul et même serveur capable de servir à la fois les requêtes TCP et UDP :

- non duplication des ressources, cohérence dans la gestion du service

- implémentation possible en mode itératif et en mode concurrent.

5.4 Serveur Multi Services

Un même serveur offre plusieurs services.Un tel service permet de diminuer le nombre de processus et de ressources consommées.

- Serveur multi services et multi protocoles :

- cas du daemon inetd « super serveur Internet » sous UNIX

- interface de configuration (/etc/services)

5.5 Multiplexage d'Entrée/Sortie

La primitive select() permet de « surveiller » plusieurs descripteurs d'entrée/sortie, en particulier plusieurs sockets, en vue d'y opérer des réceptions et émissions. Cette primitive est utile pour les serveurs multi.

6

Session 6 Compléments

6.1 Primitives de gestion d'informations

getsockname(), getpeername() : gestion d'adresses de sockets locales ou distantes.

gethostbyname(), gethostbyaddr(), sethostname() : gestion de noms d'ordinateurs

getdomainname : gestion de noms de domaines

getnetbyname(), getnetbyaddr() : gestion de réseau

getprobyname(), getprobynumber() : gestion de protocoles

getservbyname() : gestion de services

Détail de certaines primitives :

gethostbyname() : cette fonction retourne un pointeur sur une structure de type hostent

Syntaxe :

struct hostent *gethostbyname (hostname), char *hostname ; /*nom de la machine*/

getsockname() : cette fonction retourne le numéro de port attribué par le système à la socket sock.

Syntaxe :

int getsockname(sock, addr, addrlen), int sock, /*descripteur de socket*/ struct sockaddr_in addr, /*adresse de la socket*/ int addrlen ; /*longueur de l’adresse*/

getpeername() : cette function retourne l’adresse de la socket homologue qui est en connexion avec la socket sock.

Syntaxe :

int getpeername(sock, addr, addrlen), int sock, /*descripteur de socket*/ struct sockaddr_in addr, /*adresse de la socket*/ int addrlen ; /*longueur de l’adresse*/

6.2 Primitives de représentation et de conversion

Représentation normalisée TCP/IP pour les entiers utilisés dans les protocoles (ex : numéro de port) appelée Network Byte Order. htonl(), htons(), ntohl(), ntohs() : conversion représentation machine locale/NBO. inet_addr(), inet_network() : manipulation d'adresses IP : conversion 32 bits/notation décimale.

Permet la récupération d'informations caractérisant la socket. getsockopt() : configuration de valeurs de temporisations, allocation de mémoire tampon, autorisation données urgentes.