Aller au menu - Aller au contenu

[Plan du site] Vous êtes ici --- > Le Site du Zéro > Les tutoriels > Non-Officiels > Programmation > C > Les sockets > Les sockets entre deux applications > Manipulation de sockets > Lecture du tutoriel

Manipulation de sockets

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)
Avatar
Auteur : zephyr111
Note : 16 / 20 (17 votes)
Visualisations : 16 472

Plus d'informations Plus d'informations
Dans cette partie, nous verrons qu'il n'est pas si difficile de manipuler les sockets en C, et que leur utilisation peut s'avérer très pratique dans bon nombre de cas. ^^
Sommaire du chapitre :
Icône du chapitre
Chapitre précédent Sommaire Chapitre suivant

Partie 1 : L'application serveur

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 :p .
Voici comment cela va se passer vraiment si l'on reprend l'ancien schéma :
Image utilisateur
(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" :p ... 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
1
SOCKET sock;


Pour la créer, il nous faudra utiliser la fonction socket avec le prototype suivant :

Code : C
1
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 :
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 : C
1
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 : C
1
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];
};
Nous allons la déclarer et l'initialiser comme ceci :

Code : C
1
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 : C
1
int bind(int socket, const struct sockaddr *addr, socklen_t addrlen);
Donc, nous ferons toujours ainsi :

Code : C
1
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 :p ).
Pour cela, nous allons utiliser la fonction listen. Voici son prototype :

Code : C
1
int listen(int socket,int backlog);
Nous utiliserons donc notre fonction ainsi :

Code : C
1
listen(sock, 5);

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 : C
1
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.
On utilisera donc la fonction comme cela :

Code : C
1
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 : C
1
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.


Partie 2 : L'application client

Normalement, tout devrait bien se passer puisque cette partie est plus simple :p .
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 :
C'est bien d'accepter un appel, mais faut déjà commencer par faire une requête :p . 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 : C
1
int connect(int socket, struct sockaddr *addr, int *addrlen);

On va appeler notre fonction comme cela :

Code : C
1
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 :p .
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 :D .
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. ;)

Q.C.M.

Avons nous besoin de nous connecter au serveur avant toute interaction avec lui en mode TCP/IP ?
A quoi sert la fonction listen sur le serveur lors d'une connection TCP/IP ?

Statistiques de réponses au QCM


Ce chapitre est dur à comprendre, et long. Si vous avez du mal, n'hésitez pas à le relire plusieurs fois ;) .

Dans le chapitre suivant, nous verrons comment transmettre des variables et des chaînes de caractères du serveur au client (on pourra faire un chat :p ).
Chapitre précédent Sommaire Chapitre suivant
Retour en haut Retour en haut


Créé : le 03/03/2007 à 15:49:16
Modifié : le 22/08/2008 à 15:50:48
Avancement : 100%
Licence : Copie non autorisée

25 commentaires

Changer de design | En savoir plus | Plan du site | Politique d'accessibilité | Règles | RSS tutoriels | RSS news
Édité par Simple IT SARL : Nous contacter | Notre blog | Revue de presse | Publicité

Y'a plus rien à lire, faut remonter maintenant !

Hébergement web - Correction de tutoriels - Créer un site
Vous souhaitez apparaître ici ? Contactez-nous.

Nombre de connectés 148 Zéros connectés | Requêtes SQL 8 requêtes | Temps de génération de la page : Total (SQL) 0.4525s (0.4406s)