Jeux Libres

Plateforme de création de jeux vidéo (Le site est en cours de création / réorganisation)


» Les Tutoriaux » Réseau : créer une connexion

Réseau : créer une connexion


Vous voici dans l'un des chapitres les plus attendu : la programmation de jeux en réseau.

On dit souvent que le réseau est l'une des notions les plus difficiles à assimiler. Mon objectif sera attend si j'arrive à vous prouver le contraire. Je suis assez confient, je vous demande juste d'être attentif, nous allons y aller par étapes et tout ce passera bien.




Qu'est ce que le réseau ? Comment ça fonctionne ?


On va commencer par une partie un peu théorique : nous allons voir ce qu'est un réseau pour mieux comprendre son fonctionnement.

Un réseau, c'est quoi ?



Un réseau, c'est un ensemble d'ordinateurs reliés entre eux et qui peuvent s'échanger des informations. Par exemple, Internet est un réseau. Tout les ordinateurs connectés à Internet sont potentielement connecté entre eux.

De quoi est constitué un réseau ?



Un réseau est constitué d'ordinateurs relié entre eux par cables (ou autre type de liaison).

Sur chacun de ces ordinateurs, il peut y avoir des serveurs et des clients. Ces serveurs et clients sont des programmes.

J'insiste : un serveur est un programme ! Et il est exécuté sur un ordinateur.
Par abut, l'ordinateur destiné à faire tourner le programme serveur est appelé "serveur".

Un serveur, c'est quoi ?



Un serveur est un programme qui reste à l'écoute des clients. C'est à dire qu'il se tient toujours prêt à recevoir une demande de connexion.

Un client ne peut se connecter qu'à un programme à l'écoute : un serveur.

C'est toujours le client qui vient se connecter au serveur. Le contraire n'est pas possible.

Serveur, où est tu ?



Dans un réseau, chaque ordinateurs pocèdent une adresse différente. C'est grâce à cette adresse que l'on pourra identifier les ordinateurs. Ainsi, en connaissant l'adresse d'un ordinateur qui pocède un serveur, un client pourra s'y connecter.

Le soucis, c'est qu'un ordinateur peut pocéder plusieurs serveurs (beaucoup de serveur). Le client ne doit pas se tromper de serveur s'il veux avoir une chance de pouvoir communiquer avec.

Heureusement pour nous, chaque serveur n'est pas à l'écoute de tout et n'importe quoi. Il sont à l'écoute sur un port bien précis.

C'est quoi un port ?

Imaginez que c'est une porte.

Chaque ordinateur pocède 65536 ports numéroté de 0 à 65535. Chaque serveur est placé derière un port différent.
Lorsqu'un client veux se connecter à un serveur, il n'a plus qu'à frapper à la bonne porte !

Les serveurs sont toujours à l'écoute sur les mêmes ports. Il n'y a donc pas à chercher derière quel port se cache tel serveur. Pour ce qui est de l'adresse du serveur, c'est un peu plus compliqué. Ce sera au joueur d'indiquer l'adresse du serveur.

Et les jeux vidéo dans tout ça ?



Dans un jeu en réseau, le jeu lancé par le joueur fait office de client. Chaque joueur se connecte à un serveur commun.

Un serveur peut accepter plusieurs connexions de clients différents sur un même port. Et heureusement !

Vous l'aurrez compris, créer un jeux vidéo en réseau consiste à créer le jeu (un client) mais aussi un serveur de jeu.


Dans un programme, comment ça se passe ?


Le client et le serveur ne se voient pas. Le serveur ne sait même pas que le client existe.
Pour que le serveur sache que le client existe, le client doit faire une demande de connexion auprès du serveur.

Si le serveur accepte, lui et le client se munissent d'un outil de communication. Cet outil de communication s'appel un socket (rechercher le genre et la prononciation).

Ensuite, les deux programmes peuvent dialoguer :
- Pour envoyer une information du client au serveur, il suffira de placer l'information dans le socket du client.
- Pour réceptionner l'information, le serveur irra chercher l'information dans le socket du client.

Le serveur va chercher l'information dans le socket du client. Le serveur ne revient jamais les mains vide. Si le client n'y a pas déposé de nouvelle information, le serveur attendra !

Le soucis, c'est que ça peut durer longtemps. Si le client ne dépose jamais d'information dans son socket, le serveur reste bloqué.
C'est là que vont intervenir les tâches. Plutôt que de bloquer le programme en attendant la réception d'une information, on va laisser une tâche se charger de la réception. Du coup, même si la tâche reste bloquée sur la réception d'une information, cela ne bloquera par l'exécution du programme.

Nous venons de voir comment se passe le transfert d'information du client au serveur. Le transfert d'information dans l'autre sens se passe de la même façon.

De façon général, c'est le programme qui reçois l'information qui va la chercher dans le socket du programme qui l'envoi.


La librairie winsock2


Nous allons commencer par apprendre à mettre en relation un serveur et un client. On s'occupera du transfert d'information plus tard.

Oups, je fait comment moi si je n'ai qu'un seul ordinateur ?

Qui a dit qu'il falait avoir plusieurs ordinateurs ? Plusieurs ports suffiront !

Pour créer des jeux vidéo en réseau, j'ai fait le choix d'utiliser la librairie winsock2. Il s'agit d'une librairie réseau de bas niveau. Elle est un peu plus compliquée à mettre en oeuvre qu'une librairie de haut niveau mais elle est sans limite.

Normalement, la librairie winsock2 est déjà installé. Il ne reste plus qu'a l'utiliser.

A chaque fois que vous utiliserez le réseau, vous devrez inclure la librairie winsock2 : #include <winsock2.h>.

Allez, on y va !

Code : C
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h> // On va utiliser le réseau

int main()
{
   

    system ("PAUSE");
    return 0;
}


Attention : N'oublier pas de linker le fichier libws2_32.a du dossier mingw32\lib\ avant de compiler.

Initialisation



Avant d'utilisation du réseau, il faut préparer le programme à l'utiliser.
Pour celà, rien de plus simple, copiez ces deux lignes au tout début de votre programme :

Code : C
    // On initialise le programme pour le réseau
    WSADATA data;
    WSAStartup (MAKEWORD (2,0), &data);


Comme ceci :

Code : C
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h> // On va utiliser le réseau

int main()
{
    // On initialise le programme pour le réseau
    WSADATA data;
    WSAStartup (MAKEWORD (2,0), &data);


    system ("PAUSE");
    return 0;
}


Attention : L'initialisation du réseau alloue (réserve) de la place en mémoire. ll ne faudra pas oublier de désalouer (libérer) la mémoire avant la fermeture du programme.
Pour cela, faite un appel à la fonction WSACleanup (prononcez W.S.A. cliine eup).

Code : C
    // On libère la mémoire utilisée pour le réseau
    WSACleanup();

En clair !



Code : C
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h> // On va utiliser le réseau

int main()
{
    // On initialise le programme pour le réseau
    WSADATA data;
    WSAStartup (MAKEWORD (2,0), &data);



    // On libère la mémoire utilisée pour le réseau
    WSACleanup();

    system ("PAUSE");
    return 0;
}


Retenez que ceci est le code de base de tout les programmes qui utilise le réseau ; que ce soit un serveur ou un client.


Un serveur : adresse + socket


A mon avis, il est plus simple de commencer par la création d'un serveur. On va donc commencer par créer un serveur.

Important : Retenez que pour créer un serveur ou un client, vous aurrez TOUJOURS besoin de créer une adresse et un socket. Ensuite, il faut lier les deux. On appel ça la ligature.

Création du socket !



Allez, on va créer un socket pour notre serveur.

Mais c'est quoi un socket dans un programme ?

Dans un programme, un socket, c'est une variable de type SOCKET.

Code : C
    SOCKET serveur_socket; // Création d'un socket pour notre serveur


Il existe plusieurs types de socket. Tout dépend de la valeur à laquelle on l'initialise.
Pour créer des jeux vidéo, nous n'étudierons qu'un type de socket : le socket de flux utilisable sur le réseau Internet et local.

Lors de sa création, on initialisera donc toujours notre socket comme ceci :

Code : C
    // Création d'un socket pour notre serveur
    SOCKET serveur_socket = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);

On cré le socket de notre serveur :

Code : C
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h> // On va utiliser le réseau

int main()
{
    // On initialise le programme pour le réseau
    WSADATA data;
    WSAStartup (MAKEWORD (2,0), &data);

    // Création d'un socket pour notre serveur
    SOCKET serveur_socket = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);



    // On libère la mémoire utilisée pour le réseau
    WSACleanup();

    system ("PAUSE");
    return 0;
}


Un socket tout seul n'est rien ! Il lui faut une adresse pour être accessible.

Création de l'adresse ! (pour le socket)



Et une adresse c'est quoi ?

Une adresse, c'est aussi une variable. Une variable de type SOCKADDR_IN.

On cré l'adresse :

Code : C
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h> // On va utiliser le réseau

int main()
{
    // On initialise le programme pour le réseau
    WSADATA data;
    WSAStartup (MAKEWORD (2,0), &data);

    // Création d'un socket pour notre serveur
    SOCKET serveur_socket = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);

    // Un socket sans adresse n'est rien.
    // On cré une adresse pour notre socket
    SOCKADDR_IN serveur_adresse;



    // On libère la mémoire utilisée pour le réseau
    WSACleanup();

    system ("PAUSE");
    return 0;
}


Et ensuite, comme pour le socket, il faut aussi initialiser l'adresse.

Son initialisation consiste à :
- Indiquer le type de l'adresse. (adresse Internet et local dans notre cas)
- Choisir un port d'écoute.
- Indiquer l'adresse du serveur.

Une variable de type SOCKADDR_IN est une structure qui contient tout ces éléments. Tout ces élément peuvent être mémorisé sur 8 octets.

Mais ce type de variable n'est pas utilisable directement par les fonctions que nous utiliserons. Les fonction que nous utiliserons utilise plutôt des variables de type SOCKADDR. Il s'agit d'un type de variable générique (utilisable partout) d'un taille de 16 octets.

Pour pouvoir caster la variable SOCKADDR_IN vers la variable générique, les programmeurs on ajouté 8 octets inutils à la fin de la structure. On les mettera à 0.


Représentation d'un structure de type SOCKADDR_IN

- Les 2 premiers octets servent à indiquer le type d'adresse.
- Ensuite, 2 autres octets servent à indiquer le numéro du port derière lequel sera le socket.
- Les 4 derniers octets servent à y placer l'adresse IP de l'ordinateur.
- 8 octets inutils sont ajouté à la structure pour obtenir une structure de la taille d'une structure générique. (16 octets)

C'est quoi une adresse IP ?

L'adresse IP, c'est l'adresse que l'ordinateur utilise pour être situé sur le réseau. Chaque ordinateur pocède sa propre et unique adesse IP sur le réseau. Par exemple, la miène, c'est 84.103.110.220. Il s'agit de 4 nombres de 0 à 255 (4 octets) : ça rentre da les cases !


Pour connaitre votre adresse IP sur le réseau Internet, cliquez ici.

Le port à l'écoute !



Comme vous le savez, dans un octet on peut mettre l'un des 256 nombres possibles (de 0 à 255). De ce fait, sur 2 octets on peut y placer l'un des 256x256 nombres possibles. C'est à dire : 65536.
Il sera donc possible de placer le numéro du port à l'écoute dans le bloc de 2 octets (représenté en vert sur mon dessin).

Sur mon ordinateur, si je veux mettre le serveur à l'écoute sur le port 2712, je devrais faire ceci :


Serveur à l'écoute du port 2712

Vous devez commencer à comprendre le fonctionnement de l'adresse.

Voici donc les 4 blocs de la structure SOCKADDR_IN que l'on vien de créer :
- serveur_adresse.sin_family
- serveur_adresse.sin_port
- serveur_adresse.sin_addr
- serveur_adresse.sin_zero

Pour remplire le bloc serveur_adresse.sin_addr de notre serveur, nous n'aurrons pas besoin de l'adresse IP du serveur. Le simple fait de placer la valeur INADDR_ANY dans le sous-bloc serveur_adresse.sin_addr.s_addr suffira pour utiliser la structure.

Voyons ce que ça donne dans un programme.
On va commencer par le type d'adresse. Dans notre cas, sa valeur sera toujours mise à AF_INET.

Code : C
    serveur_adresse.sin_family = AF_INET; // Type d'adresse


Ensuite, le port !

Le port ne peut pas être enregistré directement dans le bloc. Pour des raisons de compatibilité entre les différentes machines, on utilisera toujours la fonction htons avant d'enregistrer le numéro du port dans la structure.

Pour enregistrer le numéro du port dans la sructure, vous devrez procéder comme ceci :

Code : C
    serveur_adresse.sin_port = htons (2712); // Numéro du port à l'écoute : 2712


Ensuite, pour l'adresse, on vient de la voir, pour un serveur ce sera toujours :

Code : C
    serveur_adresse.sin_addr.s_addr = INADDR_ANY; // Adresse automatique du serveur


Et pour finir, on met à zéro tout le reste inutil de la structure :

Code : C
    memset (serveur_adresse.sin_zero, 0, 8); // Mise à zero des 8 derniers octets de la structure


Allez, on y va !

Code : C
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h> // On va utiliser le réseau

int main()
{
    // On initialise le programme pour le réseau
    WSADATA data;
    WSAStartup (MAKEWORD (2,0), &data);

    // Création d'un socket pour notre serveur
    SOCKET serveur_socket = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);

    // Un socket sans adresse n'est rien.
    // On cré une adresse pour notre socket
    SOCKADDR_IN serveur_adresse;

    serveur_adresse.sin_family = AF_INET; // Type d'adresse
    serveur_adresse.sin_port = htons (2712); // Numéro du port à l'écoute : 2712
    serveur_adresse.sin_addr.s_addr = INADDR_ANY; // Adresse automatique du serveur
    memset (serveur_adresse.sin_zero, 0, 8); // Mise à zero des 8 derniers octets de la structure



    // On libère la mémoire utilisée pour le réseau
    WSACleanup();

    system ("PAUSE");
    return 0;
}


La ligature



A présent, nous sommes en pocéssion d'un socket et d'une adresse.
Il ne reste plus qu'a lier le socket à l'adresse et le mettre à l'écoute.

La fonction qui sert à lier le socket à l'adresse est la fonction bind.
Cette fonction prend en compte 3 paramètres : le socket du serveur, l'adresse mémoire de l'adresse du serveur dans un contexte générique et la taille de l'adresse du serveur.

Oulà ! C'est compliqué tout ça : je recommence !

On va avoir besoin du socket du serveur :

Code : C
    serveur_socket // Socket du serveur


On va également avoir besoin de l'adresse mémoire de l'adresse du serveur :

Code : C
    &serveur_adresse // Adresse mémoire de l'adresse du serveur


Attention : serveur_adresse est de type SOCKADDR_IN. Donc &serveur_adresse est de type SOCKADDR_IN*. Or la fonction bind attend une variable générique comme SOCKADDR*. Il va donc faloir caster la variable serveur_adresse dans le type SOCKADDR*.

Code : C
    (SOCKADDR*)&serveur_adresse // Adresse mémoire de l'adresse du serveur dans un contexte générique


Et pour la taille de l'adresse, on utilisera sizeof :

Code : C
    sizeof (serveur_adresse) // Taille de la structure pour l'adresse du serveur


Voici donc le code avec la ligature :

Code : C
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h> // On va utiliser le réseau

int main()
{
    // On initialise le programme pour le réseau
    WSADATA data;
    WSAStartup (MAKEWORD (2,0), &data);

    // Création d'un socket pour notre serveur
    SOCKET serveur_socket = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);

    // Un socket sans adresse n'est rien.
    // On cré une adresse pour notre socket
    SOCKADDR_IN serveur_adresse;

    serveur_adresse.sin_family = AF_INET; // Type d'adresse
    serveur_adresse.sin_port = htons (2712); // Numéro du port à l'écoute : 2712
    serveur_adresse.sin_addr.s_addr = INADDR_ANY; // Adresse automatique du serveur
    memset (serveur_adresse.sin_zero, 0, 8); // Mise à zero des 8 derniers octets de la structure
   
    // Ligature du socket et de l'adresse
    bind (serveur_socket, (SOCKADDR*)&serveur_adresse, sizeof (serveur_adresse));



    // On libère la mémoire utilisée pour le réseau
    WSACleanup();

    system ("PAUSE");
    return 0;
}


Ca y'est, tout est prêt ! Il ne reste plus qu'à mettre notre seveur à l'écoute. Notez que ce code est le code de base de tout les serveurs. Vous vous en servirez à chaque fois que vous devrez créer un serveur de jeu vidéo. D'ailleur, on s'en servira bientôt dans le prochain TP !


Etablir une connexion


Dans cette avant-dernière partie, nous allons apprendre à établir une connexion entre un client et un serveur.
Je vous rassure tout de suite, vous avez déjà fait le plus dur.

L'établissement d'une connexion se fait en 2 étapes :
- La mise à l'écoute du serveur.
- L'acceptation de la connexion.

Mise à l'écoute du serveur



Pour mettre à l'écoute le serveur, vous utiliserez la fonction listen (prononcez : lissene) (en anglais : to listen = écouter).

Son utilisation est très simple. Elle prend en compte 2 paramètres :
- Le socket du serveur.
- La taille de la file d'attente des demandes de connexion.

C'est grâce au socket du serveur que la fonction listen va savoir sur quel port on devra écouter.

Mais le socket ne comptient pas le numéro du port à l'écoute, si ?
On a dis que c'était l'adresse qui contenait le numéro du port !

C'est vrai ! Jusqu'avant le bind, le socket et l'adresse était distinct. Mais maintenant, le simple fait d'utiliser le socket sera suffisant.

Une fois que le port sera mis à l'écoute, les clients pourront venir s'y connecter. Mais l'établissement d'une connexion ne se fait pas sur une simple demande de connexion : le serveur doit accepter la connexion. Pendant ce temps, les demandes de connexion reste en attente. C'est la taille de cette file d'attente qui sera défini par le second paramètre de la fonction listen. Lorsque cette file d'attente est pleine, tout demande de connexion serra refusée automatiquement.

Ce n'est peut-être pas encore très claire dans votre esprit. Dans ce cas, ne vous prenez pas la tête : mettez 5 !

Attention : la mise à l'écoute d'un port peut nécessiter le déblocage du port auprès de votre pare-feux (FireWall). Attendez vous à devoir débloquez le port en question lors de l'exécution de votre programme. Ne le laissez surtout pas bloqué !

On reprend !
On en était à la mise à l'écoute du serveur :

Code : C
    // Mise à l'écoute du serveur
    listen (serveur_socket, 5);


On l'ajoute à notre serveur :

Code : C
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h> // On va utiliser le réseau

int main()
{
    // On initialise le programme pour le réseau
    WSADATA data;
    WSAStartup (MAKEWORD (2,0), &data);

    // Création d'un socket pour notre serveur
    SOCKET serveur_socket = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);

    // Un socket sans adresse n'est rien.
    // On cré une adresse pour notre socket
    SOCKADDR_IN serveur_adresse;

    serveur_adresse.sin_family = AF_INET; // Type d'adresse
    serveur_adresse.sin_port = htons (2712); // Numéro du port à l'écoute : 2712
    serveur_adresse.sin_addr.s_addr = INADDR_ANY; // Adresse automatique du serveur
    memset (serveur_adresse.sin_zero, 0, 8); // Mise à zero des 8 derniers octets de la structure

    // Ligature du socket et de l'adresse
    bind (serveur_socket, (SOCKADDR*)&serveur_adresse, sizeof (serveur_adresse));

    // Mise à l'écoute du serveur
    listen (serveur_socket, 5);



    // On libère la mémoire utilisée pour le réseau
    WSACleanup();

    system ("PAUSE");
    return 0;
}


Lors de son exécution, votre pare-feu a sans doute réagit : penssez bien à le débloquer ! J'insiste !
Chez moi, voici ce que j'ai obtenu :


Par sécurité, le pare-feu maintient le bloquage du programme inconnu et propose son débloquage.

Acceptation de la connexion



Ne venons de voir comment mettre un port à l'écoute. Cela a pour effet de créer une file d'attente des demande de connexion sur ce port.
Maintenant, on va apprendre à accepter les connexions qui sont dans la file d'attente.

Le principe !



Je vais commencer par vous présenter le principe de l'acceptation d'une connexion.
Si vous ne comprenez pas tout de suite et que vous trouvez ça un peu tordu : c'est normal ! Le réseau, c'est très tordu !
Mais ça ne doit pas être une excuse pour tout lacher. Si vous ne comprenez pas, recommencez votre lecture à partir d'ici.

Actuellement, nous somme en train de créer un serveur. Mais nous allons quand même créer un socket pour notre client à l'interieur de notre serveur.

Ce qu'il faut bien comprendre, c'est que le client et le serveur ont chacun leur propre socket. Mais pour que le socket du client soit accessible dans le serveur, on va tanter de le reconstituer à l'intérieur de notre serveur.

Je sais que ce n'est pas évident. Si vous n'avez pas compris : relisez ces deux lignes. Après tout, on est pas précé.

On va donc créer une socket pour le client :

Code : C
    SOCKET client_socket; // On cré un socket pour notre client


N'oubliez pas : un socket sans adresse n'est rien !

On cré aussi une adresse pour notre client :

Code : C
    SOCKADDR_IN client_adresse; // On cré une adresse pour notre client


Et comme à chaque fois, on aurra besoin de le taille de l'adresse. On cré donc une variable et on y mémoriser la taille de l'adresse :

Code : C
    // On mémorise la taille de l'adresse dans un variable
    int taille_client_adresse = sizeof (client_adresse);


Reconstitution du socket du client !



Il ne reste plus qu'a reconstituer le socket du client à l'intérieur des variables que l'on vient de déclarer. Heureusement pour nous, il existe une fonction pour faire la reconstitution : accept.

Vous l'aurrez compris, accepter une connexion se traduit par un reconstitution du socket du client dans le serveur.

Pour accepter la connexion, faite comme ceci :

Code : C
    // On accepte une demande de connexion
    client_socket = accept (serveur_socket, (SOCKADDR*)&client_adresse, &taille_client_adresse);


Et si on exécutait notre programme ?



Qu'on soit d'accord, on en est là :

Code : C
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h> // On va utiliser le réseau

int main()
{
    // On initialise le programme pour le réseau
    WSADATA data;
    WSAStartup (MAKEWORD (2,0), &data);

    // Création d'un socket pour notre serveur
    SOCKET serveur_socket = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);

    // Un socket sans adresse n'est rien.
    // On cré une adresse pour notre socket
    SOCKADDR_IN serveur_adresse;

    serveur_adresse.sin_family = AF_INET; // Type d'adresse
    serveur_adresse.sin_port = htons (2712); // Numéro du port à l'écoute : 2712
    serveur_adresse.sin_addr.s_addr = INADDR_ANY; // Adresse automatique du serveur
    memset (serveur_adresse.sin_zero, 0, 8); // Mise à zero des 8 derniers octets de la structure

    // Ligature du socket et de l'adresse
    bind (serveur_socket, (SOCKADDR*)&serveur_adresse, sizeof (serveur_adresse));

    // Mise à l'écoute du serveur
    listen (serveur_socket, 5);

    SOCKET client_socket; // On cré un socket pour notre client
    SOCKADDR_IN client_adresse; // On cré une adresse pour notre client

    // On mémorise la taille de l'adresse dans un variable
    int taille_client_adresse = sizeof (client_adresse);

    // On accepte une demande de connexion
    client_socket = accept (serveur_socket, (SOCKADDR*)&client_adresse, &taille_client_adresse);



    // On libère la mémoire utilisée pour le réseau
    WSACleanup();

    system ("PAUSE");
    return 0;
}


On lance le programme... Et ça bloque !
Le programme reste bloqué et n'arrive pas se terminer :

_


Pourquoi ça bloque ?

Tout simplement parce que le serveur attend une demande de connexion pour l'accepter. Or il n'y a personne qui veut se connecter sur le port 2712 : le serveur attend.

On dit que la fonction accept est une fonction bloquante. C'est à dire que le programme ne continue pas son exécution tant que l'exécution des cette fonction n'est pas terminé. Et vu que cette fonction ne se terminera que lorsqu'elle aurra accepté une connexion...

Provoquer une demande de connexion sur un port



Pour débloquer ce programme, il existe une solution simple. Il suffit d'utiliser un client et de faire une demande de connexion sur ce port.

On le trouve où ce client ?

Il est sous vos yeux ! Le navigateur que vous utilisez actuellement (Internet Explorer, Firefox, Konkeror...) pour consulter cette page Web est un client. Vous vous en êtes servi pour vous connecter au serveur de jeux-libres.com sans vous en rendre compte !


Un client : Faire une demande de connexion


Mettez votre serveur de côté en faisant attention de ne pas le perdre. Nous nous en reservirons juste après. Ensuite, créez un nouveau projet : nous allons repartir sur une nouvelle base.

Nous venons de voir comment faire un serveur : il suffisait de créer un socket, de le mettre à l'écoute sur un port et d'accepter la demande de connexion.

Maintenant, on va s'intéresser au client.

On va créer un socket, une adresse... Mais ce coup si on va faire une demande de connexion. Cette fois si, on n'utilisera pas la fonction accept mais la fonction connect.

En quelque sorte, c'est la fonction inverse : pour un serveur, c'est accept tandis que pour un client, c'est connect. L'un accepte la connexion, l'autre la demande.

Construction d'un client



Souvenez vous, je vous avez donné le code de base de toute les programmes qui utilise le réseau.

Le voici :

Code : C
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h> // On va utiliser le réseau

int main()
{
    // On initialise le programme pour le réseau
    WSADATA data;
    WSAStartup (MAKEWORD (2,0), &data);



    // On libère la mémoire utilisée pour le réseau
    WSACleanup();

    system ("PAUSE");
    return 0;
}


On cré un socket pour notre client.

Code : C
    // On cré un socket pour notre client
    SOCKET client_socket = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);


Un socket sans adresse n'est rien, c'est vrai. Mais cette fois-ci, on n'a pas besoin de mettre le socket à l'écoute. On a surtout besoin de se connecter sur le bon port du bon ordinateur.

On va donc créer une adresse non pas pour le client mais pour le serveur ! Comme ça, on pourra l'utiliser avec la fonction connect en sachant où est-ce que l'on doit faire notre demande de connexion.

Code : C
    // Déclaration de l'adresse du serveur
    SOCKADDR_IN serveur_adresse;


Et on remplis l'adresse du serveur comme il faut. L'adresse doit être suffisament bien informé pour que la fonction connect puisse établir la connexion.
Actuellement, on ne travaille que sur notre PC donc comme adresse on choisira 127.0.0.1. Pour ce qui est du numéro de port, je vous laisse deviner !

T'est gentil toi, mais comment on fait pour écrire l'adresse IP à l'intérieur de la structure ?

Il existe une fonction bien pratique pour ça : inet_addr. Et on l'utilise comme ça :

Code : C
    // On défini l'adresse IP du serveur
    serveur_adresse.sin_addr.s_addr = inet_addr ("127.0.0.1");


En clair, après avoir créé l'adresse du serveur, vous avez dû la remplir comme ça :

Code : C
    // On défini l'adresse IP et le port du serveur
    serveur_adresse.sin_family = AF_INET;
    serveur_adresse.sin_port = htons (2712);
    serveur_adresse.sin_addr.s_addr = inet_addr ("127.0.0.1");
    memset (serveur_adresse.sin_zero, 0, 8);


On va maintenant pouvoir s'y connecter !

Connexion au serveur !



On touche le but !
Il ne reste plus qu'a utiliser la fonction connect pour se connecter au serveur.

La fonction connect prend en compte les paramètres suivant :
- Le socket du client (Pour que le serveur sache qui on est pour qu'il puisse reconstituer notre socket).
- L'adresse du serveur (Pour savoir où est-ce que l'on doit se connecter).
- Et l'adresse mémoire d'une variable qui contient la taille de l'adresse du serveur (comme à chaque fois ).

Et oui, j'avais oublié de vous le dire : vous devez créer une variable et y mémoriser la taille de l'adresse :

Code : C
    // On cré une variable et on y mémorise la taille de l'adresse du serveur
    int taille_serveur_adresse = sizeof (serveur_adresse);


Et on se connecte !

Code : C
    // On fait une demande de connexion auprès du serveur
    connect (client_socket, (SOCKADDR*)&serveur_adresse, &taille_serveur_adresse);


Je comprend que vous ayez été très impatient d'en arriver là et que ayez sautez des étapes. Ne soyez pas perdu, on en est là :

Code : C
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h> // On va utiliser le réseau

int main()
{
    // On initialise le programme pour le réseau
    WSADATA data;
    WSAStartup (MAKEWORD (2,0), &data);

    // On cré un socket pour notre client
    SOCKET client_socket = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);

    // On cré une adresse pour que la fonction connect sache où se connecter
    SOCKADDR_IN serveur_adresse;

    // On défini l'adresse IP et le port du serveur
    serveur_adresse.sin_family = AF_INET;
    serveur_adresse.sin_port = htons (2712);
    serveur_adresse.sin_addr.s_addr = inet_addr ("127.0.0.1");
    memset (serveur_adresse.sin_zero, 0, 8);

    // On cré une variable et on y mémorise la taille de l'adresse du serveur
    int taille_serveur_adresse = sizeof (serveur_adresse);

    // On fait une demande de connexion auprès du serveur
    connect (client_socket, (SOCKADDR*)&serveur_adresse, &taille_serveur_adresse);
   
   

    // On libère la mémoire utilisée pour le réseau
    WSACleanup();

    system ("PAUSE");
    return 0;
}


Une fois de plus, il est possible que votre parefeu réagisse. Cette fois-ci, il a dû vous demander si vous vouliez bien que ce client face une demande de connexion auprès d'un serveur. Aparament, le parefeu de Windows ne réagit pas lorsqu'il s'agit d'une demande de connexion.

N'hésitez pas à me faire parvenir vos petites histoires affin que je puisse compléter le cours.

Connexion ou pas connexion ?



Si vous êtes perspicace, vous avez sans doute remarqué que la fonction connect n'est pas bloquante. Si la connexion a bien eu lieu, c'est bien. Mais si la connexion échoue pour une raison quelquonque, la fonction connect ne bloque pas le programme pour autant. Elle peut donc repartir bredouille de sa tentative de connexion et contenir une erreur. C'est quand même génant, n'est ce pas ?

Il va donc faloir vérifier que la connexion a bien eu lieu et agir en conséquence.

Je n'ai pas dû être très perspicace moi alors... Comment est ce qu'il falait faire pour constater que la fonction accept n'est pas bloquante ?

Il est très important que vous compreniez bien ça. Ecoutez moi bien.
Tout à l'heure, on a utilisé un navigateur pour lui faire faire une demande de connexion sur notre serveur. Je vous ai dis aussi qu'un navigateur était un client et qu'il pouvez se connecter aux serveurs de site Web (comme jeux-libres.com).

Et bien là, nous venons de créer notre client et, lui aussi, il peut faire une demande de connexion sur notre serveur. Lui aussi, il est en mesure de débloquer le programme serveur qui reste en attente d'une demande de connexion.

Testons notre client !



Pour qu'un client se connecte à un serveur, le serveur doit exister. On démarre donc le serveur en premier, il bloque :

_


Ensuite, on démarre notre client qui va faire une demande de connexion... Et toc, le serveur se débloque et se termine :

Appuyez sur un touche pour continuer... _


Très bien. Maintenant, revenons à nos moutons !
Je vous ai dis qu'après chaque tentative (ou demande) de connexion, il falait vérifier si la connexion avait bien eu lieu. On va donc apprendre à le faire.

La vérification est très simple. La fonction connect retourne une valeur particulière en cas d'echec de connexion. On va tout simplement s'en servir pour faire un teste.

Cette fameuse valeur, c'est : -1.

Dons maintenant, à chaque fois que vous voudrez faire une demande de connexion, vous ferrez comme ceci :

Code : C
/*
    Attention : Je n'ai volontairement pas écrit la fonction
    connect en totalité pour des raisons de clareté.
*/


    // On essait de se connecter
    if (connect() == -1)
    {
        // La tentative de connexion à échoué
        printf ("Connexion echoue !\n");
    }
    else
    {
        // La connexion à bien eu lieu, on peu continuer
        printf ("Connexion reussie !\n");
    }


Je vous laisse le soin de faire les modifications dans votre client et de constater le résultat. Lorsque le serveur est démarré, la connexion devrait s'établir :

Connexion reussie !
Appuyez sur une touche pour continuer... _


Dans le cas contraire, aucune connexion ne devrait avoir lieu :

Connexion echoue !
Appuyez sur une touche pour continuer... _


On récapitule !



Ce chapitre vous reservira, vous verrez. Afin d'éviter la relecture de tout ce chapitre, voici le code source du serveur et du client :

Serveur :

Code : C
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h> // On va utiliser le réseau

int main()
{
    // On initialise le programme pour le réseau
    WSADATA data;
    WSAStartup (MAKEWORD (2,0), &data);

    // Création d'un socket pour notre serveur
    SOCKET serveur_socket = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);

    // Un socket sans adresse n'est rien.
    // On cré une adresse pour notre socket
    SOCKADDR_IN serveur_adresse;

    serveur_adresse.sin_family = AF_INET; // Type d'adresse
    serveur_adresse.sin_port = htons (2712); // Numéro du port à l'écoute : 2712
    serveur_adresse.sin_addr.s_addr = INADDR_ANY; // Adresse automatique du serveur
    memset (serveur_adresse.sin_zero, 0, 8); // Mise à zero des 8 derniers octets de la structure

    // Ligature du socket et de l'adresse
    bind (serveur_socket, (SOCKADDR*)&serveur_adresse, sizeof (serveur_adresse));

    // Mise à l'écoute du serveur
    listen (serveur_socket, 5);

    SOCKET client_socket; // On cré un socket pour notre client
    SOCKADDR_IN client_adresse; // On cré une adresse pour notre client

    // On mémorise la taille de l'adresse dans un variable
    int taille_client_adresse = sizeof (client_adresse);

    // On accepte une demande de connexion
    client_socket = accept (serveur_socket, (SOCKADDR*)&client_adresse, &taille_client_adresse);



    // On libère la mémoire utilisée pour le réseau
    WSACleanup();

    system ("PAUSE");
    return 0;
}


Client :

Code : C
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h> // On va utiliser le réseau

int main()
{
    // On initialise le programme pour le réseau
    WSADATA data;
    WSAStartup (MAKEWORD (2,0), &data);

    // On cré un socket pour notre client
    SOCKET client_socket = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);

    // On cré une adresse pour que la fonction connect sache où se connecter
    SOCKADDR_IN serveur_adresse;

    // On défini l'adresse IP et le port du serveur
    serveur_adresse.sin_family = AF_INET;
    serveur_adresse.sin_port = htons (2712);
    serveur_adresse.sin_addr.s_addr = inet_addr ("127.0.0.1");
    memset (serveur_adresse.sin_zero, 0, 8);

    // On cré une variable et on y mémorise la taille de l'adresse du serveur
    int taille_serveur_adresse = sizeof (serveur_adresse);

    // On fait une demande de connexion auprès du serveur
    if (connect (client_socket, (SOCKADDR*)&serveur_adresse, &taille_serveur_adresse) == -1)
    {
        // La tentative de connexion à échoué
        printf ("Connexion echoue !\n");
    }
    else
    {
        // La connexion à bien eu lieu, on peu continuer
        printf ("Connexion reussie !\n");
    }



    // On libère la mémoire utilisée pour le réseau
    WSACleanup();

    system ("PAUSE");
    return 0;
}


Et si vous n'avez pas de compilateur sous la main mais que vous voulez tester nos petits programmes, je vous les met ici : serveur_client_connexion.zip.




Vous savez maintenant comment créer un jeux-vidéo multi-joueurs ! Non, je plaisante.
Désolé de vous décevoir mais nous n'avons encore rien vu. Mais vous avez maintenant toute les bases necessaires pour comprendre et aprendre à créer un serveur et un client !

Après ce chapitre d'adaptation, le chapitre sur le réseau pur et dur devrait mieux passer. J'espère, grâce à ce chapitre, éviter les messages comme : "c'est dur le réseau". Surtout, si vous avez le moindre petit bloquage pendant la lecture des ces chapitres, mailez moi à l'adresse davidlouiz@gmail.com immédiatement. Je vous en remercie d'avance.

Dans le chapitre suivant, ******* (vous verrez ! )



Rédigé par David



Hébergeur du site : 1and1.fr



Site de création de Jeux Vidéo
Apprenez à créer vos propres Jeux Video

A propos de la construction du site...
352262 pages ont été consultées sur le site !
Dont 256 pages pendant les 24 dernières heures.

Page générée en 0.824 secondes


Nos partenaires
- Otium Production : Aide aux débutants à créer leurs jeux
- Construis ton jeu en PHP : Apprenez à créer votre jeu en PHP
- A.C.S.E.L. : Club de patinage artistique de Caen


  © 2005-2008 www.jeux-libres.com - Toute reproduction totale ou partielle du contenu de ce site est strictement interdite.