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

Qt Discussion :

Conception pour communication entre GUI et serveur


Sujet :

Qt

  1. #1
    Membre à l'essai
    Profil pro
    Inscrit en
    Juin 2012
    Messages
    25
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2012
    Messages : 25
    Points : 24
    Points
    24
    Par défaut Conception pour communication entre GUI et serveur
    Bonjour,

    Je suis un train de programmer une application Qt munie d'une interface graphique et d'un serveur TCP. Le serveur TCP envoie des données saisies pas l'utilisateur via l'interface graphique, et met à jour l'interface graphique en fonction des données reçues.
    Ma question porte sur la relation qu'il existe entre les deux classes:
    Pour moi il s'agit d'une association, j'ai dans MainWindow un attribut
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    serveurTCP *monServeur;
    Et dans ma classe serveurTCP:
    Est-ce correct conceptuellement jusqu'ici?

    Si oui, comment faire pour l'implémenter? En effet, je ne peux pas déclarer un attribut serveurTCP dans ma classe MainWindow puisque les deux classe s'incluent mutuellement.

    Merci d'avance.

  2. #2
    Rédacteur
    Avatar de Amnell
    Homme Profil pro
    Étudiant
    Inscrit en
    Mars 2009
    Messages
    1 840
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Mars 2009
    Messages : 1 840
    Points : 5 545
    Points
    5 545
    Par défaut
    Bonsoir,

    Pour les inclusions, on peut faire une pré-déclaration avec uniquement "class MainWindow;" puis la déclaration d'une classe utilisant MainWindow, puis la classe MainWindow elle-même, par exemple.

    Par contre, sur le plan de l'architecture, ça ne me semble pas une bonne idée. D'où une classe Serveur aurait-elle besoin de communiquer avec une interface graphique ? Attention à bien séparer la partie réseau de la partie graphique. Dans le cas présent, il peut être intéressant d'utiliser des signaux pour dire "dès que le serveur reçoit tel paquet, il envoie tel signal". Une classe Manager, par exemple, récupèrerait les signaux, effectuerait d'éventuels traitements, et les communiqueraient à l'interface graphique qui ferait les manipulations d'affichage nécessaires. Quoi qu'il en soit, évitez de partir du principe qu'une classe MainWindow va servir à tout faire, ce n'est pas une bonne approche C++.

    Bonne soirée,
    Amnell.

  3. #3
    Membre à l'essai
    Profil pro
    Inscrit en
    Juin 2012
    Messages
    25
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2012
    Messages : 25
    Points : 24
    Points
    24
    Par défaut
    Merci pour la réponse.

    J'avais pensé ajouté un pointeur de l'un vers l'autre dans les attributs, comme ceci.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    class serveurTCP;
    class MainWindow : public QMainWindow
    {
        serveurTCP *serveur;
    };
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    class MainWindow;
     
    class serveurTCP: public QTcpServer
    {
        MainWindow *window;
    };
    Mais effectivement il n'y a aucune raison pour qu'un serveur ait besoin d'une interface graphique.
    Si j'ai bien compris, il faut donc faire une classe intermédiaire pour gérer la communication. Est-ce donc cette classe qui possède les pointeurs vers l'interface graphique et le serveur? Est-il vraiment nécessaire d'utiliser des signaux? Ne peut-on pas simplement appeler les méthodes de la classe intermédiaire?

    Quoi qu'il en soit, évitez de partir du principe qu'une classe MainWindow va servir à tout faire, ce n'est pas une bonne approche C++.
    C'est exactement ce que j'aimerais éviter. C'est pour ça que j'ai créé une classe serveurTCP, afin de n'avoir que des fonctions d'affichage dans la classe MainWindow. Mais je ne sais pas comment les interfacer proprement.
    Merci de m'éclairer sur la bonne façon d'implémenter la communication entre classes.

  4. #4
    Rédacteur
    Avatar de Amnell
    Homme Profil pro
    Étudiant
    Inscrit en
    Mars 2009
    Messages
    1 840
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Mars 2009
    Messages : 1 840
    Points : 5 545
    Points
    5 545
    Par défaut
    Bonsoir,

    Déjà, il faut diviser le projet en deux avec l'application serveur et l'application cliente.

    Imaginons que vous avez une application de cuisine, avec les fonctionnalités suivantes à implémenter :

    • Login/logout du membre ;
    • Gestion de recettes ;
    • Gestion des favoris ;
    • Liste de courses ;
    • Planning des déjeuners et dîners de la semaine.


    Détaillons un peu :

    • Dans la gestion des recettes, on a possibilité de consulter des recettes, de les mettre en favori pour les retrouver facilement et de les modifier/corriger pour leur apporter une petite touche personnelle.
    • Dans la gestion des favoris, on a la possibilité de demander la consultation d'une recette, de classifier les recettes favorites selon des thèmes et de les supprimer.
    • Dans la gestion de la liste de course, on peut simplement faire un listing de ce que l'on souhaite acheter.
    • Dans la gestion du planning des déjeuners de la semaine, on a un calendrier de la semaine avec assigné à chaque jour le menu du déjeuner et du dîner.


    Les données suivantes sont à la disposition de l'application serveur, dans une base de données quelconque :

    • Toutes les recettes ;
    • La liste des membres ;
    • Les favoris de chaque membre ;
    • Les modifications de recette de chaque membre ;
    • La liste de courses de chaque membre ;
    • Les planifications des des déjeuners et dîners de la semaine.


    On peut déjà prévoir de découper votre projet en quatre modules principaux, un pour chaque fonction de l'espace membre, plus un qui est relatif à la connexion/déconnexion des membres.

    La solution que je propose pour le serveur.

    En imaginant qu'on ait une DB MySQL derrière :

    • Une classe TcpServer, héritant de QTcpServer, gérant l'envoi et la réception de messages (du serveur aux clients et inversement) ;
    • Une classe Database, héritant par exemple de QSqlDatabase ou en ayant une instance en private, gérant la lecture et l'écriture dans la base de données.


    Ces deux classes sont des classes d'interface. Ce ne sont pas elles qui vont déterminer si tel client a le droit d'accéder à telle ou telle information. Par exemple :

    1. Réception d'un paquet par TcpServer ;
    2. Identification de la provenance (quel membre ?), du type de paquet (RequeteRecetteId) et lecture des paramètres (Id: 4384).
    3. Si le paquet est valide, on envoie un signal receptionSignal avec en paramètre le type et les données du paquet (exemple de prototype de signal : void receptionSignal(int type, QVariant data), QVariant étant une classe très puissante à même de stocker plusieurs données (voir toList(), fromData(), etc.)).


    À ce moment, le module GestionRecettes, connecté au signal receptionSignal de TcpServer, prend la main :

    1. Est-ce que c'est à moi de gérer ce signal ? Tableau de pointeurs sur méthodes. Si on y trouve le type (RequeteRecetteId), on appelle la fonction associée (traitementRequeteRecetteId(QVariant data)).
    2. (À partir d'ici, on est dans traitementRequeteRecetteId()) :
    3. Est-ce que Mr. Fred a le droit de consulter la recette ? Interrogation d'une classe GestionnaireConnexions (un singleton, par exemple).
    4. Si Mr. Fred a le droit de consulter la recette, interrogation de Database : je veux le texte de la recette 4384.
    5. Si Database m'a retourné le texte de la recette, demande à TcpServer d'envoyer un message au client Mr. Fred, avec une liste de données (type : RequeteRecetteId, valeurs : [Id: 4384, Titre: "Gratin de pommes de terres", Texte: "[...]"]).
    6. Si Database m'a retourné que la recette n'existe pas, envoi d'un message au client Mr. Fred, avec une liste de données (type : RequetRecetteIdFailure, valeurs : [Id: 4384, Erreur: "Recette introuvable"]).


    C'est alors au tour de TcpServer de prendre la main pour clore la boucle :

    1. Création du paquet en y mettant les données dedans (un type de paquet et les valeurs à envoyer) ;
    2. Envoi du paquet à Mr. Fred.


    L'arborescence des classes de type "Module" est à définir. À savoir que chaque module va avoir une fonction de réception identique, et éventuellement que chacun va faire des interrogations à GestionConnexions, peut-être envisager une classe mère commune pour éviter des répétitions de code.
    La solution que je propose pour le client.

    Une seule classe générale :

    • Une classe TcpSocket, héritant de QTcpSocket, permettant tout comme la classe TcpServer du serveur de gérer les envois/réception des données (du serveur au client et inversement).

    Tout comme pour TcpServer, il s'agit d'une classe d'interface, ce n'est pas non plus à elle de dire quoi faire à la réception de tel ou tel message.

    Il est important de savoir que chaque module, dans le cas du client, est relié à l'interface graphique. Par exemple, le module GestionRecettes du client va être relié à une interface particulière dédiée à l'affichage des recettes et à deux trois choses comme des boutons "Ajouter/Retirer aux/des favoris" ou encore "Modifier la recette".

    Bref, on va réaliser une interface Model/View : on va faire en sorte que chaque module soit relié à une vue (une interface graphique, par exemple réalisée avec Qt Designer). Du genre, la classe GestionRecettes aura à l'intérieur une instance de la classe GestionRecettesInterface qui n'est ni plus ni moins l'interface graphique de la frame de visualisation des recettes. De même, la classe GestionConnexion aura à l'intérieur une instance de la classe GestionConnexionInterface, l'interface graphique où on permet à l'utilisateur de se connecter au serveur avec un identifiant et un mot de passe.

    Une question se pose : comment transiter d'une interface d'un module à une autre, à savoir qu'on aurait par exemple un menu sur le côté nous permettant de changer de vue ? Réponse simple : les machines à états.

    MainWindow sera en fait une machine à états qui, selon l'état qu'on va lui demander d'adopter ("EtatAffichageRecettesInterface", "EtatAffichageConnexionInterface", etc.), va faire du setVisible(true/false) ou ce qu'elle souhaite sur les interfaces graphiques des modules afin de n'avoir visible que les interfaces que l'on souhaite avoir à l'écran. L'étude de QStateMachine peut être intéressante.

    Jusque là, on a abordé la partie view de l'approche Model/View. Qu'en est-il de la partie Model ? L'interface graphique ne doit aucunement être responsable des décisions, c'est le module qui gère ça. Le module gère également à l'intérieur de sa description de classe des propriétés (Q_PROPERTY ou autre), en fait des variables contenant les données importantes au bon fonctionnement du module.

    Illustration d'un changement d'états et des conséquences :

    1. L'utilisateur, une fois connecté, appuie sur le bouton "Recettes favorites" du menu.
    2. Appel d'une fonction de changement d'état de MainWindow pour lui demander de passer à l'état "EtatAffichageRecettesFavorites".
    3. MainWindow masque l'ancienne frame, s'il y en avait (par exemple, si "EtatAffichageRecettesInterface" était l'ancien état, MainWindow masque la GestionRecettesInterface) puis affiche la nouvelle (GestionRecettesFavoritesInterface).
    4. La classe module (GestionRecettesFavorites) est notifiée du changement d'états par signaux slots (elle serait connectée à un stateChanged() ou autre de MainWindow) et entre dans la fonction initInterface().
    5. Dans cette fonction, envoi d'un message du client au serveur par le biais de TcpSocket (type : RequeteRecettesFavoritesListe, valeurs : aucune, vu que le serveur doit être capable de déterminer de qui vient le message).
    6. On zappe la partie TcpSocket, c'est la même que pour TcpServer : envoi du message puis tôt ou tard, réception d'un message du serveur, interprétation, envoi du signal et catch du signal par le module GestionRecettesFavorites qui dit gogogo, on interprète.
    7. Dans la fonction de réception de la liste des favoris, on va juste mettre à jour une propriété interne (FavListe, contenant les Ids et les noms des recettes en favoris - c'est là l'aspect Model de l'architecture) et notifier via un signal/fonction la classe interface de mettre à jour ses données selon la nouvelle liste.
    8. La classe interface (GestionRecettesInterface) met à jour ses données graphiques, fin de l'histoire.


    Illustration d'une action utilisateur nécessitant (déclenchement d'une action depuis l'interface graphique associée au module, en gros) :

    1. L'utilisateur clique sur le bouton "Ajouter aux favoris".
    2. Envoi d'un signal depuis l'interface graphique disant que le bouton s'est fait cliquer dessus ;
    3. La classe module étant connectée au signal, on entre dans une de ses fonctions.
    4. Envoi d'un message au serveur (type : AjoutRecetteFavorites, valeurs : [Id: 4953, Nom: "Tarte aux pommes"]).
    5. Le serveur va répondre en envoyant la nouvelle liste des favoris. Réception dans le module TcpSocket des données, on rattrape les opérations de la liste illustrant blablabla, c'est-à-dire (let's copy/paste) :
    6. Déjà implémentée, à ne pas dupliquer : Dans la fonction de réception de la liste des favoris, on va juste mettre à jour une propriété interne (FavListe, contenant les Ids et les noms des recettes en favoris - c'est là l'aspect Model de l'architecture) et notifier via un signal/fonction la classe interface de mettre à jour ses données selon la nouvelle liste.
    7. Déjà implémentée, à ne pas dupliquer : La classe interface (GestionRecettesInterface) met à jour ses données graphiques, fin de l'histoire.
    Voilà ma proposition pour une application client/serveur de ce type. Attention, il existe de nombreuses approches pour une telle architecture, celle-ci n'est pas nécessairement meilleure qu'une autre.

    Espérant vous avoir éclairé,
    M'excusant également de cet énorme bloc,
    Amnell.

  5. #5
    Membre à l'essai
    Profil pro
    Inscrit en
    Juin 2012
    Messages
    25
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2012
    Messages : 25
    Points : 24
    Points
    24
    Par défaut
    Merci pour cette réponse!
    2 questions pour comprendre le fonctionnement et l’intérêt d'une telle architecture:

    chaque module, dans le cas du client, est relié à l'interface graphique.
    Cela signifie-t-il que les classes du modèle ont une référence vers les classes de la vue qui les intéressent? L'accès aux champs de la vue se fait donc via un pointeur dans les attributs du modèle ( et inversement ) ?
    La classe GestionConnexion aura donc une référence vers TcpSocket et une vers l'IHM?

    4. La classe module (GestionRecettesFavorites) est notifiée du changement d'états par signaux slots (elle serait connectée à un stateChanged() ou autre de MainWindow) et entre dans la fonction initInterface().
    Quel est l’intérêt d'utiliser un mécanisme signal/slot? Un appel de méthode ne pourrait-il pas faire l'affaire, l'interface étant reliée au module?
    Même question pour l'étape:
    6. [...] notifier via un signal/fonction la classe interface de mettre à jour ses données selon la nouvelle liste.
    Merci pour les informations, ça commence à prendre forme.

  6. #6
    Rédacteur
    Avatar de Amnell
    Homme Profil pro
    Étudiant
    Inscrit en
    Mars 2009
    Messages
    1 840
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Mars 2009
    Messages : 1 840
    Points : 5 545
    Points
    5 545
    Par défaut
    Bonsoir,

    Oui, c'est exact, la classe Modèle est reliée dans le cas que je présente à la classe View par le biais d'un pointeur et inversement. Attention cependant à ne pas encombrer la classe modèle de traitement graphiques. Généralement, je me limite aux opérations de setVisible() pour ce genre de chose. Le reste, c'est la view qui gère lors de la réception de signaux notifiant des modifications dans le modèle (du genre, dans le cas où on reçoit une recette depuis le serveur, le module met juste à jour sa partie modèle, qui envoie une notification de modification - un signal - auquel l'interface graphique est connectée afin de se mettre à jour).

    Pour montrer l'intérêt du mécanisme de signaux et de slots, je propose d'étudier TcpSocket. Lors de la réception de données depuis le serveur, la classe va émettre un signal receptionSignal(...) auquel les différents modules seront connectés. L'intérêt ? TcpSocket n'a aucun pointeur/autre vers les modules. Il n'a même pas connaissance de leur existence. L'objectif est que TcpSocket soit une classe générique qui ne dépend pas des autres modules. De ce fait, si un nouveau module est sur le point d'être écrit ou bien si un module existant doit être retiré, pas une ligne de code n'est à modifier du côté de TcpSocket.

    Pour la machine à états MainWindow, je pense que je me suis un peu trompé vu que je sous-entends qu'elle possède des liens avec les différents modules et/ou interfaces graphiques de modules. On peut la présenter différemment pour couper tous les liens avec les modules et la rendre également indépendante. De ce fait, on corrige :

    MainWindow sera en fait une machine à états qui, selon l'état qu'on va lui demander d'adopter ("EtatAffichageRecettesInterface", "EtatAffichageConnexionInterface", etc.), va envoyer un signal stateChanged(oldState, newState) auquel tous les modules à interface graphique seront connectés. Lors de la réception du signal, chaque module va suivre une procédure minimale :

    1. Si oldState est le state du module (par exemple dans le cas où oldState est égal à "EtatAffichageRecettesInterface" et que le module qui traite l'arrivée du signal est GestionRecettes), le module fait du setVisible(false) ou autre pour masquer son interface graphique.
    2. Si newState est le state du module (par exemple dans le cas où newState est égal à "EtatAffichageRecettesInterface" et que le module qui traite l'arrivée du signal est GestionRecettes), le module fait du setVisible(true) ou autre pour afficher son interface graphique.


    Cette procédure est minimale dans le sens où on peut bien entendu ajouter des comportements lors de l'affichage de l'interface graphique (pour une interrogation du serveur sur un point, etc.).
    Bonne soirée,
    Amnell.

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. [Web Service] Communication entre PHP et serveurs de jeux
    Par DieseL. dans le forum Bibliothèques et frameworks
    Réponses: 5
    Dernier message: 21/11/2014, 14h00
  2. [WS 2003] Pbm de communication entre clients et serveur sur les ranges IP differents
    Par CodeurNé dans le forum Windows Serveur
    Réponses: 0
    Dernier message: 12/07/2011, 11h45
  3. Réponses: 4
    Dernier message: 21/08/2009, 10h48
  4. Réponses: 3
    Dernier message: 22/04/2009, 13h06
  5. [Java] Communication entre client et serveur
    Par danje dans le forum CORBA
    Réponses: 1
    Dernier message: 14/12/2004, 18h08

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