QxOrm Windows Linux Macintosh C++

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
Version fran�aise du site Web site english version
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 : Remarque : ce tutoriel est �galement disponible sur le site www.developpez.com.
qt_ambassador
QxOrm library has been accepted into the Qt Ambassador Program

gui_qxClientServer

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 :
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 :

gui_qxServer


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.

gui_qxClient

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)


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