Le carnet On est
Cours 5 · encre sienne

Les sockets · TCP / UDP

Une API qui transforme deux processus distants en deux extrémités d'un même tuyau. Mode connecté (TCP, fiable) ou datagramme (UDP, rapide). Adresses, ports, byte order — tout l'arsenal pour parler à travers le réseau.

~ 30 min de lecture TP — client/serveur TCP en C 13 flashcards

📄 Ressources du cours

📥 Télécharger les slides  ·  🔗 Ouvrir dans un nouvel onglet

🎥 Vidéos recommandées

1. Qu'est-ce qu'une socket ?

Définition Les Sockets constituent une des A.P.I. (Application Program Interface) permettant aux applications (processus) d'accéder au réseau pour communiquer. Ensemble de primitives assurant cette communication. Cette A.P.I. s'approche de celle permettant la gestion des fichiers UNIX et réutilise certains des appels systèmes que nous avons vus.

Une socket est un point de communication (extrémité) par lequel un processus peut émettre / recevoir des données (messages) à destination / en provenance d'un autre processus.

Descripteur L'identificateur (descripteur) de socket est similaire à l'identificateur (descripteur) de fichiers.

Étendue — familles de protocoles

  • AF_INET — Internet
  • AF_UNIX — Fichiers locaux
  • OSI, SN, DEC, CCITT (X25), Appletalk…

Modèle Client / Serveur

Serveur
Processus rendant un service spécifique, installé puis mis en attente sur une station bien identifiée (sur un réseau d'ordinateurs).
Client
Processus appelant le serveur afin d'obtenir le service. Lancé à la demande à partir de n'importe quelle station.

2. Adressage TCP/IP

Chaque ordinateur (machine) possède une adresse Internet (IP) unique composée de 32 bits. L'adresse IP contient assez d'information pour identifier de façon unique un réseau donné et un ordinateur spécifique sur le réseau.

Exemples : 132.227.104.15, 193.49.30.38.

Nom de domaine — hiérarchique

  • Code d'identification de l'organisation (com commercial, edu éducation, gov gouvernement…)
  • Code d'identification du pays (fr, ca, us…)
  • Code d'identification d'un sous-domaine (univ-cergy)
  • Nom de la machine hôte (www)
  • Exemple : www.formadep.fr

La structure sockaddr_in (AF_INET)

Désignation spécifique permettant d'être identifié de l'extérieur. Structure décrite dans le fichier <netinet/in.h>.

struct sockaddr_in {
    short            sin_family;   // AF_INET
    unsigned short   sin_port;     // numero de port (htons)
    struct in_addr   sin_addr;     // adresse IP
    char             sin_zero[8];  // padding
};
bind — choix de l'adresse Choisir une des adresses, quelconque, de la machine (INADDR_ANY) ou spécifier l'adresse d'une interface IP particulière via gethostname() et gethostbyname().
bind — choix du port Choisir le numéro de port (getservbyname() pour un service existant), ou bien fixer un numéro non réservé (>= 1024), ou encore laisser le système choisir un numéro de port (sin_port = 0).

3. TCP vs UDP

CritèreUDP — User Datagram ProtocolTCP — Transport Control Protocol
Entêteminimaleplus lourde (checksum, séquencement…)
Livraisonnon fiablefiable
Checksumtotal de contrôle calculé et inclus dans chaque paquet
Connexionaucune connexion à établirétablir la connexion avant la transmission
Type de socketSOCK_DGRAMSOCK_STREAM
Modenon connectéconnecté

Types de sockets

TypeDescription
SOCK_DGRAMTransport de données en mode non connecté. Dans le domaine AF_INET, le protocole UDP est utilisé. Les frontières de messages sont préservées.
SOCK_STREAMTransport de données en mode connecté. Dans le domaine AF_INET, utilisation de TCP. Frontières de messages préservées et possibilité d'envoi de messages urgents.
SOCK_RAWAccès au service de niveau 3 (émission/réception de paquets IP). Utilise des paquets bruts (raw packets) permettant d'envoyer des paquets IP directement sans encapsulation dans les protocoles TCP ou UDP.
SOCK_SEQPACKETComme SOCK_STREAM mais ne permet pas l'envoi de messages urgents.

4. Enchaînement des primitives

Deux scénarios — mode connecté (TCP, SOCK_STREAM) et mode non connecté (UDP, SOCK_DGRAM).

SERVEUR CLIENT socket() bind() listen() accept() recv() / send() close() socket() connect() send() recv() close() demande connexion données réponse Mode connecté (SOCK_STREAM / TCP)

Cycle TCP — serveur (socket → bind → listen → accept) et client (socket → connect → send/recv → close).

Mode non connecté (UDP) Plus simple : serveur socket() → bind() → recvfrom() / sendto() → close(). Client socket() → sendto() / recvfrom() → close(). Pas de listen, pas de accept, pas de connect.

5. Les primitives — prototypes

socket() — créer une socket

Permet de créer une socket (descripteur de socket) pour établir un canal de communication.

int socket(int domain, int type, int protocol);
// domain   : AF_INET, AF_UNIX, ...
// type     : SOCK_STREAM, SOCK_DGRAM, SOCK_RAW, SOCK_SEQPACKET
// protocol : 0 (auto), IPPROTO_TCP, IPPROTO_UDP

bind() — informations locales

Permet d'associer un nom ou une adresse et un numéro de port au descripteur de socket retourné par socket(). Ses arguments sont :

  • le descripteur s de la socket considérée ;
  • un pointeur addr vers une structure qui contient l'adresse ou le nom assigné et le numéro de port utilisé ;
  • la taille namelen de la structure vers laquelle pointe addr.
int bind(int s, struct sockaddr *addr, socklen_t namelen);
Conversions d'adresses Des fonctions de conversion d'adresses doivent être utilisées pour passer d'une représentation binaire à une représentation ASCII et inversement.

listen() — file d'attente

Longueur maximum de la file d'attente des communications en cours d'établissement.

int listen(int s, int backlog);

connect() — pseudo-connexion (client)

Établissement de la (pseudo-)connexion en renseignant la socket pour le destinataire.

int connect(int s, struct sockaddr *addr, socklen_t addrlen);

accept() — connexions acceptables (serveur)

Définition de l'ensemble des connexions acceptables.

int accept(int s, struct sockaddr *addr, socklen_t *addrlen);

recv() / read() — réception

Réception de données — strictement identique à l'appel système habituel read. Autres primitives similaires à read.

ssize_t read (int s, void *buf, size_t n);
ssize_t recv (int s, void *buf, size_t n, int flags);
ssize_t recvfrom(int s, void *buf, size_t n, int flags,
                 struct sockaddr *from, socklen_t *fromlen);

send() / write() — émission

Émission de données — identiques ou similaires à l'appel système write.

ssize_t write (int s, const void *buf, size_t n);
ssize_t send  (int s, const void *buf, size_t n, int flags);
ssize_t sendto(int s, const void *buf, size_t n, int flags,
               const struct sockaddr *to, socklen_t tolen);

close() — libération

Effets de close()
• Libère le descripteur de socket
• Libère l'espace de stockage associé à la socket
• Libère la connexion si elle existe
int close(int s);

6. Conversions — noms, adresses, byte order

gethostname() — nom de la machine locale

#include <unistd.h>
#include <stdio.h>

int main() {
    char hostname[256];

    if (gethostname(hostname, sizeof(hostname)) == 0) {
        printf("Nom d'hote : %s\n", hostname);
    } else {
        perror("gethostname");
    }

    return 0;
}

gethostbyname() — résoudre un nom en IP

Paramètre d'entrée : le nom de la machine. Valeur de retour : pointeur vers une structure hostent en cas de succès, NULL en cas d'erreur.

struct hostent {
    char  *h_name;       // nom officiel de l'hote
    char **h_aliases;    // liste d'alias
    int    h_addrtype;   // type d'adresse (AF_INET)
    int    h_length;     // longueur de l'adresse
    char **h_addr_list;  // liste d'adresses IP
};
#include <netdb.h>
#include <stdio.h>
#include <arpa/inet.h>

int main() {
    struct hostent *host = gethostbyname("www.google.com");

    if (host == NULL) {
        perror("gethostbyname");
        return 1;
    }

    printf("Nom : %s\n", host->h_name);

    char *ip = inet_ntoa(*(struct in_addr *)host->h_addr_list[0]);
    // inet_ntoa() sert a convertir une adresse IP IPv4 binaire en chaine
    // de caracteres lisible.

    printf("IP : %s\n", ip);
    return 0;
}

gethostbyaddr() — DNS inverse

Fonction qui permet de résoudre une adresse IP en nom d'hôte (DNS inverse).

struct hostent *gethostbyaddr(const void *addr, socklen_t len, int type);
// addr : pointeur vers l'adresse IP (binaire)
// len  : taille de l'adresse
// type : AF_INET (IPv4) ou AF_INET6 (IPv6)
#include <netdb.h>
#include <arpa/inet.h>
#include <stdio.h>

int main() {
    struct in_addr addr;
    addr.s_addr = inet_addr("100.10.20.5");
    struct hostent *host = gethostbyaddr(&addr, sizeof(addr), AF_INET);

    if (host != NULL) {
        printf("Nom d'hote : %s\n", host->h_name);
    } else {
        perror("gethostbyaddr");
    }

    return 0;
}

getsockname() — adresse IP d'une socket

Obtenir l'adresse IP d'une socket dont on connaît le descripteur.

int getsockname(int s, struct sockaddr *name, socklen_t *namelen);

Little vs Big Endian

Attention au format réseau La représentation des nombres sur une machine dépend de son architecture interne et de son OS.
• Windows, Linux et Mac OS X : Little Endian
• Réseau Internet : Big Endian (standard réseau universel)
Il faut donc toujours convertir du Little Endian au Big Endian et vice versa avant et après communication.
@IPBig EndianLittle Endian
132.227.70.7784.E3.46.4D4D.46.E3.84

Les quatre fonctions de conversion

FonctionSensTailleUsage typique
htons(short)host to network short16 bitsnuméro de port
htonl(long)host to network long32 bitsadresse IP, entier 32 bits
ntohs(short)network to host short16 bitsport reçu du réseau
ntohl(long)network to host long32 bitsIP/entier reçu du réseau
Mnémo
h = host (machine) · n = network · s = short (16) · l = long (32).
htons se lit « host-to-network short » — sens, puis taille.

7. Exemple TCP — Serveur

Échange de messages client / serveur. Partie serveur.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>

#define PORT 8080           // Le port d'ecoute
#define BUFFER_SIZE 1024    // Taille du buffer de reception

int main() {
    int server_fd, new_socket;
    struct sockaddr_in address;
    char buffer[BUFFER_SIZE] = {0};

    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        perror("Erreur lors de la creation du socket");
        exit(EXIT_FAILURE);
    }

    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;   // Accepte les connexions sur n'importe quelle adresse
    address.sin_port = htons(PORT);

    // Lier le socket a l'adresse et au port
    if (bind(server_fd, (struct sockaddr*)&address, sizeof(address)) < 0) {
        perror("Erreur de binding");
        exit(EXIT_FAILURE);
    }

    // Ecouter les connexions entrantes
    if (listen(server_fd, 3) < 0) {
        perror("Erreur lors de l'ecoute");
        exit(EXIT_FAILURE);
    }
    printf("En attente de connexion...\n");

    new_socket = accept(server_sd, clien_address, sizeof(client_address));

    // Lire le message du client
    read(new_socket, buffer, BUFFER_SIZE);
    printf("Message du client : %s\n", buffer);

    // Repondre au client
    char *response = "Message recu!";
    send(new_socket, response, strlen(response), 0);
    printf("Reponse envoyee au client\n");

    close(new_socket);
    close(server_fd);
    return 0;
}
Variables du cours (préservées) Le PDF utilise server_sd, clien_address, client_address dans l'appel accept() — on conserve la version originale du cours sans correction.

8. Exemple TCP — Client

Partie client.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>

#define SERVER_IP "127.0.0.1"   // Adresse IP du serveur
#define PORT 8080               // Le port d'ecoute
#define BUFFER_SIZE 1024        // Taille du buffer de reception

int main() {
    int int sock = 0;
    struct sockaddr_in server_address;
    char buffer[BUFFER_SIZE] = {0};

    // Creation du socket
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        perror("Erreur lors de la creation du socket");
        exit(EXIT_FAILURE);
    }

    address.sin_family = AF_INET;
    address.sin_port   = htons(PORT);

    // Se connecter au serveur
    if (connect(sock, (struct sockaddr*)&server_address, sizeof(server_address)) < 0) {
        perror("Erreur lors de la connexion");
        exit(EXIT_FAILURE);
    }

    // Envoyer un message au serveur
    char *message = "Bonjour, serveur !";
    send(sock, message, strlen(message), 0);
    printf("Message envoye au serveur\n");

    // Lire la reponse du serveur
    read(sock, buffer, BUFFER_SIZE);
    printf("Reponse du serveur : %s\n", buffer);

    // Fermer le socket
    close(sock);
    return 0;
}
Typos du PDF préservées La ligne int int sock = 0; (double int) et la référence à address.sin_family alors que la variable est server_address sont reprises telles quelles dans le cours.

9. Connexions concurrentes

Pour qu'un serveur traite plusieurs clients en parallèle, on combine l'API socket avec les techniques de concurrence vues en début de semestre.

Avec fork()

Création / attachement (socket / bind) Ouverture du service (listen) Attente de connexion (accept) Processus père · fork() Processus fils — traite le client Le père retourne à accept() — le fils gère la requête

Schéma : Création/attachement (socket/bind) → Ouverture du service (listen) → Attente de la demande de connexion (accept) → Création d'un processus fils (fork) → traitement de la demande du client.

Avec un thread

Variante : à chaque accept() réussi, le programme courant crée un thread qui prend en charge le traitement de la demande du client. Le pattern est identique, on remplace fork() par la création de thread.

fork vs thread — rappel
fork() : isolement total, copie complète du processus, plus coûteux.
thread : mémoire partagée, plus léger, mais synchronisation à gérer.

Réviser le chapitre

❓ Q/R — pour vérifier ta compréhension

Qu'est-ce qu'une socket, en une phrase ?
Un point de communication (extrémité) par lequel un processus émet/reçoit des données via une A.P.I. proche de celle des fichiers UNIX.
Quelle famille de protocole pour Internet ? Pour les fichiers locaux ?
AF_INET pour Internet ; AF_UNIX pour les fichiers locaux.
Différence entre SOCK_STREAM et SOCK_DGRAM ?
SOCK_STREAM = mode connecté, utilise TCP, livraison fiable, frontières préservées + messages urgents possibles. SOCK_DGRAM = mode non connecté, utilise UDP, frontières préservées mais livraison non fiable.
Pourquoi a-t-on htons() / ntohs() ?
Parce que les machines courantes (Windows/Linux/macOS) stockent les nombres en Little Endian alors que le réseau Internet utilise Big Endian. Il faut convertir avant et après communication.
Quel est l'ordre des primitives côté serveur TCP ?
socket()bind()listen()accept()recv()/send()close().
Et côté client TCP ?
socket()connect()send()/recv()close(). Pas de bind, pas de listen, pas de accept.
Que vaut INADDR_ANY et à quoi sert-il ?
Valeur spéciale qui dit au noyau : « accepte les connexions sur n'importe quelle adresse de la machine ». On l'affecte à address.sin_addr.s_addr avant bind().
Comment laisser le système choisir un port libre ?
Initialiser sin_port = 0. Pour un port fixe, utiliser getservbyname() (service connu) ou un numéro non réservé ≥ 1024.
Que fait close() sur une socket ?
Libère le descripteur, libère l'espace de stockage associé, et libère la connexion si elle existe.
Comment un serveur TCP gère plusieurs clients en parallèle ?
À chaque accept() réussi, il crée un processus fils via fork() (ou un thread) qui traite la demande, pendant que le père/programme principal retourne à accept().

🃏 Flashcards éclair

Clique pour retourner.

Socket

Définition courte.

tourne →
Point de communication (extrémité) par lequel un processus émet/reçoit des messages. Descripteur similaire à celui d'un fichier UNIX.

socket() — prototype

Signature.

tourne →
int socket(int domain,
           int type,
           int protocol);

AF_INET / SOCK_STREAM

Quel protocole ?

tourne →
TCP — mode connecté, livraison fiable, frontières de messages préservées, messages urgents possibles.

AF_INET / SOCK_DGRAM

Quel protocole ?

tourne →
UDP — mode non connecté, entête minimale, livraison non fiable, frontières préservées.

sockaddr_in

Champs.

tourne →
sin_family  // AF_INET
sin_port    // htons(PORT)
sin_addr    // s_addr = IP
sin_zero[8] // padding

Cycle serveur TCP

Ordre des primitives.

tourne →
socket → bind → listen → accept → recv/send → close

Cycle client TCP

Ordre des primitives.

tourne →
socket → connect → send/recv → close — pas de bind, listen, accept.

htons vs htonl

Différence.

tourne →
htons = host to network short (16 bits, port). htonl = host to network long (32 bits, IP).

Big vs Little Endian

Côté réseau ?

tourne →
Internet = Big Endian (standard universel). Windows/Linux/macOS = Little Endian. Convertir avant/après.

INADDR_ANY

Rôle.

tourne →
Valeur passée à sin_addr.s_addr pour accepter les connexions sur n'importe quelle adresse de la machine.

UDP — primitives d'échange

Différentes de TCP ?

tourne →
sendto() et recvfrom() à la place de send/recv — on précise struct sockaddr dst/src à chaque appel.

gethostbyname()

Renvoie quoi ?

tourne →
Un struct hostent * (avec h_name, h_addr_list…) ou NULL en cas d'erreur. Résout un nom DNS en adresse IP.

close() sur une socket

3 effets.

tourne →
Libère ① le descripteur, ② l'espace de stockage associé, ③ la connexion si elle existe.

Concurrence — TCP

Comment ?

tourne →
Après chaque accept() : fork() un fils (ou créer un thread) pour traiter le client, et le père retourne à accept().

✎ Quiz éclair

test rapide · 7 questions
0 / 7
1.Quelle famille de protocole pour les sockets Internet ?
  • AF_UNIX
  • AF_INET
  • AF_LOCAL
  • SOCK_INET
AF_UNIX est pour les fichiers locaux ; AF_INET pour Internet.
2.Quel type de socket pour TCP ?
  • SOCK_DGRAM
  • SOCK_RAW
  • SOCK_STREAM
  • SOCK_SEQPACKET
SOCK_STREAM + AF_INET → TCP, mode connecté, livraison fiable.
3.Quel est le bon ordre des primitives côté serveur TCP ?
  • socket → connect → send → close
  • socket → bind → listen → accept → recv/send → close
  • socket → listen → bind → accept → close
  • bind → socket → accept → listen → close
La séquence exacte : socket, bind, listen, accept, puis échange, puis close.
4.À quoi sert htons() ?
  • Hacher un mot de passe
  • Convertir une IP en chaîne
  • Convertir un short 16 bits du format machine au format réseau (Big Endian)
  • Faire un handshake TCP
« host to network short » — typiquement pour le port dans sin_port.
5.UDP, c'est :
  • Mode non connecté, livraison non fiable, entête minimale
  • Mode connecté, livraison fiable, checksum obligatoire
  • Un protocole de niveau application
  • Un protocole sans port
C'est TCP qui établit une connexion et garantit la livraison.
6.Pour qu'un serveur écoute sur toutes les adresses de la machine, on met :
  • address.sin_addr.s_addr = 0xFFFFFFFF;
  • address.sin_addr.s_addr = htonl("any");
  • address.sin_addr.s_addr = INADDR_LOOPBACK;
  • address.sin_addr.s_addr = INADDR_ANY;
C'est exactement ce que fait le serveur TCP du cours.
7.En UDP, pour envoyer / recevoir on utilise :
  • send() / recv() uniquement
  • write() / read() après connect()
  • sendto() / recvfrom()
  • accept() / listen()
Pas de connexion → l'adresse du destinataire/source est précisée à chaque appel.

📌 À retenir

  • Socket = extrémité de communication ; descripteur ≈ descripteur de fichier UNIX
  • Familles : AF_INET (Internet) · AF_UNIX (fichiers locaux) · OSI, SN, DEC, X25, Appletalk…
  • Types : SOCK_STREAM (TCP) · SOCK_DGRAM (UDP) · SOCK_RAW · SOCK_SEQPACKET
  • TCP : mode connecté, checksum, livraison fiable, handshake
  • UDP : non connecté, entête minimale, livraison non fiable
  • struct sockaddr_in : sin_family, sin_port, sin_addr — fichier <netinet/in.h>
  • Serveur TCP : socket → bind → listen → accept → recv/send → close
  • Client TCP : socket → connect → send/recv → close
  • UDP : pas de listen/accept/connect ; échange via sendto/recvfrom
  • INADDR_ANY = toutes les interfaces ; sin_port = 0 = port choisi par le système ; port ≥ 1024 si non réservé
  • Réseau = Big Endian ; machine = Little Endian → toujours convertir
  • htons/ntohs (16 bits, port) · htonl/ntohl (32 bits, IP)
  • gethostname, gethostbyname, gethostbyaddr, inet_addr, inet_ntoa, getsockname
  • close() libère descripteur + stockage + connexion
  • Concurrence : après accept()fork() (processus fils) ou création d'un thread, le père/programme principal repart à accept()