Serveur UDP / Client UDP, non bloquant

Deux classes permettant d'échanger facilement des messages textuels sur le réseau par protocole UDP.

Classe ServeurUDP

serveurUDP.h
#ifndef SERVEURUDP_H_INCLUDED
#define SERVEURUDP_H_INCLUDED
 
#include <winsock2.h>
#include "types.h"
#include <string>
 
#define TAILLE_MAX_MESSAGE 65507 // TODO : Utiliser getsockopt() avec SO_PROTOCOL_INFO pour determiner la taille max d'un message
 
class ServeurUDP
{
    private:
        SOCKADDR_IN adresse_udp;
        SOCKET socketUdp;
 
    public:
        ServeurUDP(uint16 portEcoute);
        std::string recevoir(std::string* ipExpediteur, uint16* portExpediteur);
        void envoyer(std::string message, std::string ipDestinataire, uint16 portDestinataire);
};
 
#endif // SERVEURUDP_H_INCLUDED
serveurUDP.cpp
#include "serveurUDP.h"
#include <stdio.h>
 
ServeurUDP::ServeurUDP(uint16 portEcoute)
{
    static bool8 reseauInitialise = FALSE;
 
    // Si le reseau n'a pas ete initialise
    if (FALSE == reseauInitialise)
    {
        // Initialisation du reseau
        WSADATA WSAData;
        WSAStartup(MAKEWORD(2,0), &WSAData);
 
        // Le reseau a ete initialise
        reseauInitialise = TRUE;
    }
 
    printf("Création du ServeurUDP...\n");
 
    // Socket
    this->socketUdp = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
 
    // Socket non-bloquant
    uint32 arg = 1;
    ioctlsocket (this->socketUdp, FIONBIO, &arg);
    // Linux : fcntl (this->socket_udp, F_SETFL, fcntl (this->socket_udp, F_GETFL) | O_NONBLOCK);
 
 
    // Adresse
    this->adresse_udp.sin_family = AF_INET;
    this->adresse_udp.sin_port = htons (portEcoute);
    this->adresse_udp.sin_addr.s_addr = htonl(INADDR_ANY);
    memset(&this->adresse_udp.sin_zero, 0, 8);
 
    // Ligature
    if (-1 == bind(this->socketUdp, (SOCKADDR*)&this->adresse_udp, sizeof(this->adresse_udp)))
    {
        printf("Erreur lors de la création du ServeurUDP\n");
        return;
    }
 
    printf("ServeurUDP créé avec succès\n");
}
 
std::string ServeurUDP::recevoir(std::string* ipExpediteur, uint16* portExpediteur)
{
    SOCKADDR_IN adresseExpediteur;
    int tailleAdresseExpediteur = 0;
    char message[TAILLE_MAX_MESSAGE] = {0};
 
 
    // Reception d'un eventuel message
    tailleAdresseExpediteur = sizeof(adresseExpediteur);
    if (0 < recvfrom(this->socketUdp, message, TAILLE_MAX_MESSAGE, 0, (struct sockaddr*)&adresseExpediteur, &tailleAdresseExpediteur))
    {
        *portExpediteur = ntohs(adresseExpediteur.sin_port);
        *ipExpediteur = inet_ntoa(adresseExpediteur.sin_addr);
 
        return message;
    }
 
    return "";
}
 
void ServeurUDP::envoyer(std::string message, std::string ipDestinataire, uint16 portDestinataire)
{
    // Adresse du destinataire
    SOCKADDR_IN adresseDestinataire;
    adresseDestinataire.sin_family = AF_INET;
    adresseDestinataire.sin_port = htons(portDestinataire);
    memset(&adresseDestinataire.sin_zero, 0, 8);
 
    hostent* infosDestinataire = gethostbyname(ipDestinataire.c_str());
    adresseDestinataire.sin_addr.s_addr = ((in_addr*)infosDestinataire->h_addr)->s_addr;
 
    if (0 == adresseDestinataire.sin_addr.s_addr)
    {
        adresseDestinataire.sin_addr.s_addr = inet_addr(ipDestinataire.c_str());
    }
 
    // Envoi
    if(-1 == sendto(this->socketUdp, message.c_str(), message.length() + 1, 0, (sockaddr*)&adresseDestinataire, sizeof(adresseDestinataire)))
    {
        printf("Erreur lors de l'envoi du message\n");
    }
}

Classe ClientUDP

clientUDP.h
#ifndef CLIENTUDP_H_INCLUDED
#define CLIENTUDP_H_INCLUDED
 
#include <winsock2.h>
#include "types.h"
#include <string>
 
#define TAILLE_MAX_MESSAGE 65507
 
class ClientUDP
{
    private:
        SOCKADDR_IN adresseServeur;
        SOCKET socketUdp;
 
    public:
        ClientUDP();
        void connect(std::string ipServeur, uint16 portServeur);
        std::string recevoir();
        void envoyer(std::string message);
};
 
#endif // CLIENTUDP_H_INCLUDED
clientUDP.cpp
#include "clientUDP.h"
 
 
ClientUDP::ClientUDP()
{
    static bool8 reseauInitialise = FALSE;
 
    // Si le reseau n'a pas ete initialise
    if (FALSE == reseauInitialise)
    {
        // Initialisation du reseau
        WSADATA WSAData;
        WSAStartup(MAKEWORD(2,0), &WSAData);
 
        // Le reseau a ete initialise
        reseauInitialise = TRUE;
    }
 
    printf("Création du ClientUDP...\n");
 
    // Socket
    this->socketUdp = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
 
    // Socket non-bloquant
    uint32 arg = 1;
    ioctlsocket (this->socketUdp, FIONBIO, &arg);
    // Linux : fcntl (this->socket_udp, F_SETFL, fcntl (this->socket_udp, F_GETFL) | O_NONBLOCK);
 
    printf("ClientUDP créé avec succès\n");
}
 
void ClientUDP::connect(std::string ipServeur, uint16 portServeur)
{
    // Adresse
    this->adresseServeur.sin_family = AF_INET;
    this->adresseServeur.sin_port = htons (portServeur);
    memset(&this->adresseServeur.sin_zero, 0, 8);
 
    hostent* infosServeur = gethostbyname(ipServeur.c_str());
    if(NULL != infosServeur)
    {
        this->adresseServeur.sin_addr.s_addr = ((in_addr*)infosServeur->h_addr)->s_addr;
        if (0 == this->adresseServeur.sin_addr.s_addr)
        {
            this->adresseServeur.sin_addr.s_addr = inet_addr(ipServeur.c_str());
        }
    }
    else
    {
        // Erreur dans la resolution du nom de serveur
        this->adresseServeur.sin_addr.s_addr = 0;
    }
}
 
std::string ClientUDP::recevoir()
{
    SOCKADDR_IN adresseExpediteur;
    int tailleAdresseExpediteur = 0;
    char message[TAILLE_MAX_MESSAGE] = {0};
 
    // Reception d'un eventuel message
    tailleAdresseExpediteur = sizeof(adresseExpediteur);
    if (0 < recvfrom(this->socketUdp, message, TAILLE_MAX_MESSAGE, 0, (struct sockaddr*)&adresseExpediteur, &tailleAdresseExpediteur))
    {
        return message;
    }
 
    return "";
}
 
void ClientUDP::envoyer(std::string message)
{
    // Si on est connecte
    if(0 != this->adresseServeur.sin_addr.s_addr)
    {
        // Envoi
        if(-1 == sendto(this->socketUdp, message.c_str(), message.length() + 1, 0, (sockaddr*)&this->adresseServeur, sizeof(this->adresseServeur)))
        {
            printf("Erreur lors de l'envoi du message\n");
        }
    }
}

Installation

Pour fonctionner, les classes ServeurUDP et ClientUDP ont besoin de l'entête types.h. pour l'installer référez-vous à cette page. Cette opération devra être effectuée deux fois : une fois pour le serveur, et une fois pour le client.

Le cas d'un serveur

Si votre application est un serveur, ajoutez les fichiers serveurUDP.h et serveurUDP.cpp à votre projet, puis ajoutez -lws2_32 à l'éditeur de liens.

Le cas d'un client

Si votre application est un client, ajoutez les fichiers clientUDP.h et clientUDP.cpp à votre projet, puis ajoutez -lws2_32 à l'éditeur de liens.

Utilisation de ServeurUDP

La classe ServeurUDP ne comporte qu'un constructeur et 2 méthodes publiques : envoyer(), recevoir(). Ces méthodes ne sont pas bloquante.

Le constructeur

ServeurUDP::ServeurUDP(uint16 portEcoute);

Le constructeur prend en paramètre le numéro de port sur lequel il se met à l'écoute (ex : 2712).

recevoir()

std::string ServeurUDP::recevoir(std::string* ipExpediteur, uint16* portExpediteur);

Cette méthode permet de récupérer un éventuel message reçu. S'il y a plusieurs messages reçus, la méthode retourne le premier message reçu et laisse les autres en attente. Il conviendra alors de faire plusieurs appels successifs pour les récupérer tous, un à un. Si aucun message n'est reçu, la méthode retourne une chaine vide. La méthode prend en paramètre deux pointeurs sur un string et un uint16, dans lesquels seront mémorisés l'IP (ex : "127.0.0.1") et le port (ex : 1234) de l’expéditeur du message, si un message est reçu.

envoyer()

void ServeurUDP::envoyer(std::string message, std::string ipDestinataire, uint16 portDestinataire);

Cette méthode effectue l'envoi d'un message au destinataire indiqué en paramètre. Le premier paramètre indique le message à envoyer (ex : "SALUT"), les suivants indiquent l'IP (ex : "127.0.0.1") et le port (ex : 1234) du destinataire. Dans le cas d'une réponse, il s'agit de recopier l'IP et le port de l'expéditeur dans les champs du destinataire.

Exemple de serveur UDP

Voici un petit serveur UDP mettant en œuvre les 2 méthodes. Celui-ci récupère en boucle les éventuels messages à intervalle régulier. Lorsqu'un message est reçu, il l'affiche. Si le message "BONJOUR" est reçu, il y répond et sort de la boucle.

main.cpp
#include <iostream>
#include "serveurUDP.h"
 
int main()
{
    // Creation d'une instance de ServeurUDP
    // a l'ecoute sur le port 2712
    ServeurUDP serveurUDP(2712);
 
    // En boucle
    for(;;)
    {
        // Creation des objets permettant de memoriser
        // un eventuel message ainsi que son expediteur
        std::string ipExpediteur = "";
        uint16 portExpediteur = 0;
        std::string messageRecu = "";
 
        // Reception d'un eventuel message, memorisation de l'expediteur
        messageRecu = serveurUDP.recevoir(&ipExpediteur, &portExpediteur);
 
        // Si un message est recu
        if ("" != messageRecu)
        {
            // Affichage du message
            std::cout << "Message recu : " << messageRecu << std::endl;
 
            // Si ce message est "BONJOUR"
            if ("BONJOUR" == messageRecu)
            {
                // Reponse a l'adresse de l'expediteur memorise precedement
                serveurUDP.envoyer("SALUT", ipExpediteur, portExpediteur);
 
                // Quitte la boucle
                break;
            }
        }
        else
        {
            std::cout << "Aucun message recu" << std::endl;
 
            Sleep(1000);
        }
    }
 
    return 0;
}

Utilisation de ClientUDP

La classe ClientUDP ne comporte que 3 méthodes publiques : connect(), envoyer(), recevoir(). Ces méthodes ne sont pas bloquante.

connect()

void ClientUDP::connect(std::string ipServeur, uint16 portServeur);

Cette méthode permet d'indiquer à quel destinataire devront être adressé les prochains messages. Elle doit être appelée avant d'envoyer le premier message. Le premier paramètre indique l'adresse du destinataire, il peut s’agir d'une adresse IP (ex : "78.242.80.100") ou d'un nom de domaine (ex : "linor.fr"). Le second paramètre indique le numéro de port du destinataire (ex : 2712).

envoyer()

void ClientUDP::envoyer(std::string message);

Cette méthode effectue l'envoi d'un message au destinataire indiqué précédemment. Elle prend en paramètre le message à envoyer (ex : "BONJOUR").

recevoir()

std::string ClientUDP::recevoir();

Cette méthode permet de récupérer un éventuel message reçu. S'il y a plusieurs messages reçus, la méthode retourne le premier message reçu et laisse les autres en attente. Il conviendra alors de faire plusieurs appels successifs pour les récupérer tous, un à un. Si aucun message n'est reçu, la méthode retourne une chaine vide.

Exemple de client UDP

Voici un petit client UDP mettant en œuvre les 3 méthodes. Celui-ci envoi le message "BONJOUR" à l'adresse 127.0.0.1:2712, attend une seconde, puis vérifie si un message est arrivé pour l'afficher.

main.cpp
#include <iostream>
#include "clientUDP.h"
 
int main()
{
    // Creation d'une instance de ClientUDP
    // destine a envoyer a 127.0.0.1 sur le port 2712
    ClientUDP clientUDP;
    clientUDP.connect("127.0.0.1", 2712);
 
    // Envoi du message "BONJOUR" au destinataire
    clientUDP.envoyer("BONJOUR");
 
    // Attend une seconde
    Sleep(1000);
 
    // Lecture d'un eventuel message
    std::string messageRecu = clientUDP.recevoir();
 
    // Si un message est recu
    if ("" != messageRecu)
    {
        // Affichage du message recu
        std::cout << "Message recu : " << messageRecu << std::endl;
    }
    else
    {
        std::cout << "Aucun message recu" << std::endl;
    }
 
    return 0;
}

Exemple de réalisation : répéteur de message

Le client envoi des messages au serveur qui les réémet au client. Chacun affiche les messages qu'il reçois.

Code du client Code du serveur
Le client commence par demander l'adresse du serveur. Ensuite, l'utilisateur tape son message et appuis sur Entree pour l'envoyer. Lorsqu'un message est reçu, il s'affiche. Toutes les 3 secondes, le serveur vérifie ses messages. Lorsqu'il reçois des messages, il les affiche et les réémet au client.
main.cpp
#include <iostream>
#include "clientUDP.h"
#include <conio.h>
 
int main()
{
    ClientUDP clientUDP;
    std::string ipServeur = "";
    std::string messageRecu = "";
    std::string messageAEnvoyer = "";
 
    // Demande l'adresse du serveur
    std::cout << "Adresse du serveur : ";
    std::cin >> ipServeur;
 
    // Les prochains messages seront envoyes au serveur
    clientUDP.connect(ipServeur, 2712);
 
    // Demande du message a envoyer
    std::cout << "Message a envoyer : ";
 
    for(;;)
    {
        // Si une touche est enfoncee
        if (_kbhit())
        {
            // Lecture de la touche
            int touche = 0;
            touche = _getch();
 
            // Si c'est un caractere imprimable
            if (32 <= touche)
            {
                // Affichage du caractere
                std::cout << (char)touche;
 
                // Ajout du caractere au message en envoyer
                messageAEnvoyer += touche;
            }
 
            // Si c'est la touche entree
            else if (0x0d == touche)
            {
                // Retour chariot, saut de ligne
                std::cout << (char)0x0d << (char) 0x0a;
 
                // Envoi du message
                clientUDP.envoyer(messageAEnvoyer);
                messageAEnvoyer = "";
 
                // Demande du prochain message a envoyer
                std::cout << "Message a envoyer : ";
            }
        }
 
        // Si un message est recu
        messageRecu = clientUDP.recevoir();
        if ("" != messageRecu)
        {
            // Efface la ligne
            for(uint8 i = 0; i < 79; i++) std::cout << "\b";
            for(uint8 i = 0; i < 79; i++) std::cout << " ";
            for(uint8 i = 0; i < 79; i++) std::cout << "\b";
 
            // Affichage du message recu
            std::cout << "Message recu : "
                      << messageRecu << std::endl;
 
            // Reaffichage du message en cours d'ecriture
            std::cout << "Message a envoyer : "
                      << messageAEnvoyer;
        }
        else
        {
            Sleep(10);
        }
    }
 
    return 0;
}
main.cpp
#include <iostream>
#include "serveurUDP.h"
 
int main()
{
    // Creation d'une instance de ServeurUDP
    // a l'ecoute sur le port 2712
    ServeurUDP serveurUDP(2712);
 
    // En boucle
    for(;;)
    {
        // Creation des objets permettant de memoriser
        // un eventuel message ainsi que son expediteur
        std::string ipExpediteur = "";
        uint16 portExpediteur = 0;
        std::string messageRecu = "";
 
        // Reception d'un eventuel message, memorisation de l'expediteur
        messageRecu = serveurUDP.recevoir(&ipExpediteur, &portExpediteur);
 
        // Si un message est recu
        if ("" != messageRecu)
        {
            // Affichage du message
            std::cout << "Message recu : " << messageRecu << std::endl;
 
            // Retourne le message a l'expediteur
            serveurUDP.envoyer(messageRecu, ipExpediteur, portExpediteur);
        }
        else
        {
            // 3 secondes
            Sleep(3000);
        }
    }
 
    return 0;
}
Exécution du client Exécution du serveur
Creation du ClientUDP...
ClientUDP cree avec succes
Adresse du serveur : localhost
Message a envoyer : Bonjour serveur !
Message recu : Bonjour serveur !
Message a envoyer : abcd
Message recu : abcd
Message a envoyer : 1
Message a envoyer : 2
Message recu : 1
Message recu : 2
Message a envoyer : 3
Message a envoyer : 4
Message a envoyer : 5
Message a envoyer : 6
Message a envoyer : 7
Message a envoyer : 8
Message recu : 3
Message recu : 4
Message recu : 5
Message recu : 6
Message recu : 7
Message recu : 8
Message a envoyer : 9
Message a envoyer : 10
Message recu : 9
Message recu : 10
Message a envoyer : _
Creation du ServeurUDP...
ServeurUDP cree avec succes
Message recu : Bonjour serveur !
Message recu : abcd
Message recu : 1234
Message recu : 1
Message recu : 2
Message recu : 3
Message recu : 4
Message recu : 5
Message recu : 6
Message recu : 7
Message recu : 8
Message recu : 9
Message recu : 10
_

Exemple de réalisation : mesureur de message

Dans le premier exemple, le serveur ne fait que répéter les messages reçus. En le modifiant un peu, il mesure la taille des messages qu'il recois.

main.cpp
#include <iostream>
#include "serveurUDP.h"
#include <sstream>
 
int main()
{
    // Creation d'une instance de ServeurUDP
    // a l'ecoute sur le port 2712
    ServeurUDP serveurUDP(2712);
 
    // En boucle
    for(;;)
    {
        // Creation des objets permettant de memoriser
        // un eventuel message ainsi que son expediteur
        std::string ipExpediteur = "";
        uint16 portExpediteur = 0;
        std::string messageRecu = "";
 
        // Reception d'un eventuel message, memorisation de l'expediteur
        messageRecu = serveurUDP.recevoir(&ipExpediteur, &portExpediteur);
 
        // Si un message est recu
        if ("" != messageRecu)
        {
            // Affichage du message
            std::cout << "Message recu : " << messageRecu << std::endl;
 
            // Retourne la taille du message a l'expediteur
            std::stringstream flux;
            flux << messageRecu.length();
            serveurUDP.envoyer(flux.str(), ipExpediteur, portExpediteur);
        }
        else
        {
            Sleep(10);
        }
    }
 
    return 0;
}
Creation du ClientUDP...
ClientUDP cree avec succes
Adresse du serveur : localhost
Message a envoyer : Bonjour serveur !
Message recu : 17
Message a envoyer : Quelle est la taille de ce message ?
Message recu : 36
Message a envoyer : Et celui-ci ?
Message recu : 13
Message a envoyer : _