Annonce

Réduire
Aucune annonce.

Les sockets : développement de modules client et serveur.

Réduire
X
 
  • Filtre
  • Heure
  • Afficher
Tout nettoyer
nouveaux messages

  • Tutoriel Les sockets : développement de modules client et serveur.

    Bonsoir à tous.

    Ce tutoriel portera sur l'utilisation des sockets en langage C, et notamment sur la création d'un module client et d'un module serveur. Cependant, le langage de démonstration importe peu, car fondamentalement le fonctionnement méthodique des sockets et leur architecture sont relativement les mêmes pour tous les langages prenant en compte leur gestion.

    Ce tutoriel est mené sous Windows pour des raisons de commodité (et surtout parce que je suis sur ma session Win7), nous utiliserons donc la librairie WinSock. Toutefois, le développement est identique sous Linux, seuls l'appel aux librairies (sys/socket.h au lieu de winsock2.h), et l'initialisation de l'API diffèrent. L'IDE utilisé est Code::Blocks.



    I. Introduction

    Les sockets sont des objets, effectifs en couche 5 du modèle OSI, permettant d'établir des tunnels de liaison unidirectionnelle ou bidirectionnelle (selon la finalité recherchée) entre deux machines distantes, typiquement selon une topologie serveur-client.
    Leur protocole de définition standard leur confère une très grande flexibilité et stabilité, et leur fonctionnement intuitif en font des outils de connexion de premier ordre.




    II. Préparation liminaire

    Après avoir ouvert un nouveau projet « Console application » avec Code::Blocks, nous allons commencer par linker les librairies nécessaires à nos développements :

    Project → Build options... → Linker settings → Add → (aller dans mingw/lib/) et sélectionner libws2_32.a

    Donc nous venons de linker la librairie WinSock2 (version 2) qui contient tous les appels fonctionnels futurs.

    Ensuite, nous allons inclure les headers nécessaires, notamment WinSock2 :

    Code:
    #include <stdio.h>
    #include <winsock2.h>
    
    int main() {
    
    return 0;
    }
    Voilà, le socle est posé, nous pouvons commencer le développement.




    III.Création de la socket globale

    A) Activation de l'API WinSock2
    Comme nous l'avons vu, nous allons utiliser l'API native WinSock2 fournie par Windows. Avant toute déclaration de sockets cette API doit être activée, ceci permet d'initialiser la pile TCP dédiée à l'application. Pour ce faire, Windows nous met à disposition la fonction standard WSAStartup(), qui renvoi la valeur 0 quand l'initialisation s'est effectuée avec succès.

    Elle se compose comme suit :
    Code:
    int WSAStartup(
      _In_ WORD wVersionRequested,
      _Out_ LPWSADATA lpWSAData
    );
    wVersionRequested → spécifie la version la plus haute (last updated) utilisée pour le protocole de déclaration des sockets, c'est à dire à partir de quel fichier-version le socket s'initialisera.
    lpWSAData → pointeur vers la structure WSADATA qui recevra l'implémentation du socket.

    Pour l'utiliser, nous devons tout d'abord déclarer une variable de type WSADATA.


    Code:
    #include <stdio.h>
    #include <winsock2.h>
    
    int main() {
    
    WSADATA wsadata; // déclaration de la variable de type WSADATA
    
    /***********************************************************************
    Le paramètre « MAKEWORD » contient la version de WinSock utilisée, ici la version 2.
    Le deuxième paramètre est le pointeur renvoyant vers la structure WSADATA.
    ***********************************************************************/
    WSAStartup(MAKEWORD(2,2),&wsadata);
    
    // on test l'initialisation
    if((WSAStartup(MAKEWORD(2,2),&wsadata))!=0) { // l'initialisation a échoué
    printf("Initialization failed →  %d",WSAGetLastError());
    return 1;
    }
    else
    printf("WSA initialized successfully");
    
    return 0;
    }
    Voilà, l'API est maintenant correctement activée, nous pouvons déclarer les objets sockets qui nous serviront dans l'établissement de la connexion.



    B ) Déclaration de l'objet socket
    La seconde étape du développement consiste donc à créer une socket unique qui catalysera l'ensemble des appels fonctionnels de la procédure d'écoute, d'envoi et de réception. La socket est créée par la fonction socket().

    Elle se compose de la façon suivante :
    Code:
    SOCKET WSAAPI socket(
      _In_  int af,
      _In_  int type,
      _In_  int protocol
    );
    af → famille de l'adresse (AF_INET = IPV4, AF_INET6 = IPV6, AF_NETBIOS = adresse netbios, AF_BTH = bluetooh...)
    type → type de communication utilisé (SOCK_STREAM = TCP, SOCK_DGRAM = UDP, SOCK_RDM = PGM...)
    protocol → protocole d'échange standard utilisé (IPPROTO_ICMP = ICMP, IPPROTO_TCP = TCP, IPPROTO_UDP = UDP...)

    Pour l'utiliser, nous devons d'abord déclarer un objet SOCKET qui lui sera lié.


    Code:
    #include <stdio.h>
    #include <winsock2.h>
    
    int main() {
    
    /******************* ACTIVATION DE L'API **************/
    WSADATA wsadata; // déclaration de la variable de type WSADATA
    
    /***********************************************************************
    Le paramètre « MAKEWORD » contient la version de WinSock utilisée, ici la version 2.
    Le deuxième paramètre est le pointeur renvoyant vers la structure WSADATA.
    ***********************************************************************/
    WSAStartup(MAKEWORD(2,2),&wsadata); // activation
    
    // on test l'initialisation
    if((WSAStartup(MAKEWORD(2,2),&wsadata))!=0) { // l'initialisation a échoué
    printf("Initialization failed →  %d",WSAGetLastError());
    return 1;
    }
    else
    printf("WSA initialized successfully");
    /*************** FIN D'ACTIVATION DE L'API ***************/
    
    
    /*********** INITIALISATION DU SOCKET **************/
    SOCKET iSocket; // déclaration de l'objet socket
    
    /**********************************************************************
    La premier paramètre « AF_INET » signifie que l'on choisit la famille bidirectionnelle
    Le deuxième paramètre « SOCK_STREAM » signifie que l'on choisit le type TCP
    Le troisième paramètre « IPPROTO_TCP » signifie le choix du protocole TCP
    **********************************************************************/
    iSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); // initialisation
    
    // on test l'initialisation
    if(iSocket == INVALID_SOCKET) { // échec de l'initialisation
    printf("Socket initialization failed → %d",WSAGetLastError());
    WSACleanup(); // libération de l'API
    return 1;
    }
    else
    printf("Socket initialized successfully");
    /*************** FIN DE L'INITIALISATION DU SOCKET ************/
    
    return 0;
    }

    L'objet socket global est maintenant correctement créé et initialisé. Nous pouvons nous en servir pour les appels fonctionnels de communication.

    Arrivé à cette étape, le développement est uniquement fonction du module d'interfaçage envisagé : client ou serveur. Nous commencerons par étudier les fonctions propres au client, puis les fonctions propres au serveur.




    IV. Développement côté client

    A) Établissement de la connexion
    Il appartient au client d'ouvrir le tunnel de communication vers le serveur, pour des raisons techniques évidentes : le serveur possède une adresse IP fixe, et celle du client peut quant à elle varier.

    La première fonction à mettre en place est donc celle qui établit la connexion, nous utiliserons pour cela la fonction connect().

    Elle se compose comme suit :
    Code:
    int connect(
      _In_  SOCKET s,
      _In_  const struct sockaddr *name,
      _In_  int namelen
    );
    s → le socket par l'intermédiaire duquel on ouvre la connexion
    *name → pointeur vers la structure d'adressage contenant adresse IP et port
    namelen → taille de la structure d'adressage

    Avant de l'utiliser, nous devons déclarer une nouvelle structure d'adressage via l'objet SOCKADDR_IN.


    Code:
    #include <stdio.h>
    #include <winsock2.h>
    
    int main() {
    
    /******************* ACTIVATION DE L'API **************/
    WSADATA wsadata; // déclaration de la variable de type WSADATA
    
    /***********************************************************************
    Le paramètre « MAKEWORD » contient la version de WinSock utilisée, ici la version 2.
    Le deuxième paramètre est le pointeur renvoyant vers la structure WSADATA.
    ***********************************************************************/
    WSAStartup(MAKEWORD(2,2),&wsadata); // activation
    
    // on test l'initialisation
    if((WSAStartup(MAKEWORD(2,2),&wsadata))!=0) { // l'initialisation a échoué
    printf("Initialization failed →  %d",WSAGetLastError());
    return 1;
    }
    else
    printf("WSA initialized successfully");
    /*************** FIN D'ACTIVATION DE L'API ***************/
    
    
    /*********** INITIALISATION DU SOCKET **************/
    SOCKET iSocket; // déclaration de l'objet socket
    
    /**********************************************************************
    La premier paramètre « AF_INET » signifie que l'on choisit la famille bidirectionnelle
    Le deuxième paramètre « SOCK_STREAM » signifie que l'on choisit le type TCP
    Le troisième paramètre « IPPROTO_TCP » signifie le choix du protocole TCP
    **********************************************************************/
    iSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); // initialisation
    
    // on test l'initialisation
    if(iSocket == INVALID_SOCKET) { // échec de l'initialisation
    printf("Socket initialization failed → %d",WSAGetLastError());
    WSACleanup(); // libération de l'API
    return 1;
    }
    else
    printf("Socket initialized successfully");
    /*************** FIN DE L'INITIALISATION DU SOCKET ************/
    
    
    
    /************** ETABLISSEMENT DE LA CONNEXION AU SERVEUR *********/
    SOCKADDR_IN iSocketAddr; // déclaration de la structure d'adressage
    
    char adresse_ip_serveur[] = "xxx.xxx.xxx.xx.xxx"; // définition de l'adresse IP du serveur
    int port = 80; // définition du port de destination
    
    iSocketAddr.sin_family = AF_INET; // structure de famille AF_INET (pour TCP)
    iSocket_Addr.sin_addr.s_addr = inet_addr(adresse_ip_serveur); // passage de l'adresse IP du serveur en adresse de destination
    iSocket_Addr.sin_port = htons(port); // passage du port du serveur en port de destination
    
    // établissement de la connexion
    if(connect(iSocket,(struct sockaddr*)&iSocketAddr,sizeof(iSocketAddr))!=0) { // échec de la connexion
    printf("Connexion failed → %d",WSAGetLastError());
    return 1;
    }
    else
    printf("Connexion established");
    /********** FIN D'ETABLISSEMENT DE LA CONNEXION AU SERVEUR **********/
    return 0;
    }
    A ce stade, la connexion est correctement établie.



    B ) Envoi de données
    La connexion établie, le client peut enfin envoyer des données au serveur. Ceci est possible via l'appel à la fonction send().

    Elle se compose de cette manière :
    Code:
    int send(
      _In_  SOCKET s,
      _In_  const char *buf,
      _In_  int len,
      _In_  int flags
    );
    s → socket utilisé pour envoyer les données
    *buf → pointeur vers le buffer alloué au stockage des données à envoyer
    len → taille des données contenues dans le buffer
    flags → spécification du type de routage, généralement mis à 0


    Code:
    #include <stdio.h>
    #include <winsock2.h>
    
    int main() {
    
    /******************* ACTIVATION DE L'API **************/
    WSADATA wsadata; // déclaration de la variable de type WSADATA
    
    /***********************************************************************
    Le paramètre « MAKEWORD » contient la version de WinSock utilisée, ici la version 2.
    Le deuxième paramètre est le pointeur renvoyant vers la structure WSADATA.
    ***********************************************************************/
    WSAStartup(MAKEWORD(2,2),&wsadata); // activation
    
    // on test l'initialisation
    if((WSAStartup(MAKEWORD(2,2),&wsadata))!=0) { // l'initialisation a échoué
    printf("Initialization failed →  %d",WSAGetLastError());
    return 1;
    }
    else
    printf("WSA initialized successfully");
    /*************** FIN D'ACTIVATION DE L'API ***************/
    
    
    /*********** INITIALISATION DU SOCKET **************/
    SOCKET iSocket; // déclaration de l'objet socket
    
    /**********************************************************************
    La premier paramètre « AF_INET » signifie que l'on choisit la famille bidirectionnelle
    Le deuxième paramètre « SOCK_STREAM » signifie que l'on choisit le type TCP
    Le troisième paramètre « IPPROTO_TCP » signifie le choix du protocole TCP
    **********************************************************************/
    iSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); // initialisation
    
    // on test l'initialisation
    if(iSocket == INVALID_SOCKET) { // échec de l'initialisation
    printf("Socket initialization failed → %d",WSAGetLastError());
    WSACleanup(); // libération de l'API
    return 1;
    }
    else
    printf("Socket initialized successfully");
    /*************** FIN DE L'INITIALISATION DU SOCKET ************/
    
    
    
    /************** ETABLISSEMENT DE LA CONNEXION AU SERVEUR *********/
    SOCKADDR_IN iSocketAddr; // déclaration de la structure d'adressage
    
    char adresse_ip_serveur[] = "xxx.xxx.xxx.xx.xxx"; // définition de l'adresse IP du serveur
    int port = 80; // définition du port de destination
    
    iSocketAddr.sin_family = AF_INET; // structure de famille AF_INET (pour TCP)
    iSocket_Addr.sin_addr.s_addr = inet_addr(adresse_ip_serveur); // passage de l'adresse IP du serveur en adresse de destination
    iSocket_Addr.sin_port = htons(port); // passage du port du serveur en port de destination
    
    // établissement de la connexion
    if(connect(iSocket,(struct sockaddr*)&iSocketAddr,sizeof(iSocketAddr))!=0) { // échec de la connexion
    printf("Connexion failed → %d",WSAGetLastError());
    WSACleanup(); // libération de l'API
    return 1;
    }
    else
    printf("Connexion established");
    /********** FIN D'ETABLISSEMENT DE LA CONNEXION AU SERVEUR **********/
    
    
    
    /********* ENVOI DES DONNEES AU SERVEUR ********/
    #define BUFFER_LEN 65000 // taille du buffer contenant les données à envoyer
    char datasToSendBuffer[BUFFER_LEN]; // initialisation du buffer contenant les données à envoyer
    
    /**************************************************************************
    Les données à envoyer peuvent être de tout type, numérique (valeurs numériques, ex : clé de validation) ou sous format texte.
    Pour envoyer une string, l'on peut soit la déclarer directement dans le buffer comme ceci : char datasToSendBuffer[] = "Hello le serveur !";   soit copier dynamiquement la chaine dans le buffer via la fonction strcopy(). Cette dernière option est à privilégier car la mémoire est gérer plus précisément.
    ***************************************************************************/
    strcpy(datasToSendBuffer, "Hello le serveur !"); // copie dynamique dans le buffer
    
    // envoi des données
    if((connect(iSocket, datasToSendBuffer, strlen(datasToSendBuffer), 0))!=0) { // échec de l'envoi
    printf("Datas sending failed → %d", WSAGetLastError());
    WSACleanup(); // libération de l'API
    return 1;
    }
    else
    printf("Datas sent successfully");
    /********* FIN DE L'ENVOI DES DONNEES AU SERVEUR ********/
    
    return 0;
    }

    La partie client est maintenant terminée, toutefois il est nécessaire de libérer la pile TCP en fermant la connexion. Ceci s'effectue via la fonction shutdown().

    Elle se compose comme suit :
    Code:
    int shutdown(
      _In_  SOCKET s,
      _In_  int how
    );
    s → socket utilisée par la session TCP
    how → type de communication à fermer (0 = réception, 1 = envoi, 2 = envoi et réception)


    Code:
    if((shutdown(iSocket,2))!=0) { // échec de la fermeture
    printf("TCP session closing → %d", WSAGetLastError());
    WSACleanup(); // libération de l'API
    return 1;
    }
    else
    printf("TCP session closed successfully");

    Après avoir fermé la session TCP, nous devons libérer l'objet socket utilisé pendant le développement. Ceci s'effectue via la fonction closesocket().

    Elle se compose comme suit :
    Code:
    int closesocket(
      _In_  SOCKET s
    );
    s → socket à libérer


    Code:
    if((closesocket(iSocket))!=0) { // échec de la libération
    printf("Socket closing → %d", WSAGetLastError());
    WSACleanup(); // libération de l'API
    return 1;
    }
    else
    printf("Socket closed successfully");

    Pour finir, l'API WinSock doit être désactivée afin de libérer le canal. Ceci s'effectue via la fonction WSACleanup() ; cette fonction doit être utilisée autant de fois que l'API a été activée dans le développement.


    Code:
    if((WSACleanup())!=0) { // échec de la désactivation
    printf("WSA deactivation → %d", WSAGetLastError());
    return 1;
    }
    else
    printf("WSA deactived successfully");

    Comme vous l'aurez compris, ces trois bouts de code sont à ajouter en fin de partie client.
    Le module client est maintenant totalement terminé.





    V. Développement côté serveur

    A) Synchronisation
    Afin de pouvoir recevoir les connexions de la part des clients, la socket serveur doit être synchronisée sur un port et une adresse IP d'écoute. Pour ce faire, nous utilisons la fonction bind().

    Elle se compose comme suit :
    Code:
    int bind(
      _In_  SOCKET s,
      _In_  const struct sockaddr *name,
      _In_  int namelen
    );
    s → socket à synchroniser
    *name → pointeur vers la structure d'adressage
    namelen → taille de la structure d'adressage


    Code:
    #include <stdio.h>
    #include <winsock2.h>
    
    int main() {
    
    /******************* ACTIVATION DE L'API **************/
    WSADATA wsadata; // déclaration de la variable de type WSADATA
    
    /***********************************************************************
    Le paramètre « MAKEWORD » contient la version de WinSock utilisée, ici la version 2.
    Le deuxième paramètre est le pointeur renvoyant vers la structure WSADATA.
    ***********************************************************************/
    WSAStartup(MAKEWORD(2,2),&wsadata); // activation
    
    // on test l'initialisation
    if((WSAStartup(MAKEWORD(2,2),&wsadata))!=0) { // l'initialisation a échoué
    printf("Initialization failed →  %d",WSAGetLastError());
    return 1;
    }
    else
    printf("WSA initialized successfully");
    /*************** FIN D'ACTIVATION DE L'API ***************/
    
    
    /*********** INITIALISATION DU SOCKET **************/
    SOCKET iSocket; // déclaration de l'objet socket
    
    /**********************************************************************
    La premier paramètre « AF_INET » signifie que l'on choisit la famille bidirectionnelle
    Le deuxième paramètre « SOCK_STREAM » signifie que l'on choisit le type TCP
    Le troisième paramètre « IPPROTO_TCP » signifie le choix du protocole TCP
    **********************************************************************/
    iSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); // initialisation
    
    // on test l'initialisation
    if(iSocket == INVALID_SOCKET) { // échec de l'initialisation
    printf("Socket initialization failed → %d",WSAGetLastError());
    WSACleanup(); // libération de l'API
    return 1;
    }
    else
    printf("Socket initialized successfully");
    /*************** FIN DE L'INITIALISATION DU SOCKET ************/
    
    
    
    /********** SYNCHRONISATION DU SOCKET **************/
    SOCKADDR_IN iSocketAddr; // déclaration de la structure d'adressage
    
    int port = 80; // définition du port d'écoute
    
    iSocketAddr.sin_family = AF_INET; // structure de famille AF_INET (pour TCP)
    iSocket_Addr.sin_addr.s_addr = INADDR_ANY; // comme l'on ne connait pas l'IP du client qui se connectera, on écoute toutes les IPs entrantes
    iSocket_Addr.sin_port = htons(port); // passage du port d'écoute
    
    // établissement de la synchronisation
    if(bind(iSocket,(struct sockaddr*)&iSocketAddr,sizeof(iSocketAddr))!=0) { // échec de la synchronisation
    printf("Synchronization failed → %d",WSAGetLastError());
    WSACleanup(); // libration de l'API
    return 1;
    }
    else
    printf("Synchronization established");
    /***** FIN DE SYNCHRONISATION DU SOCKET ************/
    
    return 0;
    }

    La socket étant maintenant synchronisée, le serveur doit écouter le port de réception en attente de connexions entrantes.



    B ) Mise en écoute
    Afin d'identifier et d'accepter les connexions entrantes des clients, le serveur doit systématiquement être placé en attente de connexions, c'est la mise en écoute. Ceci s'effectue via la fonction listen().

    Elle se compose de cette façon :
    Code:
    int listen(
      _In_  SOCKET s,
      _In_  int backlog
    );
    s → socket utilisée pour l'écoute
    backlog → taille de la file d'attente


    Code:
    #include <stdio.h>
    #include <winsock2.h>
    
    int main() {
    
    /******************* ACTIVATION DE L'API **************/
    WSADATA wsadata; // déclaration de la variable de type WSADATA
    
    /***********************************************************************
    Le paramètre « MAKEWORD » contient la version de WinSock utilisée, ici la version 2.
    Le deuxième paramètre est le pointeur renvoyant vers la structure WSADATA.
    ***********************************************************************/
    WSAStartup(MAKEWORD(2,2),&wsadata); // activation
    
    // on test l'initialisation
    if((WSAStartup(MAKEWORD(2,2),&wsadata))!=0) { // l'initialisation a échoué
    printf("Initialization failed →  %d",WSAGetLastError());
    return 1;
    }
    else
    printf("WSA initialized successfully");
    /*************** FIN D'ACTIVATION DE L'API ***************/
    
    
    /*********** INITIALISATION DU SOCKET **************/
    SOCKET iSocket; // déclaration de l'objet socket
    
    /**********************************************************************
    La premier paramètre « AF_INET » signifie que l'on choisit la famille bidirectionnelle
    Le deuxième paramètre « SOCK_STREAM » signifie que l'on choisit le type TCP
    Le troisième paramètre « IPPROTO_TCP » signifie le choix du protocole TCP
    **********************************************************************/
    iSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); // initialisation
    
    // on test l'initialisation
    if(iSocket == INVALID_SOCKET) { // échec de l'initialisation
    printf("Socket initialization failed → %d",WSAGetLastError());
    WSACleanup(); // libération de l'API
    return 1;
    }
    else
    printf("Socket initialized successfully");
    /*************** FIN DE L'INITIALISATION DU SOCKET ************/
    
    
    
    /********** SYNCHRONISATION DU SOCKET **************/
    SOCKADDR_IN iSocketAddr; // déclaration de la structure d'adressage
    
    int port = 80; // définition du port d'écoute
    
    iSocketAddr.sin_family = AF_INET; // structure de famille AF_INET (pour TCP)
    iSocket_Addr.sin_addr.s_addr = INADDR_ANY; // comme l'on ne connait pas l'IP du client qui se connectera, on écoute toutes les IPs entrantes
    iSocket_Addr.sin_port = htons(port); // passage du port d'écoute
    
    // établissement de la synchronisation
    if(bind(iSocket,(struct sockaddr*)&iSocketAddr,sizeof(iSocketAddr))!=0) { // échec de la synchronisation
    printf("Synchronization failed → %d",WSAGetLastError());
    WSACleanup(); // libration de l'API
    return 1;
    }
    else
    printf("Synchronization established");
    /***** FIN DE SYNCHRONISATION DU SOCKET ************/
    
    
    
    /****** ECOUTE DE CONNEXIONS ENTRANTES *******/
    int switch = 1; // switch de contrôle de la boucle
    
    while(switch!=0) { // en attente de connexion (connexion effectuée → switch = 0)
    switch = listen(iSocket,5); // mise en écoute pour 5 connexions clients
    }
    printf("Awaiting connexion...");
    /********* FIN D'ECOUTE DE CONNEXIONS ENTRANTES *****/
    
    return 0;
    }

    Le serveur peut maintenant recevoir la demande de connexion de la part d'un client. Une fois reçue, il doit accepter cette connexion.




    C) Validation de l'établissement de la connexion
    Quand un client envoi une demande de connexion au serveur, ce dernier doit valider l'ouverture de la session TCP via la fonction accept().

    Elle se compose de cette manière :
    Code:
    SOCKET accept(
      _In_     SOCKET s,
      _Out_    struct sockaddr *addr,
      _Inout_  int *addrlen
    );
    s → socket par laquelle s'effectue l'établissement de la connexion
    *addr → pointeur vers la structure d'adressage
    *addrlen → pointeur vers la taille de la structure d'adressage

    Cette fonction est particulière car elle nécessite la déclaration d'un nouvel objet socket, le premier étant monopolisé par la fonction listen().


    Code:
    #include <stdio.h>
    #include <winsock2.h>
    
    int main() {
    
    /******************* ACTIVATION DE L'API **************/
    WSADATA wsadata; // déclaration de la variable de type WSADATA
    
    /***********************************************************************
    Le paramètre « MAKEWORD » contient la version de WinSock utilisée, ici la version 2.
    Le deuxième paramètre est le pointeur renvoyant vers la structure WSADATA.
    ***********************************************************************/
    WSAStartup(MAKEWORD(2,2),&wsadata); // activation
    
    // on test l'initialisation
    if((WSAStartup(MAKEWORD(2,2),&wsadata))!=0) { // l'initialisation a échoué
    printf("Initialization failed →  %d",WSAGetLastError());
    return 1;
    }
    else
    printf("WSA initialized successfully");
    /*************** FIN D'ACTIVATION DE L'API ***************/
    
    
    /*********** INITIALISATION DU SOCKET **************/
    SOCKET iSocket; // déclaration de l'objet socket
    
    /**********************************************************************
    La premier paramètre « AF_INET » signifie que l'on choisit la famille bidirectionnelle
    Le deuxième paramètre « SOCK_STREAM » signifie que l'on choisit le type TCP
    Le troisième paramètre « IPPROTO_TCP » signifie le choix du protocole TCP
    **********************************************************************/
    iSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); // initialisation
    
    // on test l'initialisation
    if(iSocket == INVALID_SOCKET) { // échec de l'initialisation
    printf("Socket initialization failed → %d",WSAGetLastError());
    WSACleanup(); // libération de l'API
    return 1;
    }
    else
    printf("Socket initialized successfully");
    /*************** FIN DE L'INITIALISATION DU SOCKET ************/
    
    
    
    /********** SYNCHRONISATION DU SOCKET **************/
    SOCKADDR_IN iSocketAddr; // déclaration de la structure d'adressage
    
    int port = 80; // définition du port d'écoute
    
    iSocketAddr.sin_family = AF_INET; // structure de famille AF_INET (pour TCP)
    iSocket_Addr.sin_addr.s_addr = INADDR_ANY; // comme l'on ne connait pas l'IP du client qui se connectera, on écoute toutes les IPs entrantes
    iSocket_Addr.sin_port = htons(port); // passage du port d'écoute
    
    // établissement de la synchronisation
    if(bind(iSocket,(struct sockaddr*)&iSocketAddr,sizeof(iSocketAddr))!=0) { // échec de la synchronisation
    printf("Synchronization failed → %d",WSAGetLastError());
    WSACleanup(); // libération de l'API
    return 1;
    }
    else
    printf("Synchronization established");
    /***** FIN DE SYNCHRONISATION DU SOCKET ************/
    
    
    
    /****** ECOUTE DE CONNEXIONS ENTRANTES *******/
    int switch = 1; // switch de contrôle de la boucle
    
    while(switch!=0) { // en attente de connexion (connexion effectuée → switch = 0)
    switch = listen(iSocket,5); // mise en écoute pour 5 connexions clients
    }
    printf("Awaiting connexion...");
    /********* FIN D'ECOUTE DE CONNEXIONS ENTRANTES *****/
    
    
    
    /********* VALIDATION D'OUVERTURE DE SESSION TCP *******/
    SOCKET iSocket2; // déclaration de la deuxième socket
    
    // validation de l'ouverture TCP via la nouvelle socket
    iSocket2 = accept(iSocket,(struct sockaddr*)&iSocket_Addr,&sizeof(iSocket_Addr));
    if(iSocket2 == INVALID_SOCKET) { // échec de la validation
    printf("TCP session establishement failed→ %d",WSAGetLastError());
    WSACleanup(); // libération de l'API
    return 1;
    }
    else
    printf("TCP session established");
    /***** FIN DE VALIDATION D'OUVERTURE DE SESSION TCP *****/
    
    return 0;
    }

    La connexion TCP est maintenant établie entre le client et le serveur. Ce dernier doit maintenant récupérer dans un tableau les données envoyées par le client.




    D. Récupération des données envoyées par le client
    Une fois la connexion établie, le serveur doit être capable de récupérer les données envoyées par le client, de les stocker puis, pourquoi pas, les afficher. Ceci s'effectue via la fonction recv().

    Elle se compose comme suit :
    Code:
    int recv(
      _In_   SOCKET s,
      _Out_  char *buf,
      _In_   int len,
      _In_   int flags
    );
    s → socket utilisée pour la réception
    *buf → pointeur vers le buffer stockant les données reçues
    len → taille du buffer de réception des données
    flags → gestion du comportement de routage, généralement mis à 0


    Code:
    #include <stdio.h>
    #include <winsock2.h>
    
    int main() {
    
    /******************* ACTIVATION DE L'API **************/
    WSADATA wsadata; // déclaration de la variable de type WSADATA
    
    /***********************************************************************
    Le paramètre « MAKEWORD » contient la version de WinSock utilisée, ici la version 2.
    Le deuxième paramètre est le pointeur renvoyant vers la structure WSADATA.
    ***********************************************************************/
    WSAStartup(MAKEWORD(2,2),&wsadata); // activation
    
    // on test l'initialisation
    if((WSAStartup(MAKEWORD(2,2),&wsadata))!=0) { // l'initialisation a échoué
    printf("Initialization failed →  %d",WSAGetLastError());
    return 1;
    }
    else
    printf("WSA initialized successfully");
    /*************** FIN D'ACTIVATION DE L'API ***************/
    
    
    /*********** INITIALISATION DU SOCKET **************/
    SOCKET iSocket; // déclaration de l'objet socket
    
    /**********************************************************************
    La premier paramètre « AF_INET » signifie que l'on choisit la famille bidirectionnelle
    Le deuxième paramètre « SOCK_STREAM » signifie que l'on choisit le type TCP
    Le troisième paramètre « IPPROTO_TCP » signifie le choix du protocole TCP
    **********************************************************************/
    iSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); // initialisation
    
    // on test l'initialisation
    if(iSocket == INVALID_SOCKET) { // échec de l'initialisation
    printf("Socket initialization failed → %d",WSAGetLastError());
    WSACleanup(); // libération de l'API
    return 1;
    }
    else
    printf("Socket initialized successfully");
    /*************** FIN DE L'INITIALISATION DU SOCKET ************/
    
    
    
    /********** SYNCHRONISATION DU SOCKET **************/
    SOCKADDR_IN iSocketAddr; // déclaration de la structure d'adressage
    
    int port = 80; // définition du port d'écoute
    
    iSocketAddr.sin_family = AF_INET; // structure de famille AF_INET (pour TCP)
    iSocket_Addr.sin_addr.s_addr = INADDR_ANY; // comme l'on ne connait pas l'IP du client qui se connectera, on écoute toutes les IPs entrantes
    iSocket_Addr.sin_port = htons(port); // passage du port d'écoute
    
    // établissement de la synchronisation
    if(bind(iSocket,(struct sockaddr*)&iSocketAddr,sizeof(iSocketAddr))!=0) { // échec de la synchronisation
    printf("Synchronization failed → %d",WSAGetLastError());
    WSACleanup(); // libération de l'API
    return 1;
    }
    else
    printf("Synchronization established");
    /***** FIN DE SYNCHRONISATION DU SOCKET ************/
    
    
    
    /****** ECOUTE DE CONNEXIONS ENTRANTES *******/
    int switch = 1; // switch de contrôle de la boucle
    
    while(switch!=0) { // en attente de connexion (connexion effectuée → switch = 0)
    switch = listen(iSocket,5); // mise en écoute pour 5 connexions clients
    }
    printf("Awaiting connexion...");
    /********* FIN D'ECOUTE DE CONNEXIONS ENTRANTES *****/
    
    
    
    /********* VALIDATION D'OUVERTURE DE SESSION TCP *******/
    SOCKET iSocket2; // déclaration de la deuxième socket
    
    // validation de l'ouverture TCP via la nouvelle socket
    iSocket2 = accept(iSocket,(struct sockaddr*)&iSocket_Addr,&sizeof(iSocket_Addr));
    if(iSocket2 == INVALID_SOCKET) { // échec de la validation
    printf("TCP session establishement failed→ %d",WSAGetLastError());
    WSACleanup(); // libération de l'API
    return 1;
    }
    else
    printf("TCP session established");
    /***** FIN DE VALIDATION D'OUVERTURE DE SESSION TCP *****/
    
    
    
    /***** RECEPTION DES DONNEES CLIENT *****/
    #define BUFFER_LEN 65000 // taille du buffer contenant les données reçues
    char datasToStockBuffer[BUFFER_LEN]; // initialisation du buffer contenant les données reçues
    
    // réception des données
    if((recv(iSocket2, datasToStockBuffer, BUFFER_LEN, 0)) == INVALID_SOCKET) { // échec 
    printf("Datas reception failed → %d",WSAGetLastError());
    WSACleanup(); // libération de l'API
    return 1;
    }
    else {
    datasToStockBuffer[recv(iSocket2, datasToStockBuffer, BUFFER_LEN, 0)] = 0; // fermeture de flux de sortie du tableau
    printf("Datas received : %s", datasToStockBuffer);
    }
    /************ FIN DE RECEPTION DES DONNEES CLIENT *****/
    
    
    /***** FERMETURE SESSION TCP, LIBERATION SOCKETS ET API ******/
    // fermeture session TCP
    if((shutdown(iSocket2,2))!=0) { // échec de la fermeture
    printf("TCP session closing → %d", WSAGetLastError());
    WSACleanup(); // libération de l'API
    return 1;
    }
    else
    printf("TCP session closed successfully");
    
    // libération des sockets
    if((closesocket(iSocket))!=0) { // échec de la libération
    printf("Socket closing → %d", WSAGetLastError());
    WSACleanup(); // libération de l'API
    return 1;
    }
    else
    printf("Socket closed successfully");
    if((closesocket(iSocket2))!=0) { // échec de la libération
    printf("Socket closing → %d", WSAGetLastError());
    WSACleanup(); // libération de l'API
    return 1;
    }
    else
    printf("Socket closed successfully");
    
    // désactivation de l'API
    if((WSACleanup())!=0) { // échec de la désactivation
    printf("WSA deactivation → %d", WSAGetLastError());
    return 1;
    }
    else
    printf("WSA deactived successfully");
    
    return 0;
    }

    Le module serveur est maintenant totalement terminé.
    -------------------------------------------------------

    N'hésitez pas à me demander d'éclaircir d'autres points concernant les sockets, j'éditerai mon post au fur et à mesure.



    Ex-membre Hackademiciens.

  • #2
    Pourrais tu expliquer la gestion des threads avec un serveur, je cherche depuis longtemps mais n'arrive toujours pas à comprendre :'(

    Sinon superbe tutoriel

    Commentaire

    Chargement...
    X