5 Socket
5 Socket
5 Socket
Les sockets
Table des matières
1. Introduction......................................................................................................................................2
2. Fonctionnement des sockets.............................................................................................................2
2.1. Sur MS-Windows......................................................................................................................2
2.2. Sur GNU/Linux.........................................................................................................................2
2.3. Un code portable.......................................................................................................................3
3. Manipulation de sockets...................................................................................................................4
3.1. Créer une socket........................................................................................................................5
3.2. Paramétrer une socket...............................................................................................................5
3.3. Établir une connexion avec le client.........................................................................................6
3.4. Fermer la connexion.................................................................................................................7
4. Transmission d'une chaîne de caractères..........................................................................................7
4.1. La fonction send........................................................................................................................8
4.2. La fonction recv........................................................................................................................8
4.3. La fonction shutdown...............................................................................................................9
4.4. Exemple d'application Client/serveur.......................................................................................9
4.4.1. L'application CLIENT.......................................................................................................9
4.4.1. L'application SERVEUR.................................................................................................10
5. Un problème de portabilité.............................................................................................................12
5.1. Fonctions de conversion.........................................................................................................12
6. La sélection de sockets...................................................................................................................13
6.1. Threads....................................................................................................................................13
6.2. Le fonctionnement global.......................................................................................................13
6.3. Le descripteur de socket.........................................................................................................14
6.3.1. L'initialisation des descripteurs.......................................................................................15
6.3.2. La sélection de la socket.................................................................................................15
6.3.3. Exemple..........................................................................................................................16
7. Bibliothèques..................................................................................................................................18
Les sockets sont des flux de données, permettant à des machines locales ou distantes de
communiquer entre elles via des protocoles. Les différents protocoles sont TCP qui est un protocole
dit "connecté", et UDP qui est un protocole dit "non connecté".
5-socket.odt 1
Classe de terminale SI
1. Introduction
Les sockets ont été mises au point en 1984, lors de la création des distributions BSD (Berkeley
Software Distribution). Apparues pour la première fois dans les systèmes UNIX, les sockets sont
des points de terminaison mis à l'écoute sur le réseau, afin de faire transiter des données logicielles.
Celles-ci sont associées à un numéro de port.
Les ports sont des numéros allant de 0 à 216-1 inclus (soit 65535 ). Chacun de ces ports est associé
à une application (à savoir que les 1024 premiers ports sont réservé à des utilisations bien précises).
Les sockets sont aussi associées à un protocole (UDP/IP et TCP/IP). Nous utiliserons ici
uniquement le protocole TCP/IP.
Les sockets servent à établir une transmission de flux de données (octets) entre deux machines ou
applications.
5-socket.odt 2
Classe de terminale SI
typedef.
Commençons par inclure les fichiers nécessaires :
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
Un premier problème se pose :
Dans le fichier "socket.h" de GNU/Linux, la fonction qui sert à fermer une socket (que nous verrons
par la suite) se nomme close alors que dans le fichier "winsock2.h" de MS-Windows la fonction se
nomme closesocket... Pour éviter de faire deux codes sources pour deux OS différents, nous
utiliserons une définition comme il suit :
#define closesocket(param) close(param)
Ainsi dans le code la fonction closesocket() sera remplacée par la fonction close() qui pourra
ensuite être exécutée.
Le deuxième problème vient du fait qu'il "manque" deux définitions et trois typedef qui peuvent
nous être utile dans le fichier "socket.h" de GNU/Linux par rapport au fichier "winsock2.h" de MS-
Windows.
Voila donc le contenu du fichier pour le moment :
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#define INVALID_SOCKET -1
#define SOCKET_ERROR -1
#define closesocket(param) close(param)
5-socket.odt 3
Classe de terminale SI
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
// De même
typedef int SOCKET;
typedef struct sockaddr_in SOCKADDR_IN;
typedef struct sockaddr SOCKADDR;
#endif
int main(void)
{
// Si la plateforme est MS-Windows
#if defined (WIN32)
WSADATA WSAData;
WSAStartup(MAKEWORD(2,2), &WSAData);
#endif
return EXIT_SUCCESS;
}
3. Manipulation de sockets
1. Tout d'abord, il faut créer une socket pour pouvoir configurer la connexion qu'elle va établir.
2. Ensuite, la paramétrer pour communiquer avec le client.
3. Enfin, fermer la connexion précédemment établie.
Exemple de la mise en œuvre du protocole :
5-socket.odt 4
Classe de terminale SI
5-socket.odt 5
Classe de terminale SI
short sin_family;
unsigned short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
Notez que la structure in_addr ne contient qu'un seul et unique champ nommé s_addr dont le type
importe peu car nous n'y touchons pas directement (de plus celui-ci varie plus ou moins d'un
système d'exploitation à un autre).
• sin.sin_addr.s_addr sera l'IP donnée automatiquement au serveur. Pour le connaître nous
utiliserons la fonction htonl avec comme seul paramètre la valeur INADDR_ANY.
Si vous voulez spécifier une adresse IP precise à utiliser, il est possible d'utiliser la fonction
inet_addr avec comme seul paramètre l'IP dans une chaine de caractères :
inet_addr("127.0.0.1");
• sin.sin_family sera toujours égal à AF_INET dans notre cas.
• Et sin.sin_port sera égal à la valeur retournée par la fonction htons, avec comme paramètre
le port utilisé.
• Le champ sin_zero ne sera pas utilisé.
Nous allons la déclarer et l'initialiser comme ceci :
SOCKADDR_IN sin;
sin.sin_addr.s_addr = htonl(INADDR_ANY);
sin.sin_family = AF_INET;
sin.sin_port = htons(23);
5-socket.odt 6
Classe de terminale SI
5-socket.odt 7
Classe de terminale SI
3. La fonction shutdown, qui va désactiver les envois et les réceptions sur la socket.
5-socket.odt 8
Classe de terminale SI
5-socket.odt 9
Classe de terminale SI
#endif
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
const unsigned int PORT = 23;
/* Configuration de la connexion */
SOCKADDR_IN sin;
sin.sin_addr.s_addr = inet_addr("127.0.0.1");
sin.sin_family = AF_INET;
sin.sin_port = htons(PORT);
/* On ferme la socket */
closesocket(sock);
return EXIT_SUCCESS;
}
5-socket.odt 10
Classe de terminale SI
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
const unsigned int PORT = 23;
const char buffer[] = "Bonjour !";
/* Configuration */
SOCKADDR_IN sin;
sin.sin_addr.s_addr = htonl(INADDR_ANY); /* Adresse IP automatique */
sin.sin_family = AF_INET; /* Protocole familial (IP) */
sin.sin_port = htons(PORT); /* Ecoute du port */
int sock_err = bind(sock, (SOCKADDR*)&sin, sizeof(sin));
/* Si la socket fonctionne */
if ( sock_err != SOCKET_ERROR ) {
/* Démarrage du listage (mode server) */
sock_err = listen(sock, 5);
printf("Ecoute du port %d...\n", PORT);
/* Si la socket fonctionne */
if ( sock_err != SOCKET_ERROR ) {
/* Attente pendant laquelle le client se connecte */
printf("Patientez pendant que le client se connecte sur le port
%d...\n", PORT);
SOCKADDR_IN csin;
socklen_t recsize = sizeof(csin);
if ( sock_err != SOCKET_ERROR )
printf("Chaine envoyee : %s\n", buffer);
else
printf("Erreur de transmission\n");
5-socket.odt 11
Classe de terminale SI
/* Fermeture de la socket */
printf("Fermeture de la socket...\n");
closesocket(sock);
printf("Fermeture du serveur terminee\n");
}
return EXIT_SUCCESS;
}
5. Un problème de portabilité
La taille d'une variable de type int peut varier d'un ordinateur à un autre. De même, l'ordre des
octets d'une variable codée sur plusieurs octets n'est pas toujours le même non plus.
Il y a plusieurs façon de représenter un groupe d'octets en mémoire, on peut commencer par l'octet
de poids fort ou par l'octet de poids faible par exemple, cela s'appelle l'Endianness (ou boutisme en
français). Si un groupe d'octet commence par l'octet de poids fort on dit que sont orientation est big-
endian, s'il commence par l'octet de poids faible on dit que sont orientation est little-endian. La
façon d'organiser un groupe d'octet en mémoire dépend de l'architecture de la machine.
Pour réaliser une communication portable entre deux ordinateurs, il faut absolument transmettre les
octets des variables un à un selon un ordre donné qui est le même pour les deux applications
distantes.
5-socket.odt 12
Classe de terminale SI
Exemple : on veut transmettre un entier codée sur 4 octets à un autre ordinateur de manière
portable. Il va falloir décomposer l'entier en 4 parties et envoyer chaque octet un à un.
De même pour la réception sauf qu'il va juste falloir faire l'opération inverse.
void send4(int sock, unsigned long data)
{
// On convertit data en entier big-endian
long dataSend = htonl(data);
6. La sélection de sockets
La sélection de sockets permet de manipuler plusieurs sockets avec un thread.
6.1. Threads
Un même processus peut se décomposer en plusieurs parties, qui vont s'exécuter simultanément en
partageant les mêmes données en mémoire. Ces parties se nomment threads.
Du point de vue de l'utilisateur, les threads semblent se dérouler en parallèle.
Lorsqu'une fonction bloque par exemple un programme (comme la fonction recv), si celui-ci
dispose d'une interface graphique, il sera inactif tant que la fonction le bloquera.
5-socket.odt 13
Classe de terminale SI
5-socket.odt 14
Classe de terminale SI
de lier une ou plusieurs sockets à des ensembles. Par exemple, si nous voulons que la sélection
d'une socket cliente soit bloquante jusqu'à se qu'elle reçoive des données en lecture, alors nous
allons initialiser un ensemble de lecture et nous allons lui ajouter cette socket. Si l'ensemble en
lecture est vide cela voudra dire qu'il n'y a rien à lire sur la socket. A l'inverse, si l'ensemble n'est
pas vide cela signifie que la socket a reçu des données et que nous pouvons les lire.
De même, si nous voulons par exemple que deux sockets clientes bloquent la sélection jusqu'à ce
qu'elles reçoivent des données en lecture, il suffira d'ajouter ces deux sockets à un même ensemble
de lecture.
Un ensemble est un type de variable permettant de connaître l'état du descripteur de socket. Il en
existe trois :
• L'ensemble de lecture readfds, il permet de savoir si le client a envoyé des données sur la
socket sélectionnée. Un appel à recv ne sera donc pas bloquant.
• L'ensemble de écriture writefds, il permet de savoir si le client a reçu les données sur la
socket sélectionnée. Un appel à send ne sera donc pas bloquant.
• L'ensemble d'exception exceptfds, il permet de gérer les exceptions.
6.3.1. L'initialisation des descripteurs
Pour faire cela nous utiliserons à quatre fonctions présenté ci-dessous.
• FD_SET(int fd, fd_set* set);
Cette fonction ajoute le descripteur fd à l'ensemble set.
Le descripteur fd n'est rien d'autre qu'une socket mais comme dit plus haut, une socket est
avant tout un type int.
• FD_ISSET(int fd, fd_set* set);
Cette fonction vérifie si le descripteur fd est contenu dans l'ensemble set après l'appel à
select.
Par exemple, si l'ensemble set est un ensemble de lecture la fonction servira à savoir si la
socket fd a reçu des données.
• FD_CLR(int fd, fd_set *set);
Cette fonction supprime le descripteur fd de l'ensemble set.
Cette fonction est beaucoup moins utilisé que les trois autre mais n'en n'est pas pour au
temps inutile.
• FD_ZERO(fd_set *set);
• Cette fonction vide l'ensemble set. Cela revient à supprimer tout les descripteurs ajouté
précédemment à l'ensemble.
6.3.2. La sélection de la socket
La sélection de sockets se fait via la fonction select qui détient le prototype suivant :
int select(int fdmax, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
struct timeval *timeout);
En cas de réussite la fonction retourne le nombre de descripteurs dans les ensembles. Si la fonction
rend la main à l'application car le timeout a expiré alors elle retourne 0, sinon en cas d'erreur la
5-socket.odt 15
Classe de terminale SI
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
int main(void)
{
const unsigned int PORT = 23;
5-socket.odt 16
Classe de terminale SI
if ( !erreur ) {
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
if ( sock != INVALID_SOCKET ) {
printf("La socket %d est maintenant ouverte en mode TCP/IP\n", sock);
SOCKADDR_IN sin;
int recsize = sizeof sin;
sin.sin_addr.s_addr = htonl(INADDR_ANY);
sin.sin_family = AF_INET;
sin.sin_port = htons(PORT);
int sock_err = bind(sock, (SOCKADDR*) &sin, recsize);
if ( sock_err != SOCKET_ERROR ) {
sock_err = listen(sock, 5);
printf("Ecoute du port %d...\n", PORT);
if ( sock_err != SOCKET_ERROR ) {
/* Création de l'ensemble de lecture */
fd_set readfs;
for (;;) {
/* On vide l'ensemble de lecture et on lui ajoute
la socket serveur */
FD_ZERO(&readfs);
FD_SET(sock, &readfs);
SOCKADDR_IN csin;
int crecsize = sizeof csin;
5-socket.odt 17
Classe de terminale SI
return EXIT_SUCCESS;
}
Notez qu'une socket serveur reçoit des données en lecture que quand un client se connecte à celui-
ci. Bien que, les fonctions recv et accept soit bloquantes en temps normale, ici, elles ne le sont plus
car on les appellent lorsqu'il le faut (par exemple, on sait que la fonction recv ne sera pas bloquante
si des données viennent d'être reçues).
La sélection de sockets est présentée, ici, pour une application serveur mais sachez que le principe
fonctionne aussi avec les applications clientes.
7. Bibliothèques
Il existe un certain nombre de bibliothèques pour faciliter la gestion des sockets. Voici les plus
connues.
• libcurl : libcurl est une bibliothèque de transfert de fichier multiprotocole. Elle inclut entre
autres les protocoles HTTP, FTP, HTTPS, SCP, TELNET... La gestion des sockets est faite
en interne.
• GNet : GNet est une bibliothèque écrite en C, orientée objet et basée sur la GLib. Elle inclut
entre autres un système de client/serveur TCP, des sockets multicasts UDP, des sockets
asynchrones...
• SDL_net : SDL_net est une bibliothèque permettant d'accéder au réseau de manière
portable. Elle inclut les protocoles UDP et TCP avec des systèmes de client/serveur.
5-socket.odt 18