Vous vous apprêtez à lire un tutoriel rédigé par un membre de ce site. Malgré tout le soin que ce membre a pu apporter au tutoriel, nous ne pouvons pas garantir que les informations contenues sur cette page sont exactes à 100%. Merci de garder cela en tête lorsque vous lirez cette page ;o)
Pour pouvoir utiliser pleinement les sockets, nous allons suivre une démarche précise

:
Tout d'abord, nous allons
créer une socket pour pouvoir configurer la connexion qu'elle va établir.
Ensuite, nous allons
la paramétrer pour
communiquer avec le client.
Enfin, nous allons
fermer la connexion précédemment établie.
Je ne sais pas si vous vous rappelez du schéma que j'avais fait dans le chapitre précédent, si c'est le cas oubliez-le et sinon tant mieux

.
Voici comment cela va se passer vraiment si l'on reprend l'ancien schéma :
(Notez que ce schéma est toujours simplifié car le client ne va pas dire "Bonjour" et le serveur ne va pas répondre "oui, bien sûr"

... tout se fait par données.)
Chaque action est associée à une fonction que nous allons voir dans ce chapitre

.
Créer une socket
Pour utiliser une socket, il va nous falloir le déclarer avec le type
SOCKET :
Code : C
Pour la créer, il nous faudra utiliser la fonction
socket avec le prototype suivant :
Code : C1 | int socket(int domain, int type, int protocol);
|
Le paramètre
domain représente la famille de protocoles utilisée.
Il prend la valeur
AF_INET pour TCP/IP.
Sinon, il prend la valeur
AF_UNIX pour les communications UNIX en local sur une même machine.
Le
type indique le type de service, il peut avoir les valeurs suivantes :
- SOCK_STREAM, si on utilise le protocole TCP/IP.
- SOCK_DGRAM, si on utilise le protocole UDP/IP.
Nous utiliserons donc la première (notez qu'il en existe d'autres comme SOCK_RAW mais ils nous seront inutiles).
Dans le cas de la suite TCP/IP, le paramètre
protocol n'est pas utile, on le mettra
ainsi toujours à 0.
Comme dans notre cas nous utiliserons le protocole TCP/IP, notre fonction sera toujours :
Code : C1 | sock = socket(AF_INET, SOCK_STREAM, 0);
|
Paramétrer une socket
Après avoir déclarée et créée la socket, nous allons la paramétrer.
Pour cela, nous allons déclarer une structure de type SOCKADDR_IN qui va nous permettre de configurer la connexion. On l'appelle contexte d'adressage. Cette structure est définie de la façon suivante :
Code : C1
2
3
4
5
6
7 | struct sockaddr_in
{
short sin_family;
unsigned short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
|
- 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 :
Code : C
- sin.sin_family sera toujours égal à AF_INET dans notre cas (en savoir plus).
- 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 :
Code : C1
2
3
4 | SOCKADDR_IN sin;
sin.sin_addr.s_addr = htonl(INADDR_ANY);
sin.sin_family = AF_INET;
sin.sin_port = htons(23);
|
Etablir une connexion avec le client
Enfin, pour associer à la socket ces informations, nous allons utiliser la fonction :
Code : C1 | int bind(int socket, const struct sockaddr *addr, socklen_t addrlen);
|
- La fonction retourne SOCKET_ERROR en cas d'erreur (en savoir plus).
- Le paramètre socket désigne la socket du serveur avec laquelle on va associer les informations.
- Le paramètre addr est un pointeur de structure sockaddr du serveur.
Il spécifie l'IP à laquelle on se connecte... Comme la fonction a besoin d'un pointeur sur structure sockaddr, et que nous disposons que d'une structure SOCKADDR_IN, nous allons faire un cast, pour éviter que le compilateur nous retourne une erreur lors de la compilation.
- Le paramètre addrlen sera la taille mémoire occupée par le contexte d'adressage du serveur (notre structure SOCKADDR_IN), nous utiliserons donc sizeof
(si vous ne vous rappelez plus du cours de m@teo21, je vous conseil de relire le cours sur l'allocation dynamique
).
Donc, nous ferons toujours ainsi :
Code : C1 | bind(sock, (SOCKADDR *) &sin, sizeof sin);
|
Voilà ! Maintenant que toutes les informations sont données, il va falloir mettre la socket dans un état d'écoute (établir la connexion, si vous préférez

).
Pour cela, nous allons utiliser la fonction
listen. Voici son prototype :
Code : C1 | int listen(int socket,int backlog);
|
- La fonction retourne SOCKET_ERROR si une erreur est survenue.
- Le paramètre socket désigne la socket qui va être utilisée.
- Le paramètre backlog représente le nombre maximal de connexions pouvant être mises en attente.
Nous utiliserons donc notre fonction ainsi :
Code : C
En général, on met le nombre maximal de connexions pouvant être mises en attente à 5 (comme les clients FTP).
Enfin, on termine avec la fonction
accept avec le prototype suivant :
Code : C1 | int accept(int socket,struct sockaddr * addr,int * addrlen);
|
Cette fonction permet la connexion entre le client et le serveur en acceptant un appel de connexion.
- La fonction retourne la valeur INVALID_SOCKET en cas d'échec. Sinon, elle retourne la socket du client.
- Le paramètre socket est, comme dans les autre fonctions, la socket serveur utilisée.
- Le paramètre addr est un pointeur sur le contexte d'adressage du client.
- Le paramètre addrlen ne s'utilise pas comme dans la fonction bind ; ici, il faut créer une variable taille, égale à la taille du contexte d'adressage du client... Ensuite, il faudra passer l'adresse de cette variable en paramètre.
On utilisera donc la fonction comme cela :
Code : C1
2 | unsigned int taille = (unsigned int) sizeof(csin);
csock = accept(sock, (SOCKADDR *) &csin, &taille);
|
Avec csock représentant la socket client et csin sont contexte d'adressage.
La fonction
accept est une fonction bloquante qui se termine que si un client se connecte. Pour le moment cela ne nous gène pas puisque nous sommes sous la console, mais après, quand nous ferrons des applications fenêtrées, il va falloir gérer
les threads. Vous devez juste retenir qu?ils servent à faire plusieurs choses en parallèle dans une même application

. Ne vous inquiétez pas j'aborderai les threads dans la suite du cours.
Fermer la connexion
Finalement nous terminerons par la fonction
closesocket qui permet de fermer une socket.
Code : C1 | int closesocket(int sock);
|
Son prototype est très simple, je pense donc que la fonction se passe de commentaires

.
On récapitule
Nous allons réaliser une application qui va attendre qu'un client se connecte à celle-ci. Bien sûr, comme nous n'avons pas encore fait l'application "client", notre application (qui jouera le rôle de serveur) ne pourra pas établir de connexion... Nous verrons ensuite les fonctions relatives au "client", ce qui nous permettra de réaliser une vraie connexion.
Sachez cependant que la partie serveur était la plus difficile et la plus longue

.
Réfléchissez un peu à l'ordre d'utilisation des fonctions, passez un peu de temps dessus.
Voir la solution directement ne vous aidera pas

.
Il ne faut pas oublier que chaque client et serveur contient une socket et un contexte d'adressage pour lui seul. Notez qui est possible d'avoir plusieurs sockets serveur sur pour une même application.
Secret (cliquez pour afficher)Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98 | #if defined (WIN32)
#include <winsock2.h>
#elif defined (linux)
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#define INVALID_SOCKET -1
#define SOCKET_ERROR -1
#define closesocket(s) close(s)
typedef int SOCKET;
typedef struct sockaddr_in SOCKADDR_IN;
typedef struct sockaddr SOCKADDR;
#endif
#include <stdio.h>
#include <stdlib.h>
#define PORT 23
int main(void)
{
#if defined (WIN32)
WSADATA WSAData;
int erreur = WSAStartup(MAKEWORD(2,0), &WSAData);
#else
int erreur = 0;
#endif
/* Socket et contexte d'adressage du serveur */
SOCKADDR_IN sin;
SOCKET sock;
unsigned int recsize = (unsigned int) sizeof sin;
/* Socket et contexte d'adressage du client */
SOCKADDR_IN csin;
SOCKET csock;
unsigned int crecsize = (unsigned int) sizeof csin;
int sock_err;
if(!erreur)
{
/* Création d'une socket */
sock = socket(AF_INET, SOCK_STREAM, 0);
/* Si la socket est valide */
if(sock != INVALID_SOCKET)
{
printf("La socket %d est maintenant ouverte en mode TCP/IP\n", sock);
/* Configuration */
sin.sin_addr.s_addr = htonl(INADDR_ANY); /* Adresse IP automatique */
sin.sin_family = AF_INET; /* Protocole familial (IP) */
sin.sin_port = htons(PORT); /* Listage du port */
sock_err = bind(sock, (SOCKADDR *) &sin, recsize);
/* Si la socket fonctionne */
if(sock_err != SOCKET_ERROR)
{
/* Démarrage du listage (mode server) */
sock_err = listen(sock, 5);
printf("Listage 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);
csock = accept(sock, (SOCKADDR *) &csin, &crecsize);
printf("Un client se connecte avec la socket %d de %s:%d\n", csock, inet_ntoa(csin.sin_addr), htons(csin.sin_port));
}
else
perror("listen");
}
else
perror("bind");
/* Fermeture de la socket client et de la socket serveur */
printf("Fermeture de la socket client\n");
closesocket(csock);
printf("Fermeture de la socket serveur\n");
closesocket(sock);
printf("Fermeture du serveur terminée\n");
}
else
perror("socket");
#if defined (WIN32)
WSACleanup();
#endif
}
return EXIT_SUCCESS;
}
|
Le code n'est pas complexe quand on connaît les fonctions qu'il utilise

.
Vous devez être en root pour faire fonctionner un programme serveur sinon cela ne fonctionnera pas.
Normalement, tout devrait bien se passer puisque cette partie est plus simple

.
Sachez que les inclusions et les définitions se conservent, et donc qu'il n'y aura qu'une partie de la fonction principale qui changera

. Maintenant, nous allons réaliser l'application qui va jouer le rôle du client. Pour cela, créez un
nouveau projet.
Eh bien : récapitulons ce que nous savons faire :
- Créer une socket.
- Associer une Socket à un point de terminaison local.
- Mettre une Socket en état d'écoute.
- Accepter un appel de connexion avec un client.
- Fermer la connexion Socket, et libérer toutes les ressources associées.
C'est bien d'accepter un appel, mais faut déjà commencer par faire une requête

. Et oui, nous ne savons pas établir une connexion du côté client ! Pour cela nous allons utiliser la fonction
connect.
Son prototype est le suivant :
Code : C1 | int connect(int socket, struct sockaddr *addr, int *addrlen);
|
- La fonction retourne 0 si la connexion s'est bien déroulée, sinon -1.
- Le paramètre socket représente la socket à utiliser (ça n'a toujours pas changé
).
- Le paramètre addr représente l'adresse de l'hôte à contacter. On va faire un cast comme avec la fonction accept().
- Le dernier paramètre, addrlen, représente la taille de l'adresse de l'appelant (un sizeof suffira
).
On va appeler notre fonction comme cela :
Code : C1 | connect(sock, (SOCKADDR *)&sin, sizeof(sin))
|
Avec la structure
SOCKADDR_IN sin précédemment déclarée.
Sachez que pour l'application client, il n'y aura besoin d'utiliser ni la fonction bind puisqu'elle est comprise dans la fonction connect, ni listen puisqu'il n'y a pas de sockets à mettre à l'écoute, ni encore accept() puisque l'application joue le rôle de client.
Et voila c'est finie pour les nouvelles fonctions de notre application client

. Je vous avez dit que cette partie était plus simple

.
Maintenant que vous savez tout ce dont vous avez besoin, vous pouvez commencer à réfléchir au code.
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60 | #if defined (WIN32)
#include <winsock2.h>
#elif defined (linux)
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#define INVALID_SOCKET -1
#define SOCKET_ERROR -1
#define closesocket(s) close (s)
typedef int SOCKET;
typedef struct sockaddr_in SOCKADDR_IN;
typedef struct sockaddr SOCKADDR;
#endif
#include <stdio.h>
#include <stdlib.h>
#define PORT 23
int main (void)
{
#if defined (WIN32)
WSADATA WSAData;
int erreur = WSAStartup(MAKEWORD(2,0), &WSAData);
#else
int erreur = 0;
#endif
SOCKET sock;
SOCKADDR_IN sin;
if(!erreur)
{
/* Création de la socket */
sock = socket(AF_INET, SOCK_STREAM, 0);
/* Configuration de la connexion */
sin.sin_addr.s_addr = inet_addr("127.0.0.1");
sin.sin_family = AF_INET;
sin.sin_port = htons(PORT);
/* Si le client arrive à se connecté */
if(connect(sock, (SOCKADDR *)&sin, sizeof(sin)) != SOCKET_ERROR)
printf("Connexion à %s sur le port %d\n", inet_ntoa(sin.sin_addr), htons(sin.sin_port));
else
printf("Impossible de se connecter\n");
/* On ferme la socket précédemment ouverte */
closesocket(sock);
#if defined (WIN32)
WSACleanup();
#endif
}
return EXIT_SUCCESS;
}
|
Ce code affiche cela si tout se passe bien :
Code : Console | Connexion à 127.0.0.1 sur le port 23 |
Ce code va créer une socket et va essayer de se connecter sur un application serveur en local,
une seule fois, puis va se fermer.
Vous pouvez maintenant tester votre programme serveur

.
Lancez votre programme serveur en
premier,
puis votre programme client.
En effet : si vous faites le contraire, votre programme client va essayer de se connecter au programme serveur alors que celui-ci n'est pas lancé... Comme il ne se connecte qu'une seule fois, le programme client se fermera alors que le programme serveur, lui, attendra une connexion du client.
Votre programme serveur affiche donc ça avant d'être fermé :
Code : Console | La socket xxxx est maintenant ouverte en mode TCP/IP
Listage du port 23...
Patientez pendant que le client se connecte sur le port 23...
Un client se connecte avec la socket xxxx de 127.0.0.1:xxxx
Fermeture de la socket
Fermeture du serveur terminée |
Si ce n'est pas le cas, vérifiez que vous ne vous êtes pas trompés précédemment dans le code

.
Exercice
Maintenant, vous pouvez réaliser une connexion du client en boucle. Puis, une fois connectés, vous fermez les deux programmes.
Secret (cliquez pour afficher)Utilisez une boucle.