Accueil | T�l�chargement | Exemple rapide | Tutoriel (4) | Manuel (2) | Forum | Nos clients

QxOrm >> Tutoriel >> qxClientServer

Version courante : | QxOrm 1.5.0 - documentation en ligne de la biblioth�que QxOrm - GitHub
| QxEntityEditor 1.2.8

S�lection du tutoriel : | Tuto n�1 : qxBlog Tuto n�2 : qxClientServer Tuto n�3 : install QxOrm Tuto n�4 : video QxEntityEditor

Le tutoriel qxClientServer a pour objectif d'expliquer le fonctionnement du module QxService de la biblioth�que QxOrm.
Le module QxService permet de cr�er rapidement un serveur d'applications C++ performant (notion de services avec demande du client et r�ponse du serveur).
Les sources du projet qxClientServer sont disponibles dans le dossier ./test/qxClientServer/ de la distribution de QxOrm.
Il est conseill� d'avoir lu le tutoriel qxBlog avant de lire cet article, notamment tout ce qui concerne la fonction de mapping de QxOrm : void qx::register_class<T>(...).
Remarque : pour activer le module QxService (n�cessaire pour compiler les sources de ce tutoriel), il faut d�finir l'option de compilation _QX_ENABLE_QT_NETWORK dans le fichier de configuration QxOrm.pri. Pour plus de d�tails sur ces options de compilation, rendez-vous sur le manuel utilisateur de la biblioth�que QxOrm.
Le r�sultat final de ce tutoriel comporte deux ex�cutables et une couche service :

- qxServer : serveur d'applications C++ avec une interface utilisateur pour param�trer le serveur et un champ pour afficher la derni�re transaction effectu�e entre le client et le serveur ;
- qxClient : interface utilisateur contenant plusieurs boutons pour ex�cuter diff�rentes requ�tes au serveur ;
- qxService : couche service, le serveur et le client partagent cette m�me couche pour transf�rer les donn�es et appeler les services.

Le tutoriel qxClientServer est constitu� des �tapes suivantes :

- 1- Cr�ation de l'interface serveur : qxServer
  - 1.1- Description du fichier main_dlg.h
  - 1.2- Description du fichier main_dlg.cpp, m�thode init()
  - 1.3- Description du fichier main_dlg.cpp, m�thode loadServices()
  - 1.4- Description du fichier main_dlg.cpp, m�thode onClickStartStop()
  - 1.5- Description du fichier main_dlg.cpp, m�thodes onError() et onTransactionFinished()
  - 1.6- R�sultat obtenu pour le projet qxServer
- 2- Cr�ation de la couche service : qxService
  - 2.1- Ecriture du premier service : r�cup�rer la date-heure courante du serveur
  - 2.2- Description du fichier server_infos.h
  - 2.3- Description du fichier server_infos.cpp
  - 2.4- Ecriture du second service : op�rations avec une classe persistante
- 3- Cr�ation de l'interface cliente : qxClient
  - 3.1- Description de la m�thode onClickBtnDateTime()
  - 3.2- Description de la m�thode onClickBtnDateTimeAsync()
  - 3.3- Description de la m�thode onClickBtnAddUser()
  - 3.4- Description de la m�thode onClickBtnGetAllUsers()

Remarque : ce tutoriel est �galement disponible sur le site www.developpez.com.

QxOrm library has been accepted into the Qt Ambassador Program

Remarque : pour plus de d�tails sur la notion de socket, de thread et de r�seau, le site de Qt propose des tutoriels sur l'utilisation du module QtNetwork :

- Fortune Client ;
- Blocking Fortune Client ;
- Fortune Server ;
- Threaded Fortune Server.

1- Cr�ation de l'interface serveur : qxServer
Le projet qxServer contient une seule fen�tre : l'interface utilisateur a �t� r�alis�e avec l'outil Qt Designer propos� par la biblioth�que Qt.
Cette interface a pour seul objectif d'afficher � l'utilisateur la derni�re transaction client-serveur, et de pouvoir configurer certains param�tres du serveur.
Pour une utilisation r�elle (logiciel de production), il est conseill� de proposer un syt�me de log plut�t qu'un affichage � l'utilisateur.
Une interface la plus minimaliste possible (voire aucune interface) est de mani�re g�n�rale la solution la plus optimale pour un serveur d'applications.
Les fichiers main_dlg.h et main_dlg.cpp correspondent au code C++ de l'interface du projet qxServer.
1.1- Description du fichier main_dlg.h

    #ifndef _QX_SERVER_MAIN_DLG_H_
    #define _QX_SERVER_MAIN_DLG_H_

    #include "../qt/ui/include/ui_qxServer.h"

    class main_dlg : public QWidget, private Ui::dlgServer
    { Q_OBJECT

    private:

       qx::service::QxThreadPool_ptr m_pThreadPool; // Liste de threads pour recevoir les requ�tes des clients

    public:

       main_dlg(QWidget * parent = NULL) : QWidget(parent), Ui::dlgServer() { main_dlg::init(); }
       virtual ~main_dlg() { ; }

    private:

       void init();
       void loadServices();

    private Q_SLOTS:

       void onClickStartStop();
       void onCboIndexChanged(int index);
       void onError(const QString & err, qx::service::QxTransaction_ptr transaction);
       void onServerIsRunning(bool bIsRunning, qx::service::QxServer * pServer);
       void onTransactionFinished(qx::service::QxTransaction_ptr transaction);

    };

    #endif // _QX_SERVER_MAIN_DLG_H_

La variable m_pThreadPool de type qx::service::QxThreadPool_ptr contient toute la logique du serveur d'applications.
Cette logique est g�r�e de mani�re automatique par la biblioth�que QxOrm.
La m�thode init() permet d'initialiser les param�tres par d�faut du serveur, de connecter les �v�nements SIGNAL-SLOT et de lancer automatiquement le serveur.
Nous allons voir tout ceci plus en d�tails avec l'impl�mentation des m�thodes dans le fichier main_dlg.cpp...
1.2- Description du fichier main_dlg.cpp, m�thode init()

    void main_dlg::init()
    {
       setupUi(this);

       QObject::connect(btnStartStop, SIGNAL(clicked()), this, SLOT(onClickStartStop()));
       QObject::connect(cboSerializationType, SIGNAL(currentIndexChanged(int)), this, SLOT(onCboIndexChanged(int)));

       cboSerializationType->addItem("0- serialization_binary", QVariant((int)qx::service::QxConnect::serialization_binary));
       cboSerializationType->addItem("1- serialization_xml", QVariant((int)qx::service::QxConnect::serialization_xml));
       cboSerializationType->addItem("2- serialization_text", QVariant((int)qx::service::QxConnect::serialization_text));
       cboSerializationType->addItem("3- serialization_portable_binary", QVariant((int)qx::service::QxConnect::serialization_portable_binary));
       cboSerializationType->addItem("4- serialization_wide_binary", QVariant((int)qx::service::QxConnect::serialization_wide_binary));
       cboSerializationType->addItem("5- serialization_wide_xml", QVariant((int)qx::service::QxConnect::serialization_wide_xml));
       cboSerializationType->addItem("6- serialization_wide_text", QVariant((int)qx::service::QxConnect::serialization_wide_text));
       cboSerializationType->addItem("7- serialization_polymorphic_binary", QVariant((int)qx::service::QxConnect::serialization_polymorphic_binary));
       cboSerializationType->addItem("8- serialization_polymorphic_xml", QVariant((int)qx::service::QxConnect::serialization_polymorphic_xml));
       cboSerializationType->addItem("9- serialization_polymorphic_text", QVariant((int)qx::service::QxConnect::serialization_polymorphic_text));
       cboSerializationType->setCurrentIndex(cboSerializationType->findData(QVariant((int)qx::service::QxConnect::getSingleton()->getSerializationType())));

       spinPortNumber->setValue(7694);
       spinThreadCount->setValue(qx::service::QxConnect::getSingleton()->getThreadCount());
       onServerIsRunning(false, NULL);
       onClickStartStop();
    }

L'�v�nement onClickStartStop() permet de d�marrer/arr�ter le serveur.
Le serveur d'applications peut s�rialiser les r�ponses � envoyer aux clients de plusieurs fa�ons : ce param�tre est disponible avec la combobox cboSerializationType.
Pour plus d'informations sur les diff�rents types de s�rialisation support�s par la biblioth�que QxOrm, suivre ce lien du manuel utilisateur.
D'une mani�re g�n�rale, la s�rialisation binaire est fortement conseill�e pour une transaction r�seau car elle est plus rapide � ex�cuter et permet de limiter le traffic sur le r�seau.
On d�finit �galement le port d'�coute du serveur d'applications avec le champ spinPortNumber.
Un param�tre important est le nombre de threads disponibles sur le serveur d'applications : cel� correspond aux nombres de clients pouvant se connecter au serveur simultan�ment.
La valeur par d�faut de ce param�tre est 30, vous pouvez modifier cette valeur suivant la charge estim�e de votre serveur d'applications.
Si le nombre de clients d�passe le nombre de threads disponibles, la requ�te est mise en attente : d�s qu'un thread se lib�re, alors la requ�te s'ex�cute normalement.
Tout ceci est g�r� automatiquement par la biblioth�que QxOrm : il est juste important de faire une estimation de la charge que pourra avoir votre serveur d'applications.
Enfin, l'appel � onClickStartStop() permet de d�marrer automatiquement le serveur d�s l'ex�cution du programme qxServer.
1.3- Description du fichier main_dlg.cpp, m�thode loadServices()

    void main_dlg::loadServices()
    {
       // N�cessaire pour �tre certain de charger les DLL contenant les services : cr�ation d'un service fant�me pour chaque DLL
       // Il peut �tre int�ressant � ce niveau de cr�er un m�canisme de plugins pour charger les diff�rents services
       server_infos dummy_01; Q_UNUSED(dummy_01);
    }

La m�thode loadServices() est l'unique d�pendance avec les services propos�s par le serveur d'applications.
Elle sert uniquement � cr�er une instance fant�me pour �tre certain que la DLL contenant la liste des services soient correctement charg�e au d�marrage de l'application.
Pour un logiciel en production, il peut �tre int�ressant � ce niveau de proposer un syst�me de plugins pour charger les diff�rents services.
1.4- Description du fichier main_dlg.cpp, m�thode onClickStartStop()

    void main_dlg::onClickStartStop()
    {
       if (m_pThreadPool)
       {
          m_pThreadPool->disconnect();
          m_pThreadPool.reset();
          txtError->setPlainText("");
          txtTransaction->setPlainText("");
          onServerIsRunning(false, NULL);
       }
       else
       {
          qx::service::QxConnect::getSingleton()->setPort(spinPortNumber->value());
          qx::service::QxConnect::getSingleton()->setThreadCount(spinThreadCount->value());
          qx::service::QxConnect::getSingleton()->setSerializationType((qx::service::QxConnect::serialization_type)
                                                                       (cboSerializationType->itemData(cboSerializationType->currentIndex()).toInt()));
          qx::service::QxConnect::getSingleton()->setCompressData(chkCompressData->isChecked());
          qx::service::QxConnect::getSingleton()->setEncryptData(chkEncryptData->isChecked());

          m_pThreadPool.reset(new qx::service::QxThreadPool());
          QObject::connect(m_pThreadPool.get(), SIGNAL(error(const QString &, qx::service::QxTransaction_ptr)), this,
                                                SLOT(onError(const QString &, qx::service::QxTransaction_ptr)));
          QObject::connect(m_pThreadPool.get(), SIGNAL(serverIsRunning(bool, qx::service::QxServer *)), this,
                                                SLOT(onServerIsRunning(bool, qx::service::QxServer *)));
          QObject::connect(m_pThreadPool.get(), SIGNAL(transactionFinished(qx::service::QxTransaction_ptr)), this,
                                                SLOT(onTransactionFinished(qx::service::QxTransaction_ptr)));
          m_pThreadPool->start();
       }
    }

La m�thode onClickStartStop() permet de d�marrer/arr�ter le serveur d'applications : elle s'occupe de cr�er une instance de type qx::service::QxThreadPool_ptr ou bien de la d�truire.
Si la variable m_pThreadPool est valoris�e, alors cel� signifie que l'on souhaite arr�ter le serveur : m_pThreadPool.reset();.
Sinon, le serveur est arr�t� donc on souhaite le d�marrer :
m_pThreadPool.reset(new qx::service::QxThreadPool());.
m_pThreadPool->start();.
Le param�trage du serveur est effectu� gr�ce au singleton qx::service::QxConnect::getSingleton().
Enfin, l'interface utilisateur s'abonne aux �v�nements envoy�s par le serveur d'applications (m�canisme SIGNAL-SLOT de Qt) pour r�cup�rer une erreur ou bien afficher la derni�re transaction client-serveur.
1.5- Description du fichier main_dlg.cpp, m�thodes onError() et onTransactionFinished()

    void main_dlg::onError(const QString & err, qx::service::QxTransaction_ptr transaction)
    {
       if (err.isEmpty()) { txtError->setPlainText(""); return; }
       QString errText = QDateTime::currentDateTime().toString("dd.MM.yyyy hh:mm") + " : " + err;
       if (transaction) { errText += QString("\r\n\r\n") + qx::serialization::xml::to_string(* transaction); }
       txtError->setPlainText(errText.replace("\t", "    "));
    }

    void main_dlg::onTransactionFinished(qx::service::QxTransaction_ptr transaction)
    {
       if (! transaction) { txtTransaction->setPlainText(""); return; }
       QString text = qx::serialization::xml::to_string(* transaction);
       txtTransaction->setPlainText(text.replace("\t", "    "));
    }

Toutes les transactions entre client et serveur sont repr�sent�es par la classe qx::service::QxTransaction_ptr.
Cette classe contient toutes les informations n�cessaires � l'ex�cution d'un service (identifiant unique, date-heure, requ�te du client, service � ex�cuter, r�ponse du serveur, code et message d'erreur, etc.).
La transaction est s�rialis�e au format XML (ou JSON) avant d'�tre affich�e � l'utilisateur dans le champ txtTransaction.
Cette s�rialisation est ind�pendante de la r�ponse envoy�e au client qui, par d�faut, est au format binaire.
1.6- R�sultat obtenu pour le projet qxServer
Et... c'est tout : vous pouvez constater que l'�criture d'un serveur d'applications est extr�mement simple avec la biblioth�que QxOrm.
Votre serveur d'applications est pr�t pour proposer de multiples services aux diff�rents clients.
Voici le r�sultat obtenu :
2- Cr�ation de la couche service : qxService
La couche service doit �tre partag�e entre le client et le serveur.
La compilation du projet qxService cr�e deux DLL (ou fichiers *.so sous Linux) : qxServiceClient et qxServiceServer.
Une option de compilation _QX_SERVICE_MODE_CLIENT permet de faire la distinction entre le client et le serveur.
L'outil qmake de Qt et le syst�me de fichiers *.pro et *.pri permettent de cr�er facilement ce type d'architecture :

- Le fichier qxService.pri correspond au tronc commun des deux DLL, c'est-�-dire l'ensemble des d�pendances et des fichiers � compiler ;
- Le fichier qxServiceClient.pro est sp�cifique au mode client : d�finition de l'option de compilation _QX_SERVICE_MODE_CLIENT et du nom de la DLL ;
- Le fichier qxServiceServer.pro est sp�cifique au mode serveur : d�finition du nom de la DLL.

Il est important de signaler que ce m�canisme permet au programme client de partager les m�mes fichiers que le programme serveur.
La partie cliente n'a aucun code � �crire pour pouvoir appeler un service : le serveur peut livrer la liste des fichiers de type headers, les .dll et .lib (ou *.so sous Linux).
2.1- Ecriture du premier service : r�cup�rer la date-heure courante du serveur
Le premier service propos� par notre serveur d'applications de test est relativement simple : il consiste � renvoyer aux clients la date-heure courante du serveur.
Ce service est disponible avec la classe server_infos : fichiers server_infos.h et server_infos.cpp.
Une m�me classe peut proposer plusieurs services : la classe server_infos pourrait par exemple renvoyer en plus de la date-heure courante, un nom de machine, une fr�quence processeur du serveur, etc.
Chaque classe service poss�de des param�tres d'entr�e (demande du client) et des param�tres de sortie (r�ponse du serveur).
Une classe param�tre (entr�e ou sortie) doit h�riter de la classe qx::service::IxParameter et doit �tre s�rialisable.
Une classe service doit h�riter du template qx::service::QxService<INPUT, OUTPUT> et doit d�finir une liste de m�thodes (services disponibles).
Il est conseill� d'�crire les classes param�tres d'entr�e, param�tres de sortie et services dans le m�me fichier.
2.2- Description du fichier server_infos.h

    #ifndef _QX_SERVICE_SERVER_INFOS_H_
    #define _QX_SERVICE_SERVER_INFOS_H_

    /* -- Liste des param�tres d'entr�e du service -- */

    class QX_SERVICE_DLL_EXPORT server_infos_input : public qx::service::IxParameter
    { ; };

    QX_REGISTER_HPP_QX_SERVICE(server_infos_input, qx::service::IxParameter, 0)
    typedef std::shared_ptr<server_infos_input> server_infos_input_ptr;

    /* -- Liste des param�tres de sortie du service -- */

    class QX_SERVICE_DLL_EXPORT server_infos_output : public qx::service::IxParameter
    { public: QDateTime current_date_time; };

    QX_REGISTER_HPP_QX_SERVICE(server_infos_output, qx::service::IxParameter, 0)
    typedef std::shared_ptr<server_infos_output> server_infos_output_ptr;

    /* -- D�finition du service -- */

    typedef qx::service::QxService<server_infos_input, server_infos_output> server_infos_base_class;
    class QX_SERVICE_DLL_EXPORT server_infos : public server_infos_base_class
    {
    public:
       server_infos() : server_infos_base_class("server_infos") { ; }
       virtual ~server_infos() { ; }
       void get_current_date_time();
    };

    QX_REGISTER_HPP_QX_SERVICE(server_infos, qx::service::IxService, 0)
    typedef std::shared_ptr<server_infos> server_infos_ptr;

    #endif // _QX_SERVICE_SERVER_INFOS_H_

Le fichier server_infos.h poss�de trois classes :

- server_infos_input : h�rite de qx::service::IxParameter et correspond aux param�tres d'entr�e du service (demande du client). Notre service de test n'a pas besoin de param�tres en entr�e, donc cette classe ne contient aucune propri�t� ;
- server_infos_output : h�rite de qx::service::IxParameter et correspond aux param�tres de sortie du service (r�ponse du serveur). Cette classe contient une seule propri�t�, la date-heure courante du serveur (QDateTime current_date_time) ;
- server_infos : h�rite de qx::service::QxService<INPUT, OUTPUT> et contient la liste des services disponibles : une seule m�thode pour r�cup�rer la date-heure courante du serveur.

Ces trois classes doivent �tre enregistr�es dans le contexte QxOrm, de la m�me fa�on qu'une classe persistante (voir le tutoriel qxBlog).
C'est pourquoi nous utilisons la macro QX_REGISTER_HPP_QX_SERVICE pour ces trois classes.
De plus, pour simplifier l'�criture des pointeurs, la gestion de la m�moire et �viter les probl�mes de fuites m�moires, nous utilisons les pointeurs intelligents de la biblioth�que standard : std::shared_ptr.
Le module QxService travaille essentiellement avec des pointeurs intelligents, c'est pourquoi il est fortement conseill� de cr�er les typedef correspondants, par exemple :
typedef std::shared_ptr<server_infos_input> server_infos_input_ptr;.
typedef std::shared_ptr<server_infos_output> server_infos_output_ptr;.
typedef std::shared_ptr<server_infos> server_infos_ptr;.
Enfin, le constructeur du service doit indiquer en param�tre le nom de la classe sous forme de cha�ne de caract�res : ceci est indispensable pour le moteur d'introspection de QxOrm pour pouvoir instancier dynamiquement les services correspondant aux requ�tes des clients.
2.3- Description du fichier server_infos.cpp

    #include "../../include/precompiled.h"

    #include "../../include/service/server_infos.h"

    #include <QxOrm_Impl.h>

    QX_REGISTER_CPP_QX_SERVICE(server_infos_input)
    QX_REGISTER_CPP_QX_SERVICE(server_infos_output)
    QX_REGISTER_CPP_QX_SERVICE(server_infos)

    namespace qx {

    template <> void register_class(QxClass<server_infos_input> & t)
    { Q_UNUSED(t); }

    template <> void register_class(QxClass<server_infos_output> & t)
    { t.data(& server_infos_output::current_date_time, "current_date_time"); }

    template <> void register_class(QxClass<server_infos> & t)
    { t.fct_0<void>(& server_infos::get_current_date_time, "get_current_date_time"); }

    } // namespace qx

    #ifdef _QX_SERVICE_MODE_CLIENT

    void server_infos::get_current_date_time()
    { qx::service::execute_client(this, "get_current_date_time"); }

    #else // _QX_SERVICE_MODE_CLIENT

    void server_infos::get_current_date_time()
    {
       server_infos_output_ptr output = server_infos_output_ptr(new server_infos_output());
       output->current_date_time = QDateTime::currentDateTime();
       setOutputParameter(output);
       setMessageReturn(true);
    }

    #endif // _QX_SERVICE_MODE_CLIENT

Le fichier server_infos.cpp contient l'impl�mentation du service pour le mode client et le mode serveur : c'est la macro _QX_SERVICE_MODE_CLIENT qui fait la distinction entre client et serveur au moment de la compilation du projet.
La macro QX_REGISTER_CPP_QX_SERVICE permet d'enregistrer les trois classes dans le contexte QxOrm, de la m�me fa�on qu'une classe persistante (voir le tutoriel qxBlog).
Ensuite, nous �crivons la m�thode de mapping void qx::register_class(...) pour les trois classes du service :

- Les deux classes de param�tres enregistrent les propri�t�s utilis�es pour effectuer une demande du client (aucune pour notre service de test), et les propri�t�s qui seront renvoy�es pour la r�ponse du serveur (date-heure courante : t.data(& server_infos_output::current_date_time, "current_date_time");) ;
- La classe service doit enregistrer la liste des m�thodes disponibles, dans notre cas : t.fct_0<void>(& server_infos::get_current_date_time, "get_current_date_time");.

Remarque : toutes les m�thodes de type service doivent avoir la m�me signature : pas de valeur de retour, et pas d'argument (par exemple : void my_service()).
En effet, dans un service, les param�tres d'entr�e sont disponibles par la m�thode getInputParameter() (de type server_infos_input_ptr dans notre exemple).
Les param�tres de sortie peuvent �tre valoris�s par la m�thode setOutputParameter() (de type server_infos_output_ptr dans notre exemple).
Une valeur de retour de type qx_bool permet d'indiquer que la transaction s'est d�roul�e normalement, ou bien qu'une erreur quelconque est survenue (avec libell� et code de l'erreur).
Il est tr�s important d'�crire setMessageReturn(true); � la fin de chaque m�thode service pour indiquer que tout s'est bien d�roul�.
La derni�re partie de notre fichier contient l'impl�mentation de la m�thode server_infos::get_current_date_time() pour le mode client et serveur :

- Pour le mode client, le code est tr�s simple et sera le m�me pour tous les services : qx::service::execute_client(this, "get_current_date_time"); ;
- Pour le mode serveur, notre service de test est tr�s simple : on valorise la date-heure courante, on la transf�re dans les param�tres de sortie, puis on indique que la transaction s'est d�roul�e sans aucune erreur.

2.4- Ecriture du second service : op�rations avec une classe persistante
Le projet qxService contient un second exemple de service plus complet avec une classe persistante (classe user), et des actions sur une base de donn�es (SELECT, INSERT, UPDATE, DELETE, etc.).
Ce deuxi�me exemple fait transiter sur le r�seau des structures complexes : pointeurs, pointeurs intelligents, collections, crit�res de recherche, etc.
Nous ne d�taillerons pas ce second service dans le tutoriel, le principe �tant identique au premier service :

    #include "../../include/precompiled.h"

    #include "../../include/service/user_service.h"

    #include "../../include/dao/user_manager.h"

    #include <QxOrm_Impl.h>

    QX_REGISTER_CPP_QX_SERVICE(user_service_input)
    QX_REGISTER_CPP_QX_SERVICE(user_service_output)
    QX_REGISTER_CPP_QX_SERVICE(user_service)

    namespace qx {

    template <> void register_class(QxClass<user_service_input> & t)
    {
       t.data(& user_service_input::id, "id");
       t.data(& user_service_input::user, "user");
       t.data(& user_service_input::criteria, "criteria");
    }

    template <> void register_class(QxClass<user_service_output> & t)
    {
       t.data(& user_service_output::user, "user");
       t.data(& user_service_output::list_of_users, "list_of_users");
    }

    template <> void register_class(QxClass<user_service> & t)
    {
       t.fct_0<void>(& user_service::insert, "insert");
       t.fct_0<void>(& user_service::update, "update");
       t.fct_0<void>(& user_service::remove, "remove");
       t.fct_0<void>(& user_service::remove_all, "remove_all");
       t.fct_0<void>(& user_service::fetch_by_id, "fetch_by_id");
       t.fct_0<void>(& user_service::fetch_all, "fetch_all");
       t.fct_0<void>(& user_service::get_by_criteria, "get_by_criteria");
    }

    } // namespace qx

    #ifdef _QX_SERVICE_MODE_CLIENT

    void user_service::insert()            { qx::service::execute_client(this, "insert"); }
    void user_service::update()            { qx::service::execute_client(this, "update"); }
    void user_service::remove()            { qx::service::execute_client(this, "remove"); }
    void user_service::remove_all()        { qx::service::execute_client(this, "remove_all"); }
    void user_service::fetch_by_id()       { qx::service::execute_client(this, "fetch_by_id"); }
    void user_service::fetch_all()         { qx::service::execute_client(this, "fetch_all"); }
    void user_service::get_by_criteria()   { qx::service::execute_client(this, "get_by_criteria"); }

    #else // _QX_SERVICE_MODE_CLIENT

    void user_service::insert()
    {
       user_service_input_ptr input = getInputParameter();
       if (! input) { setMessageReturn(0, "invalid input parameter to call service 'user_service::insert()'"); return; }
       QSqlError err = user_manager().insert(input->user);
       if (err.isValid()) { setMessageReturn(0, err.text()); return; }
       user_service_output_ptr output = user_service_output_ptr(new user_service_output());
       output->user = input->user;
       setOutputParameter(output);
       setMessageReturn(true);
    }

    void user_service::update()
    {
       user_service_input_ptr input = getInputParameter();
       if (! input) { setMessageReturn(0, "invalid input parameter to call service 'user_service::update()'"); return; }
       QSqlError err = user_manager().update(input->user);
       if (err.isValid()) { setMessageReturn(0, err.text()); }
       else { setMessageReturn(true); }
    }

    void user_service::remove()
    {
       user_service_input_ptr input = getInputParameter();
       if (! input) { setMessageReturn(0, "invalid input parameter to call service 'user_service::remove()'"); return; }
       user_ptr user_tmp = user_ptr(new user());
       user_tmp->id = input->id;
       QSqlError err = user_manager().remove(user_tmp);
       if (err.isValid()) { setMessageReturn(0, err.text()); }
       else { setMessageReturn(true); }
    }

    void user_service::remove_all()
    {
       QSqlError err = user_manager().remove_all();
       if (err.isValid()) { setMessageReturn(0, err.text()); }
       else { setMessageReturn(true); }
    }

    void user_service::fetch_by_id()
    {
       user_service_input_ptr input = getInputParameter();
       if (! input) { setMessageReturn(0, "invalid input parameter to call service 'user_service::fetch_by_id()'"); return; }
       user_ptr user_output = user_ptr(new user());
       user_output->id = input->id;
       QSqlError err = user_manager().fetch_by_id(user_output);
       if (err.isValid()) { setMessageReturn(0, err.text()); return; }
       user_service_output_ptr output = user_service_output_ptr(new user_service_output());
       output->user = user_output;
       setOutputParameter(output);
       setMessageReturn(true);
    }

    void user_service::fetch_all()
    {
       list_of_users_ptr list_of_users_output = list_of_users_ptr(new list_of_users());
       QSqlError err = user_manager().fetch_all(list_of_users_output);
       if (err.isValid()) { setMessageReturn(0, err.text()); return; }
       user_service_output_ptr output = user_service_output_ptr(new user_service_output());
       output->list_of_users = list_of_users_output;
       setOutputParameter(output);
       setMessageReturn(true);
    }

    void user_service::get_by_criteria()
    {
       user_service_input_ptr input = getInputParameter();
       if (! input) { setMessageReturn(0, "invalid input parameter to call service 'user_service::get_by_criteria()'"); return; }
       list_of_users_ptr list_of_users_output = list_of_users_ptr(new list_of_users());
       QSqlError err = user_manager().get_by_criteria(input->criteria, list_of_users_output);
       if (err.isValid()) { setMessageReturn(0, err.text()); return; }
       user_service_output_ptr output = user_service_output_ptr(new user_service_output());
       output->list_of_users = list_of_users_output;
       setOutputParameter(output);
       setMessageReturn(true);
    }

    #endif // _QX_SERVICE_MODE_CLIENT

A ce niveau du tutoriel, notre serveur d'applications C++ est termin� et propose plusieurs services.
Il reste � pr�sent � �crire le code client qui va appeler tous les services que nous avons mis en place...
3- Cr�ation de l'interface cliente : qxClient
De la m�me fa�on que le projet qxServer, le projet qxClient poss�de une interface utilisateur construite avec l'outil Qt Designer de la biblioth�que Qt.
Cette interface poss�de plusieurs boutons pour appeler l'ensemble des services propos�s par notre serveur d'applications.
L'interface permet �galement d'indiquer une adresse ip et un num�ro de port pour se connecter au serveur d'applications.
3.1- Description de la m�thode onClickBtnDateTime()
Comment r�cup�rer la date-heure courante du serveur d'applications ?
Voici le code qui s'ex�cute lorsque l'utilisateur clique sur le bouton Get Server DateTime :

    void main_dlg::onClickBtnDateTime()
    {
       // Cr�ation d'une instance de service et appel � la m�thode pour recevoir la date-heure courante du serveur
       server_infos service;
       service.get_current_date_time();
       // Affiche la derni�re transaction au format XML (ou JSON)
       updateLastTransactionLog(service.getTransaction());
    }

Comme vous pouvez le constater, la partie cliente n'a aucun code sp�cifique � �crire pour pouvoir appeler un service.
Il suffit d'instancier un service, puis d'appeler la m�thode qui nous int�resse : get_current_date_time().
La m�thode updateLastTransactionLog() permet d'afficher la derni�re transaction client-serveur (au format XML ou JSON) ex�cut�e.
Si une erreur s'est produite, alors un message appara�t � l'�cran pour le signaler � l'utilisateur.
Pour savoir si le service s'est ex�cut� correctement, il faut utiliser la m�thode : service.getMessageReturn(); (de type qx_bool qui peut contenir un code et un libell� d'une �ventuelle erreur).
Enfin, pour r�cup�rer la r�ponse du serveur (donc sa date-heure courante), il faut utiliser la m�thode : service.getOutputParameter(); (de type user_service_output_ptr).
3.2- Description de la m�thode onClickBtnDateTimeAsync()

    void main_dlg::onClickBtnDateTimeAsync()
    {
       if (m_pDateTimeAsync) { qDebug("[QxOrm] '%s' transaction is already running", "server_infos::get_current_date_time"); return; }
       // Cr�ation d'une instance de service et appel � la m�thode pour recevoir la date-heure courante du serveur (mode asynchrone)
       server_infos_ptr service = server_infos_ptr(new server_infos());
       m_pDateTimeAsync.reset(new qx::service::QxClientAsync());
       QObject::connect(m_pDateTimeAsync.get(), SIGNAL(finished()), this, SLOT(onDateTimeAsyncFinished()));
       m_pDateTimeAsync->setService(service, "get_current_date_time");
       m_pDateTimeAsync->start();
    }

    void main_dlg::onDateTimeAsyncFinished()
    {
       if (! m_pDateTimeAsync || ! m_pDateTimeAsync->getService()) { return; }
       updateLastTransactionLog(m_pDateTimeAsync->getService()->getTransaction());
       m_pDateTimeAsync.reset();
    }

Ce second exemple correspond au bouton Get Server DateTime Async de l'interface utilisateur.
Il montre comment appeler un service de mani�re asynchrone, c'est-�-dire sans bloquer l'IHM en attendant la r�ponse du serveur.
La biblioth�que QxOrm propose la classe qx::service::QxClientAsync pour simplifier les appels asynchrones.
Le m�canisme des appels asynchrones avec le module QxService est tr�s simple :

- cr�ation d'une instance d'un service ;
- cr�ation d'une instance de type qx::service::QxClientAsync ;
- connexion � l'�v�nement finished (pour indiquer qu'une r�ponse du serveur vient d'arriver) ;
- passage de l'instance du service et de la m�thode � appeler (sous forme de chaine de caract�res) � l'objet qx::service::QxClientAsync ;
- d�marrage de la transaction avec l'appel de la m�thode start().

3.3- Description de la m�thode onClickBtnAddUser()

    void main_dlg::onClickBtnAddUser()
    {
       // Cr�ation des param�tres d'entr�e contenant l'utilisateur � ajouter en BDD
       user_service_input_ptr input = user_service_input_ptr(new user_service_input());
       input->user = fileUser();
       // Cr�ation d'une instance de service et association des param�tres d'entr�e
       user_service service;
       service.setInputParameter(input);
       service.insert();
       // Si la transaction s'est d�roul�e correctement, on affiche l'identifiant qui vient d'�tre ajout� en BDD
       user_ptr output = (service.isValidWithOutput() ? service.getOutputParameter()->user : user_ptr());
       if (output) { fillUser(output); }
       // Affiche la derni�re transaction au format XML (ou JSON)
       updateLastTransactionLog(service.getTransaction());
    }

Ce 3�me exemple correspond au bouton Add dans la section User transaction.
Il permet � l'utilisateur d'ajouter une nouvelle personne dans la base de donn�es.
Cet exemple nous montre comment passer une structure (classe user) en param�tre d'entr�e d'un service.
La m�thode fileUser() permet de cr�er une instance de type user et de valoriser ses propri�t�s en fonction des champs de l'IHM.
Cette instance est ensuite utilis�e comme param�tre d'entr�e de notre service.
Si la transaction s'est d�roul�e correctement, le param�tre de retour (r�ponse du serveur) contient lui aussi une instance de type user avec le nouvel identifiant qui vient d'�tre ajout� en base de donn�es.
On utilise alors la m�thode fillUser() pour mettre � jour l'interface utilisateur en fonction de la r�ponse du serveur et afficher ainsi le nouvel identifiant.
3.4- Description de la m�thode onClickBtnGetAllUsers()

    void main_dlg::onClickBtnGetAllUsers()
    {
       // Cr�ation d'une instance de service
       user_service service;
       service.fetch_all();
       // Si la transaction s'est d�roul�e correctement, affiche un message avec le nombre d'utilisateurs stock�s en BDD
       list_of_users_ptr output = (service.isValidWithOutput() ? service.getOutputParameter()->list_of_users : list_of_users_ptr());
       if (output) { QMessageBox::information(this, "qxClient - get all users", "database contains '" + QString::number(output->size()) + "' user(s)."); }
       // Affiche la derni�re transaction au format XML (ou JSON)
       updateLastTransactionLog(service.getTransaction());
    }

Ce 4�me exemple correspond au bouton Get All de la section User transaction.
Il permet de r�cup�rer la liste de tous les user pr�sents dans la base de donn�es.
Le param�tre de retour est une liste fortement typ�e : il est possible d'utiliser les collections des biblioth�ques stl, boost, Qt ou qx::QxCollection.
Le module QxService permet donc d'�changer des structures complexes entre client et serveur.
A pr�sent, bon courage avec le module QxService... ;o)

| � 2011-202XDL Teamty - ic-east.com |