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

Boost C++ Discussion :

[Boost::Asio] Plusieurs questions


Sujet :

Boost C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre confirmé
    Homme Profil pro
    Développeur de jeux vidéo
    Inscrit en
    Août 2011
    Messages
    88
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur de jeux vidéo

    Informations forums :
    Inscription : Août 2011
    Messages : 88
    Par défaut [Boost::Asio] Plusieurs questions
    Bonjour à tous,

    Voilà, j'ai quelques questions à poser, travaillant actuellement sur une architecture client/serveur pour un MMORPG, j'utilise tout naturellement boost::asio (je me penche actuellement sur un TaskManager ou ThreadPoolManager).
    Même si vous ne connaissez pas la réponse, vos idées/suggestions me seront utiles

    1ère question :

    Le premier schéma utilisé pour gérer tous ces utilisateurs était le suivant :
    chaque session utilisateur est en fait un thread, qui est lancé par la ThreaPool à la connexion de l'utilisateur. Chaque session écoute donc en boucle (asio::read()) sur son socket en attente de recevoir un packet.
    On reçoit dans un buffer taille 1 de multiple fois jusqu'à avoir complètement reconstitué le packet puis on l'analyse, et on éxécute la fonction associée, bref.
    Le point faible survient évidemment lorsque le nombre d'utilisateur augmente : on augmente le nombre de thread à gérer, ce qui doit être plutôt lourd au final.

    Voilà ce que j'aimerais donc faire pour être plus efficace :
    La threadpool est optimisée pour avoir un nombre de thread limité :
    Le thread de login s'occupe de la connexion (plutôt qu'une fonction async),
    Le thread de selection reçoit l'ensemble des packets venant des clients, analyse et place chaque tâche dans une queue
    Les threads "workers" vont sans arrêt prendre les tâches venant de la queue et les exécuter

    Et c'est là que je bloque :
    J'ai d'abord pensé stocker toutes les sockets clientes dans un vector (par exemple), puis boucler dessus et faire un certain nombre de tour de boucle puis passer au socket suivant, etc ...
    Cependant malgré la vitesse d'exécution du principe, il y aura un temps d'attente en cas de nombre élevé de client. Procédé à exclure.

    Ensuite, j'ai pensé effectivement continuer de boucler sur les sockets, mais en lançant une opération de réception async pour chacun d'entre eux.
    Une fois la réception terminée, le callback analyse le packet et place la tâche aux bon soins des workers.
    Mais question : est-ce que ça ne reviendrait pas à faire un équivalent du premier schéma que j'ai évoqué plus haut ? Avec 1 000 clients, on aurait 1000 réception asynchrone sans arrêt, si tenté que ceci soit réalisable
    Justement : Est-ce que je me plante complètement et qu'une meilleure technique existe pour gérer de multiples clients en même temps ?

    2ème question :

    Une IP PC est-elle stable (est-elle sujète à changement autrement dit) ?
    Peut-on récupérer l'IP PC avec boost::asio ? (Pour un système de banIP efficace ...)

    3ème question :

    Comment implémenter un programmateur ? C'est à dire qu'il me faudrait un thread à part pour les tâches qui ne doivent être exécutée qu'à un certain horaire, ou tout les x temps ...

    J'ai pensé à un tableau <pair <"délai_avant_exécution", tâche> > au lieu d'une queue. Les workers regardent si une tâche peut-êre exécutée, regarde son timer, si = 0 exécute sinon on parcourt encore le tableau pour trouver une tâche exécutable.
    Je pense que ça peut marcher, mais qu'en est-il si je veux scripter un évènement se déroulant tous les jours à 18h00 par exemple ? ça veut dire que la tâche resterait 24h dans le tableau en attente, ce qui n'est pas vraiment
    optimisé question mémoire (d'autant que si cette tâche est seule, les workers vont passer dessus sans arrêt en attendant de pouvoir l'exécuter).

    Cela dit je n'ai pas d'autre idée pour le moment. Des pistes ? (Et merci de cette lecture.)

  2. #2
    Membre confirmé
    Homme Profil pro
    Développeur de jeux vidéo
    Inscrit en
    Août 2011
    Messages
    88
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur de jeux vidéo

    Informations forums :
    Inscription : Août 2011
    Messages : 88
    Par défaut
    Je me permet de faire un UP !

    EDIT : Une question supplémentaire

    Lorsque l'on utilise boost::asio, on doit #define WIN32_WINNT 0x0500 pour Windows XP, mais qu'en est-il de Windows 7 ?

  3. #3
    Membre Expert

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2007
    Messages
    1 895
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Septembre 2007
    Messages : 1 895
    Par défaut
    Citation Envoyé par Nekkro Voir le message
    Je me permet de faire un UP !

    EDIT : Une question supplémentaire

    Lorsque l'on utilise boost::asio, on doit #define WIN32_WINNT 0x0500 pour Windows XP, mais qu'en est-il de Windows 7 ?
    http://msdn.microsoft.com/en-us/libr...(v=vs.85).aspx

    Pour une utilisation approfondie de cette définition. A noter que cette valeur spécifie la machine cible, pas la machine ou le code est compilé.
    [FAQ des forums][FAQ Développement 2D, 3D et Jeux][Si vous ne savez pas ou vous en êtes...]
    Essayez d'écrire clairement (c'est à dire avec des mots français complets). SMS est votre ennemi.
    Evitez les arguments inutiles - DirectMachin vs. OpenTruc ou G++ vs. Café. C'est dépassé tout ça.
    Et si vous êtes sages, vous aurez peut être vous aussi la chance de passer à la télé. Ou pas.

    Ce site contient un forum d'entraide gratuit. Il ne s'use que si l'on ne s'en sert pas.

  4. #4
    Membre Expert

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2007
    Messages
    1 895
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Septembre 2007
    Messages : 1 895
    Par défaut
    Je te conseille asio plutot que boost.asio. Boost.asio, c'est asio repackagé, mais elle évolue moins vite. cf. http://think-async.com/Asio/AsioAndBoostAsio.

    Citation Envoyé par Nekkro Voir le message
    1ère question :

    Le premier schéma utilisé pour gérer tous ces utilisateurs était le suivant :
    chaque session utilisateur est en fait un thread, qui est lancé par la ThreaPool à la connexion de l'utilisateur. Chaque session écoute donc en boucle (asio::read()) sur son socket en attente de recevoir un packet.
    On reçoit dans un buffer taille 1 de multiple fois jusqu'à avoir complètement reconstitué le packet puis on l'analyse, et on éxécute la fonction associée, bref.
    Le point faible survient évidemment lorsque le nombre d'utilisateur augmente : on augmente le nombre de thread à gérer, ce qui doit être plutôt lourd au final.
    Je confirme. Les temps de latence vont exploser lorsque le nombre d'utilisateur va monter.

    Citation Envoyé par Nekkro Voir le message
    Voilà ce que j'aimerais donc faire pour être plus efficace :
    La threadpool est optimisée pour avoir un nombre de thread limité :
    Le thread de login s'occupe de la connexion (plutôt qu'une fonction async),
    Le thread de selection reçoit l'ensemble des packets venant des clients, analyse et place chaque tâche dans une queue
    Les threads "workers" vont sans arrêt prendre les tâches venant de la queue et les exécuter
    C'est mieux. Mais ce n'est pas parfait.

    Citation Envoyé par Nekkro Voir le message
    Et c'est là que je bloque :
    J'ai d'abord pensé stocker toutes les sockets clientes dans un vector (par exemple), puis boucler dessus et faire un certain nombre de tour de boucle puis passer au socket suivant, etc ...
    Cependant malgré la vitesse d'exécution du principe, il y aura un temps d'attente en cas de nombre élevé de client. Procédé à exclure.
    Pourquoi une thread de sélection ? Les socket proposent un mode de pooling relativement correct (voire plus que correct sur certaines architectures) qui te permet d'attendre sur toutes les threads traitée un évènement particulier (ex: réception de données du client). Dès que cet évènement arrive, il est remonté et tu lance le traitement sur cette connexion particulière (cf. io_service::poll()).

    Citation Envoyé par Nekkro Voir le message
    Ensuite, j'ai pensé effectivement continuer de boucler sur les sockets, mais en lançant une opération de réception async pour chacun d'entre eux.
    Une fois la réception terminée, le callback analyse le packet et place la tâche aux bon soins des workers.
    Nope. Le polling fait par l'OS sera plus efficace. Une fois que l'OS détermine que des données sont dispo une socket, alors tu peux lancer une opération de lecture async (et éxécuter une autre fonction lors de la completion de la lecture async).

    Citation Envoyé par Nekkro Voir le message
    Mais question : est-ce que ça ne reviendrait pas à faire un équivalent du premier schéma que j'ai évoqué plus haut ? Avec 1 000 clients, on aurait 1000 réception asynchrone sans arrêt, si tenté que ceci soit réalisable
    C'est largement réalisable.

    Citation Envoyé par Nekkro Voir le message
    Justement : Est-ce que je me plante complètement et qu'une meilleure technique existe pour gérer de multiples clients en même temps ?
    C'est un problème d'architecture de code (et dans une certaine mesure d'architecture logicielle). Une bonne connaissance réseau est un plus. Cf. http://www.kegel.com/c10k.html (le problème des 10000 connexions).

    Tu peux trouver plusieurs articles concernant la programmation de serveurs haute performance utilisant asio. Cf. par exemple http://psiphi.co.za/psi/?p=324 (serveur UDP haute performance pour un jeu).

    Citation Envoyé par Nekkro Voir le message
    Une IP PC est-elle stable (est-elle sujète à changement autrement dit) ?
    Dans le principe, non. Rares sont les FAI à proposer des IP statiques, notamment hors du Royaume de France. Par contre, dans la plupart des cas, l'IP ne change que toutes les 24h (elle est attribuée par DHCP). Sur certains réseau, le simple fait de rebooter son modem permet d'obtenir une nouvelle adresse IP.

    Citation Envoyé par Nekkro Voir le message
    Peut-on récupérer l'IP PC avec boost::asio ? (Pour un système de banIP efficace ...)
    Oui, fort heureusement. L'IP est récupérer au moment de l'accept (dans le cas d'un serveur TCP) ou au moment de la réception des messages (dans le cas d'un serveur UDP). Au niveau de boost.asio, acceptor::accept (TCP synchrone) socket::receive_from (UDP synchrone), socket::async_receive_from() (UDP asynchrone) acceptent un parametre endpoint qui stocke cette addresse IP (sur un overload si la fonction en propose plusieurs).

    Cf. la doc de boost.asio, et notament les tutoriaux : http://www.boost.org/doc/libs/1_47_0.../tutorial.html


    Citation Envoyé par Nekkro Voir le message
    Comment implémenter un programmateur ? C'est à dire qu'il me faudrait un thread à part pour les tâches qui ne doivent être exécutée qu'à un certain horaire, ou tout les x temps ...

    J'ai pensé à un tableau <pair <"délai_avant_exécution", tâche> > au lieu d'une queue. Les workers regardent si une tâche peut-êre exécutée, regarde son timer, si = 0 exécute sinon on parcourt encore le tableau pour trouver une tâche exécutable.
    Non. std::list<std::pair<"heure d'exec prévue", tâche> >. Chaque insertion se fait en mode trié ; parcourir la liste ne te prendra pas plus que quelques microsecondes, tu peux le faire toutes les minutes (ou définir une granularité plus faible pour la spécification des heures d'exécution possible, par exemple tous les 1/4 d'heure).

    Citation Envoyé par Nekkro Voir le message
    Je pense que ça peut marcher, mais qu'en est-il si je veux scripter un évènement se déroulant tous les jours à 18h00 par exemple ? ça veut dire que la tâche resterait 24h dans le tableau en attente, ce qui n'est pas vraiment
    optimisé question mémoire (d'autant que si cette tâche est seule, les workers vont passer dessus sans arrêt en attendant de pouvoir l'exécuter).
    Premature optimisation is the root of all evil. Tu fait des suppositions sur ce que ton système va pouvoir supporter. On parle de quelques octets de mémoire, et d'une thread dédiée qui va être réveillée toutes les minutes ou plus par l'OS, et qui le reste du temps ne prendra que l'espace d'un pointeur dans une table interne à l'OS (une thread dans l'état dormant ne prends pas de temps CPU). Sur Windows, un WaitForSingleObject(eHandle,(temps d'attente en ms)) permettra d'endormir la thread pour un temps d'attente précis à 10ms prêt, ce qui est largement suffisant pour n'importe quel programmateur de tâches.

    Implémente ton système et mesure. Tu verras qu'il passe complètement inaperçu (d'autant plus si la thread est exécutée en priorité basse).
    [FAQ des forums][FAQ Développement 2D, 3D et Jeux][Si vous ne savez pas ou vous en êtes...]
    Essayez d'écrire clairement (c'est à dire avec des mots français complets). SMS est votre ennemi.
    Evitez les arguments inutiles - DirectMachin vs. OpenTruc ou G++ vs. Café. C'est dépassé tout ça.
    Et si vous êtes sages, vous aurez peut être vous aussi la chance de passer à la télé. Ou pas.

    Ce site contient un forum d'entraide gratuit. Il ne s'use que si l'on ne s'en sert pas.

  5. #5
    Membre confirmé
    Homme Profil pro
    Développeur de jeux vidéo
    Inscrit en
    Août 2011
    Messages
    88
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur de jeux vidéo

    Informations forums :
    Inscription : Août 2011
    Messages : 88
    Par défaut
    Je te remercie de ta réponse, je commençais à croire que personne ne voulait/pouvait me conseiller, malgré le nombre de lecture du sujet ...

    J'ai partiellement parcouru les liens que tu m'as fourni. Je vais rester sur un système TCP/IP parce que pour un jeu en tour par tour et non temps réel, il me paraît impensable de laisser des packets se perdre (je lance un sort sur ma cible ... et il ne se passe rien, plus qu'à passer son tour par manque de points d'action). Mieux vaut renvoyer le packet en cas de perte.

    Voilà ce que j'aimerais donc faire pour être plus efficace :
    La threadpool est optimisée pour avoir un nombre de thread limité :
    Le thread de login s'occupe de la connexion (plutôt qu'une fonction async),
    Le thread de selection reçoit l'ensemble des packets venant des clients, analyse et place chaque tâche dans une queue
    Les threads "workers" vont sans arrêt prendre les tâches venant de la queue et les exécuter
    C'est mieux. Mais ce n'est pas parfait.
    Je m'en doute bien, mais le principe est, je pense, suffisant pour ce dont j'ai besoin.

    Et c'est là que je bloque :
    J'ai d'abord pensé stocker toutes les sockets clientes dans un vector (par exemple), puis boucler dessus et faire un certain nombre de tour de boucle puis passer au socket suivant, etc ...
    Cependant malgré la vitesse d'exécution du principe, il y aura un temps d'attente en cas de nombre élevé de client. Procédé à exclure.
    Pourquoi une thread de sélection ? Les socket proposent un mode de pooling relativement correct (voire plus que correct sur certaines architectures) qui te permet d'attendre sur toutes les threads traitée un évènement particulier (ex: réception de données du client). Dès que cet évènement arrive, il est remonté et tu lance le traitement sur cette connexion particulière (cf. io_service::poll()).
    Je vais regarder un peu tout ça, mais je veux réduire au maximum le nombre de threads qui tournent, chacun d'eux ayant un rôle bien particulier :
    - 1 pour les connexions,
    - 1 selector
    - 1 executor
    - quelques workers pour l'executor

    Ensuite, j'ai pensé effectivement continuer de boucler sur les sockets, mais en lançant une opération de réception async pour chacun d'entre eux.
    Une fois la réception terminée, le callback analyse le packet et place la tâche aux bon soins des workers.
    Nope. Le polling fait par l'OS sera plus efficace. Une fois que l'OS détermine que des données sont dispo une socket, alors tu peux lancer une opération de lecture async (et éxécuter une autre fonction lors de la completion de la lecture async).
    De quelle façon effectuer ce genre d'opération ? Actuellement chaque session stocke sa propre socket. Je dois donc attendre qu'un client envoie sur sa socket, l'OS va déterminer que des données sont en attentes en je pourrais les traiter ?

    Une IP PC est-elle stable (est-elle sujète à changement autrement dit) ?
    Dans le principe, non. Rares sont les FAI à proposer des IP statiques, notamment hors du Royaume de France. Par contre, dans la plupart des cas, l'IP ne change que toutes les 24h (elle est attribuée par DHCP). Sur certains réseau, le simple fait de rebooter son modem permet d'obtenir une nouvelle adresse IP.
    Peut-on récupérer l'IP PC avec boost::asio ? (Pour un système de banIP efficace ...)
    Oui, fort heureusement. L'IP est récupérer au moment de l'accept (dans le cas d'un serveur TCP) ou au moment de la réception des messages (dans le cas d'un serveur UDP). Au niveau de boost.asio, acceptor::accept (TCP synchrone) socket::receive_from (UDP synchrone), socket::async_receive_from() (UDP asynchrone) acceptent un parametre endpoint qui stocke cette addresse IP (sur un overload si la fonction en propose plusieurs).
    Je sais que je peux trouver l'IP internet (par exemple 82.225.xxx.xxx), mais je peux aussi avoir mon IP interne (192.168.xxx.xxx) de cette manière ?

    Comment implémenter un programmateur ? C'est à dire qu'il me faudrait un thread à part pour les tâches qui ne doivent être exécutée qu'à un certain horaire, ou tout les x temps ...

    J'ai pensé à un tableau <pair <"délai_avant_exécution", tâche> > au lieu d'une queue. Les workers regardent si une tâche peut-êre exécutée, regarde son timer, si = 0 exécute sinon on parcourt encore le tableau pour trouver une tâche exécutable.
    Non. std::list<std::pair<"heure d'exec prévue", tâche> >. Chaque insertion se fait en mode trié ; parcourir la liste ne te prendra pas plus que quelques microsecondes, tu peux le faire toutes les minutes (ou définir une granularité plus faible pour la spécification des heures d'exécution possible, par exemple tous les 1/4 d'heure).
    Après réflexion, j'aurais un simple thread qui lancera des timers asynchrones, lorsque ceux-ci seront terminés, le callback placera la fonction associée dans la queue de l'executor. C'est plus simple, d'autant que je ne savais pas si le timer découlait même placé dans un tableau par exemple.

  6. #6
    Membre confirmé
    Homme Profil pro
    Développeur de jeux vidéo
    Inscrit en
    Août 2011
    Messages
    88
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur de jeux vidéo

    Informations forums :
    Inscription : Août 2011
    Messages : 88
    Par défaut
    Je bump un petit coup et je reviens à la charge !

    Y a-t-il un moyen, lorsqu'un client m'envoie un packet sans indication de taille, de faire un read avec un buffer qui s’adapterait à la taille du packet ?

    Si j'ai bien compris ce que m'a écrit Emmanuel Deloget, je pars sur le principe suivant (que je ne sais pas encore comment implémenter) :

    Chaque session reçoit des données sur son socket
    Lorsque la réception des données est terminée, les threads worker sont notifiés et s'occupent d'analyser le packet
    Après analyse, ils exécutent l'éventuelle fonction associée.

    De cette façon ma queue de tâche est disponible pour les opérations temporelles (qui s'exécutent toutes les x secondes ou dans x secondes).

    Qu'en pensez-vous ?

  7. #7
    Membre confirmé
    Homme Profil pro
    Développeur de jeux vidéo
    Inscrit en
    Août 2011
    Messages
    88
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur de jeux vidéo

    Informations forums :
    Inscription : Août 2011
    Messages : 88
    Par défaut
    J'ai donc finalement presque mis au point ma threadpool en utilisant les io_service. Il ne me reste qu'un gros détail à régler : la réception de données via les sockets.

    J'ai eu beau fouiller dans la doc de asio, j'ai pas trouvé comment savoir si des données ont été envoyées sur la socket.

    Voici comment fonctionne à peu près ma threadpool :

    Les workers tournent en boucle pour exécuter les tâches du io_stream.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    void worker_thread (boost::shared_ptr< boost::asio::io_service > io_service)
    {
        while (true)
        {
            io_service->run();
        }
    }
    J'ai un Executor qui contient tous les workers, et qui régulièrement join_all sur eux.

    Donc ce qu'il me manque maintenant c'est de quoi implémenter le schéma suivant :

    - le client/serveur envoie des données sur la socket
    - le selector détecte la réception, et post() la lecture aux workers
    - les workers read() puis analysent le packet, et post() la tâche associée au packet
    - les workers effectuent la tâche (avec transfert du socket pour identifier le destinataire de la tâche)

    Quelqu'un saurait-il donc comment je pourrais connaitre si le socket est rempli avec des données ?

Discussions similaires

  1. Question [Boost Asio]
    Par Linunix dans le forum Boost
    Réponses: 6
    Dernier message: 08/07/2013, 11h05
  2. Plusieurs questions sur Samba sous Ubuntu
    Par Niktou dans le forum Réseau
    Réponses: 12
    Dernier message: 12/02/2006, 15h45
  3. [Py2exe] Plusieurs questions...
    Par Arthur17 dans le forum Py2exe
    Réponses: 4
    Dernier message: 17/11/2005, 22h41
  4. J'ai plusieur question pr windows 2000
    Par Guixx dans le forum Windows Serveur
    Réponses: 1
    Dernier message: 10/09/2005, 18h23
  5. Réponses: 4
    Dernier message: 11/09/2004, 16h38

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