IdentifiantMot de passe
Loading...
Mot de passe oubli� ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les r�ponses en temps r�el, voter pour les messages, poser vos propres questions et recevoir la newsletter

C++ Discussion :

Framework de paquets r�seau en C++ moderne


Sujet :

C++

  1. #1
    Membre chevronn�

    Homme Profil pro
    D�veloppeur informatique
    Inscrit en
    Juin 2007
    Messages
    373
    D�tails du profil
    Informations personnelles :
    Sexe : Homme
    �ge : 36
    Localisation : Royaume-Uni

    Informations professionnelles :
    Activit� : D�veloppeur informatique
    Secteur : Sant�

    Informations forums :
    Inscription : Juin 2007
    Messages : 373
    Par d�faut Framework de paquets r�seau en C++ moderne
    Bonjour,

    Je travaille sur un petit projet de jeu sur mon temps libre, et je me casse un peu les dents sur l'architecture du code au niveau du passage d'information entre les clients et le serveur. Je me repose sur la SFML pour la gestion bas niveau du r�seau. Si vous ne connaissez pas, l'essentiel est que je dispose d'une classe sf::Packet qui peut s�rialiser/d�-s�rialiser n'importe quel type de POD pour les acheminer sur le r�seau. Un exemple simplifi� :
    Code : S�lectionner tout - Visualiser dans une fen�tre � part
    1
    2
    3
    4
    5
    6
    // Alice
    int i = 5;
    double d = 3.14;
    sf::Packet p;
    p << i << d;
    socket.send(p);
    Code : S�lectionner tout - Visualiser dans une fen�tre � part
    1
    2
    3
    4
    5
    6
    // Bob
    int i;
    double d;
    sf::Packet p;
    socket.receive(p);
    p >> i >> d;
    Le principal probl�me � ce stade est de s'assurer que Alice remplisse correctement le paquet avant de l'envoyer, et que Bob essaye seulement d'en extraire ce qu'il contient. J'ai donc �crit un framework qui automatise le tout.

    Dans ce cadre, Alice peut :
    • envoyer un message informatif � Bob, sans rien attendre en retour (send_message())
    • envoyer une requ�te � Bob, et enregistrer un callback � ex�cuter quand la r�ponse de Bob arrivera (send_request())

    Et Bob peut :
    • enregistrer un callback a ex�cuter quand il re�oit un message particulier (watch_message())
    • enregister un callback a ex�cuter quand il re�oit une requ�te (le callback a alors la responsabilit� d'�mettre une r�ponse) (watch_request())

    Bien s�r, Alice peut aussi faire ce que Bob fait, et r�ciproquement...

    Pour l'automatisation, je dois cr�er une classe pour chaque type de message/requ�te. Elle porte en elle :
    • l'identifiant unique correspondant � ce type de paquet (il sert � trouver quel callback appeler une fois le paquet re�u)
    • la liste des types des objets qui sont envoy�s dans le paquet


    Un exemple :
    Code : S�lectionner tout - Visualiser dans une fen�tre � part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    template<typename ... Args>
    struct type_list {};
     
    struct client_connected {
        // L'ID doit être unique dans tout le programme et constant
        // d'une unité de compilation à une autre (et bien sûr d'une
        // exécution à une autre)
        static const std::uint16_t id = 486; 
     
        using types = type_list<
            std::size_t, // id du client
            std::string  // nom du client
        >;
    };
    Du coup, quand Alice appelle send_message<client_connected>(12, "toto"), le framework v�rifie � la compilation que les arguments correspondent � ceux de la liste client_connected::types, et les envoie. De son c�t�, Bob peut d�cider d'effectuer une action � la r�ception de ce message. Il va alors appeler watch_message<client_connected>([](std::size_t id, std::string name) { ... }); et, l� aussi, le framework v�rifie � la compilation que les arguments de la fonction sont compatibles avec le contenu attendu du paquet. Le reste est fait sous le capot (r�ception du paquet, dispatch vers le bon callback, d�-s�rialisation des �l�ments, etc.).

    Je suis assez content du r�sultat, mais il reste une ombre au tableau : attribuer un ID unique � chaque type de message/requ�te. Je peux bien s�r faire �a � la main (commencer � 0, puis donner 1 au second message que je vais cr�er, etc.), mais �a pose des probl�mes de maintenance �vidents : si je me trompe et donne le m�me ID � deux types de paquets diff�rents je n'ai aucun m�canisme pour le d�tecter. Si je d�cide de supprimer un paquet, alors son ID sera inutilis� (il faudra que je pense � le redonner � un futur nouveau paquet). Bref je voudrai �viter �a.

    Une des solutions temporaire que j'ai trouv� consiste � d�clarer tous les messages dans un m�me header, et d'utiliser la macro __LINE__ pour g�n�rer un ID unique. �a pose aussi son lot de probl�me : les valeurs retourn�es par __LINE__ sont certes uniques, mais elles ne sont pas tr�s optimales (il y a plein de trous : on passe de 26 � 42 par exemple), et surtout �a me force � tout d�clarer au m�me endroit, ce qui est tr�s moche en terme de localit� du code (au niveau conceptuel et temps de compilation). Par exemple avec client_connected je pr�f�re que le type de l'ID du client soit un typedef, using client_id_t = std::size_t, qui soit utilis� dans le reste du code. Mais du coup je dois le d�clarer dans ce header, et tout le monde va en profiter sans forc�ment en avoir besoin...

    Une autre option serait de d�clarer dans un header commun une grosse �num�ration avec tous les messages possibles :
    Code : S�lectionner tout - Visualiser dans une fen�tre � part
    1
    2
    3
    4
    5
    enum message_id_t : std::uint16_t {
        client_connected_id,
        client_disconnected_id,
        ...
    };
    Puis d'�crire :
    Code : S�lectionner tout - Visualiser dans une fen�tre � part
    1
    2
    3
    4
    struct client_connected {
        static const std::uint16_t id = client_connected_id; 
        using types = type_list<std::size_t, std::string>;
    };
    Mais �a veut dire que je dois "d�clarer" le paquet � deux endroits diff�rents. Le code se r�p�te un peu, et je peux me planter dans l'affectation de l'ID (si je copie/colle le code d'un autre paquet et oublie de changer l'ID par exemple). �a ne me pla�t pas beaucoup non plus.

    Ma seule solution est-elle de recourir � un outil externe qui va g�n�rer ces ID � ma place � la compilation, en lisant mon code source ? Si oui, connaissez-vous de bons outils portables (et si possible l�gers) qui permettent de faire �a proprement ?

  2. #2
    Expert �minent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    D�tails du profil
    Informations personnelles :
    Sexe : Homme
    �ge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activit� : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par d�faut
    Salut,

    Pourquoi ne pas simplement utiliser une fonction de hashage (std::hash, en C++11 )

    Tu r�cup�re un entier (64 bits, il me semble) non sign� avec les certitudes:
    • qu'il sera unique, � moins de chercher la collision (en fait, tu risques d'avoir des collisions, mais, bon, le risque n'est pas tr�s grand )
    • qu'il sera constant : une valeur identique fournira forc�ment une cl� de hashage identique


    Tu n'es m�me pas forc�ment oblig� de prendre du tr�s costaud, un simple CRC 32 pourrait tou aussi bien faire l'affaire si tu gardes un nombre de messages plus ou moins limit�
    A m�diter: La solution la plus simple est toujours la moins compliqu�e
    Ce qui se con�oit bien s'�nonce clairement, et les mots pour le dire vous viennent ais�ment. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 f�vrier 2014
    mon tout nouveau blog

  3. #3
    Membre chevronn�

    Homme Profil pro
    D�veloppeur informatique
    Inscrit en
    Juin 2007
    Messages
    373
    D�tails du profil
    Informations personnelles :
    Sexe : Homme
    �ge : 36
    Localisation : Royaume-Uni

    Informations professionnelles :
    Activit� : D�veloppeur informatique
    Secteur : Sant�

    Informations forums :
    Inscription : Juin 2007
    Messages : 373
    Par d�faut
    Et je lui donne le nom du paquet � hasher ?
    Code : S�lectionner tout - Visualiser dans une fen�tre � part
    static const std::size_t id = std::hash<std::string>()("client_connected");
    Pas mal comme id�e, merci pour ta r�ponse, mais j'y vois deux probl�mes majeurs :
    • les impl�mentations de std::hash ne sont pas standardis�es : une compilation avec gcc ne donnera peut �tre pas le m�me r�sultat qu'une compilation VC++ (emb�tant si le serveur est compil� avec gcc, et qu'un gars utilise un client qu'il a construit lui m�me avec VC++ par exemple).
    • std::size_t, qui est le type de retour de std::hash::operator(), n'est pas un type fixe, donc si je cr�� un ID sur le serveur en 64 bit, je ne sais pas ce que �a donnera pour un client 32 bit... En tout cas je ne peux pas le faire transiter sur le r�seau facilement.

  4. #4
    Expert �minent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    D�tails du profil
    Informations personnelles :
    Sexe : Homme
    �ge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activit� : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par d�faut
    C'est pour cela que j'ai parl� de crc_32

    L'algorithme est simple et tu a la certitude d'avoir d'office toujours les m�mes valeurs

    Comme je pr�sumes que tu n'auras, de toutes mani�res, pas des centaines de milliers de messages, tout ce qu'il te faut, c'est le moyen d'automatiser la g�n�ration d'un identifiant unique qui puisse �tre ind�pendant de la machine afin que chaque partie en pr�sence puisse savoir exactement ce que l'autre essaye de lui dire

    Il n'est pas n�cessaire de sortir l'artillerie lourde pour (en consid�rant MD-5 ou ShaXXX comme tel ) pour y arriver, car le but ici n'est absolument pas de s�curiser l'information (�a, �a se fait au niveau du paquet, et non au niveau du contenu qu'on y mettra ) mais juste d'�tre sur que si tu envoies le message "bonjour", celui qui le recevra ne risque pas de l'interpr�ter comme "tu me fais ch..."

    Je n'ai cit� std::hash que pour l'exemple, parce que j'avais remarqu� que tu profites d�j� des fonctionnalit�s de C++11
    A m�diter: La solution la plus simple est toujours la moins compliqu�e
    Ce qui se con�oit bien s'�nonce clairement, et les mots pour le dire vous viennent ais�ment. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 f�vrier 2014
    mon tout nouveau blog

  5. #5
    Membre chevronn�

    Homme Profil pro
    D�veloppeur informatique
    Inscrit en
    Juin 2007
    Messages
    373
    D�tails du profil
    Informations personnelles :
    Sexe : Homme
    �ge : 36
    Localisation : Royaume-Uni

    Informations professionnelles :
    Activit� : D�veloppeur informatique
    Secteur : Sant�

    Informations forums :
    Inscription : Juin 2007
    Messages : 373
    Par d�faut
    Pardon, j'�tais pass� � c�t�... Tu as raison, �a r�gle les deux probl�mes restants.

    Voil� ce que j'ai fini par �crire :
    Code : S�lectionner tout - Visualiser dans une fen�tre � part
    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
    // Calcule récursivement le CRC32 d'une chaîne de caractère.
    // J'ai été obligé d'implémenter ça de manière récursive pour
    // pouvoir la déclarer constexpr. Du coup c'est un peu laid...
    constexpr std::uint32_t recursive_crc32(std::uint32_t id, const char* str, std::size_t i,
        std::size_t n, std::size_t j, std::uint32_t poly) {
        return j == 8 ?
            (i == n ? id : recursive_crc32(id, str, i+1, n, 0, poly)) :
            recursive_crc32(
                ((id >> 31) ^ ((str[i] >> j) & 1)) == 1 ?
                    ((id << 1) ^ poly) : id << 1,
                str, i, n, j+1, poly
            );
    }
     
    // User defined litteral pour avoir la taille de la chaîne facilement
    constexpr std::uint32_t operator "" _crc32(const char* str, std::size_t length) {
        return recursive_crc32(0, str, 0, length, 0, 0x4C11DB7);
    }
     
    // Class utilitaire template qui permet d'éviter des problèmes de
    // linkage pour l'ID constant
    namespace crc32_impl {
        template<std::uint32_t ID>
        struct base {
            static const std::uint32_t id = ID;
        };
     
        template<std::uint32_t ID>
        const std::uint32_t base<ID>::id;
    }
     
    // La macro magique
    #define ID_STRUCT(name) struct name : public crc32_impl::base<#name ## _crc32>
    D�finir un nouveau message revient simplement � :
    Code : S�lectionner tout - Visualiser dans une fen�tre � part
    1
    2
    3
    ID_STRUCT(client_connected) {
        using types = type_list<std::size_t, std::string>;
    };
    Et rien d'autre. C'est beau, j'aime beaucoup ! Merci Koala
    Bon, il reste le probl�me des collisions. La probabilit� devrait �tre assez faible, mais �a serait bon de v�rifier. � la limite je peux aussi �crire un petit programme qui va chercher la liste de tous les messages dans le code, et qui v�rifie qu'il n'y a pas de collision.

  6. #6
    Expert �minent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    D�tails du profil
    Informations personnelles :
    Sexe : Homme
    �ge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activit� : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par d�faut
    J'ai trouv� ce lien sur stak-overflow: http://preshing.com/20110504/hash-co...probabilities/

    Si m�me tu atteins les 927 messages, la probabilit� d'avoir une collision est de 1/10000 avec un algorithme de g�n�ration sur 32 bits.

    Le risque est l� et bien l�, mais ca laisse de la marge
    A m�diter: La solution la plus simple est toujours la moins compliqu�e
    Ce qui se con�oit bien s'�nonce clairement, et les mots pour le dire vous viennent ais�ment. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 f�vrier 2014
    mon tout nouveau blog

+ R�pondre � la discussion
Cette discussion est r�solue.

Discussions similaires

  1. R�ponses: 2
    Dernier message: 02/06/2012, 19h33
  2. Structure des paquets r�seau
    Par keyga dans le forum R�seau et multijoueurs
    R�ponses: 15
    Dernier message: 10/05/2012, 09h32
  3. SQL Server, taille du paquet r�seau
    Par Philippe Robert dans le forum MS SQL Server
    R�ponses: 1
    Dernier message: 19/07/2011, 18h35
  4. choix framework - dessin graphe r�seau
    Par manik971 dans le forum Frameworks Web
    R�ponses: 0
    Dernier message: 27/04/2010, 15h48
  5. [SSL] Passerelle de paquets r�seau
    Par baptx dans le forum R�seau
    R�ponses: 0
    Dernier message: 21/01/2009, 14h57

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo